12_聊聊js中的数据类型,如何用js实现一个对象的深拷贝

一、栈堆,基本数据类型与引用数据类型

1、栈(stack)和堆(heap):stack为自动分配的内存空间,它由系统自动释放;而heap则是动态分配的内存,大小也不一定会自动释放。

2、基本的数据类型:String, Number, Boolean, Null, Undefined,Symbol(ES6新增)

特点: 存储的是该对象的实际数据,(存放在栈中)

3、对象数据类型(也称为引用数据类型):Array,Object,Function

特点: 存储的是该对象在栈中引用,真实的数据存放在堆内存里,(存放在堆内存中的对象,每个空间大小不一样,要根据情况进行特定的配置)

注:在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象

二、基本数据类型和引用数据类型区别

1、声明变量时内存分配不同

  • 原始类型:在栈中,因为占据空间是固定的,可以将他们存在较小的内存中-栈中,这样便于迅速查询变量的值
  • 引用类型:存在堆中,栈中存储的变量,只是用来查找堆中的引用地址。

这是因为:引用值的大小会改变,所以不能把它放在栈中,否则会降低变量查寻的速度。相反,放在变量的栈空间中的值是该对象存储在堆中的地址。地址的大小是固定的,所以把它存储在栈中对变量性能无任何负面影响

2、不同的内存分配带来不同的访问机制

在javascript中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆内存中的地址,然后再按照这个地址去获得这个对象中的值,这就是按引用访问。

而原始类型的值则可以直接访问。

3、复制变量时的不同

  • 原始值:在将一个保存着原始值的变量复制给另一个变量时,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value而已。

  • 引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,也就是说这两个变量都指向了堆内存中的同一个对象,他们中任何一个作出的改变都会反映在另一个身上。(这里要理解的一点就是,复制对象时并不会在堆内存中新生成一个一模一样的对象,只是多了一个保存指向这个对象指针的变量)。多了一个指针

4、参数传递的不同(把实参复制给形参的过程)

首先我们应该明确一点:ECMAScript中所有函数的参数都是按值来传递的。但是为什么涉及到原始类型与引用类型的值时仍然有区别呢?这是因为内存分配时的差别。

  • 原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
  • 引用值:对象变量它里面的值是这个对象在堆内存中的内存地址。

因此它传递的值也就是这个内存地址,这也就是为什么函数内部对这个参数的修改会体现在外部的原因,因为它们都指向同一个对象。

三、对象深拷贝

1、深拷贝与浅拷贝区别

深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的复制实体,而不是引用。对象数据存放的是对象在栈内存的引用,直接复制的是对象的引用。深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。

  • 浅拷贝 —-只是拷贝了基本类型的数据,而引用类型数据,复制后也是会发生引用,我们把这种拷贝叫做“浅拷贝”,换句话说,浅拷贝仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变。
  • 深拷贝 —-在计算机中开辟了一块新的内存地址用于存放复制的对象。

通俗一点理解就是浅拷贝出来的数据并不独立,如果被复制的对象改变了,那么浅拷贝的对象也会改变,深拷贝之后就会完全独立,与浅拷贝断绝关系。

2、对象深拷贝的实现方式

(1)JSON.parse(JSON.stringify())

let arr = [1, 3, {
    username: 'kobe'
  }];
let arr2 = JSON.parse(JSON.stringify(arr));
arr2[2].username = '小林老师';
console.log(arr, arr2)

原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。

这种方法虽然可以实现数组或对象深拷贝,但不能处理函数。

(2)手写递归方法实现拷贝所有层级属性

function deepCopyTwo(obj) {
    let objClone = Array.isArray(obj) ? [] : {};
    if (obj && typeof obj == 'object') {
        for (const key in obj) {
            //判断obj子元素是否为对象,如果是,递归复制
            if (obj[key] && typeof obj[key] === "object") {
                objClone[key] = deepCopyTwo(obj[key]);
            } else {
                //如果不是,简单复制
                objClone[key] = obj[key];
            }
        }
    }
    return objClone;
}

测试:

let obj1 = {
    a: 1,
    b: { f: { g: 1 } },
    c: [1, 2, 3]
  }
let obj2 = obj1;
let obj3 = deepCopyTwo(obj1)
console.log(obj1.b.f === obj2.b.f) // true
console.log(obj1.b.f === obj3.b.f) // false

(3)函数库lodash

const _ = require('lodash');
let obj1 = {
    a: 1,
    b: { f:  { g: 1} },
    c: [1, 2, 3]
}
let obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f) // false
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。