Javascript对象拷贝

682 阅读4分钟

「这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战

Javascript对象拷贝

JS中的原始值和引用值概念

ECMAScript 变量可以包含两种不同类型的数据:原始值和引用值。原始值(primitive value)就是最简单的数据,引用值(reference value)则是由多个值构成的对象。

在把一个值赋给变量时,JavaScript 引擎必须确定这个值是原始值还是引用值。JS中有6种原始值:Undefined、Null、Boolean、Number、String 和 Symbol(6种基础类型)。保存原始值的变量是按值(byvalue)访问的,因为我们操作的就是存储在变量中的实际值。

引用值是保存在内存中的对象。与其他语言不同,JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身。为此,保存引用值的变量是按引用(by reference)访问的

原始值和引用值的定义方式很类似,都是创建一个变量,然后给它赋一个值。不过,在变量保存了这个值之后,可以对这个值做什么,则大有不同。对于引用值而言,可以随时添加、修改和删除其属性和方法,。比如,看下面的例子:

let person = new Object();
person.name = "Nicholas";
console.log(person.name); // "Nicholas"

原始值不能有属性,尽管尝试给原始值添加属性不会报错。比如:

let name = "Nicholas";
name.age = 27;
console.log(name.age); // undefined

除了存储方式不同,原始值和引用值在通过变量复制时也有所不同。在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置

let num1 = 5;
let num2 = num1;

这里,num1 包含数值 5。当把 num2 初始化为 num1 时,num2 也会得到数值 5。这个值跟存储在 num1 中的 5 是完全独立的,因为它是那个值的副本。这两个变量可以独立使用,互不干扰

image.png 在把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完成后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来

let obj1 = new Object(); 
let obj2 = obj1; 
obj1.name = "Nicholas"; 
console.log(obj2.name); // "Nicholas"

在这个例子中,变量 obj1 保存了一个新对象的实例。然后,这个值被复制到 obj2,此时两个变量都指向了同一个对象。在给 obj1 创建属性 name 并赋值后,通过 obj2 也可以访问这个属性,因为它们都指向同一个对象。下图展示了变量与堆内存中对象之间的关系。

image.png

浅拷贝

浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象

image.png

  • 直接赋值:任何操作都会影响原对象
let obj2 = obj1;
  • Object.assign: 拷贝属性值,假如属性值是一个对象的引用,那么也会指向那个引用,就是上文所说的拷贝的是内存地址
let obj2 = Object.assign({},obj1);
  • Array.prototype.slice();

    • 提取数组;
    • 参数(可选):startIndex,endIndex(与索引值一致);
    • 拷贝规则同Object.assign;
    let arr2 = arr1.slice(start,end);
    
  • 扩展运算符(...);

    • 拷贝规则同Object.assign;
    let obj2 = {obj1} or [...obj1];
    
  • lodash(_.clone());

    • 拷贝规则同Object.assign;
    let obj2 = _.clone(obj1);
    

深拷贝

将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象 image.png

  • JSON.parse(JSON.stringify());

    • 将字符串parse后创建新对象
    let obj2=JSON.parse(JSON.stringify(obj1));
    
    • 如果这个对象里属性是function,不会拷贝成功,
    let obj = {
        fun: function name(params) {
        }
    }
    
    console.log(obj);// { fun: [Function: name] }
    let temp = JSON.parse(JSON.stringify(obj))
    console.log(temp);// {}
    
    
    • 如果被拷贝的对象中某个属性的值为undefined,则拷贝之后该属性会丢失
    let obj = {
        a:2,
        name:undefined
    }
    
    console.log(obj);// {a:2, name: undefined }
    let temp = JSON.parse(JSON.stringify(obj))
    console.log(temp);// {a:2}
    
    
    • 如果被拷贝的对象中有正则表达式,则拷贝之后的对象正则表达式会变成Object,但对象是{}
    let obj = {
        a:2,
        name:/abc/
    }
    
    console.log(obj);// { a:2, name: /abc/ }
    let temp = JSON.parse(JSON.stringify(obj))
    console.log(temp);// {a:2, name:{}}
    
    • 对象数组中的拷贝也是一样
    let obj = [{ name: undefined }]
    console.log(obj);// [ { name: undefined } ]
    let temp = JSON.parse(JSON.stringify(obj))
    console.log(temp);// [ {} ]
    
  • 递归赋值;

    function deepClone(obj){
        let objClone = Array.isArray(obj)?[]:{};
        if(obj && typeof obj==="object"){
            for(key in obj){
                //判断是否为自身属性
                if(obj.hasOwnProperty(key)){
                    //判断ojb子元素是否为对象,如果是,递归复制
                    if(obj[key]&&typeof obj[key] ==="object"){
                        objClone[key] = deepClone(obj[key]);
                    }else{
                        //如果不是,简单复制
                        objClone[key] = obj[key];
                    }
                }
            }
        }
        return objClone;
    }
    
  • lodash(_.cloneDeep());

    let obj2 = _.cloneDeep(obj1);