对象引用和复制-Object基础

把对象赋值给变量时,并不是在变量里存储了对象,而是在变量里存储了对象在内存中的地址
所以对象的存储和引用的存储不是绑定的。

let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // 通过 "admin" 引用来修改
alert(user.name); // 'Pete',修改能通过 "user" 引用看到

user变量和admin变量就像带有两把钥匙的柜子,使用其中一把钥匙打开柜子做了变动,另一把钥匙后面打开柜子是,会看到之前的变动。

字面量对象的相等性

只有当两个变量指向同一个对象时,才是相等的。

克隆与合并

最简单的方法:

let a = {
    age: 99,
    likes: ["game", "book"],
    action: function () {
        console.log(this.likes);
    },
}

let b = {
    age: 9999,
    link2: a,
}
a["link1"] = b;

a.action();

let copy = {};
for (let k in a) {
    copy[k] = a[k];
}
for (let k in b) {
    copy[k] = b[k];
}
console.log(copy);  //循环引用可以正常复制, 不会报错

copy.action();

console.log(copy.action === a.action);  // true
console.log(copy.likes === a.likes);  // true

缺点是引用类型(包括函数)都是直接复制的引用。

Object.assign

与前面的简单方法一样。缺点也一样。

let copy = Object.assign({}, a, b);

深层克隆

如果某个属性的值也是一个对象,那么也要复制它的结构。这就叫“深拷贝“。

参考文章

let a = [1,2,3];
let f = function(){
}
let d = new Date();
typeof a //'object'
typeof f //'function'
typeof d //'object'

由于typeof无法判断object和array。可以使用object.prototype.toString方法,但是直接使用实例对象.toString()调用时,数组还是返回的"[object Object]",时间Date的实例对象返回的是日期字符串。

所以,深复制的关键是递归调用和类型判断

let a = [1,2,3];
let f = function(){
}
let d = new Date();
ff.toString(); // "function ff() {}"
d.toString(); // "Mon Aug 02 2021 17:25:28 GMT+0800 (中国标准时间)"
a.toString(); // "1,2,3"

原因是因为这些对象的的原型上已经覆盖了toString方法。
类型判断的一个解决方法是,使用Object.prototype.toString方法时绑定当前实例对象Object的toString方法上。

function typeValue(value) {
    let typeMap = {
        '[object Array]': 'array',
        '[object Function]': 'function',
        '[object Object]': 'object',
    }
    return typeMap[Object.prototype.toString.call(obj)];
}
let a = [1,2,3];
let f = function(){
}
let d = new Date();
typeValue(f); // 'function'
typeValue(a); // 'array'
Object.prototype.toString.call(d); // '[object Date]'

在进行值的复制时,就可以判断这个值是数组还是其他类型了。

function deepClone(value) {
    let copy;  // 最终返回的复制后的值

    function clone(value) {
        if (typeValue(value) === 'array') {
            let temp = [];
            for (let e of value) {
                // 属性值递归clone
                temp.push(clone(e));
            }
            return temp;
        }
        if (typeValue(value) === 'object') {
            let temp = {};
            // 加上这一句是因为assign方法可以看到symbol属性,但是for...in看不到
            Object.assign(temp, value);
            for (let key in value) {
                // 数组元素还需要再进行clone
                temp[key] = clone(value[key]);
            }
            return temp;
        }
        // 既不是数组,也不是对象Object
        return value;
    }
    
    copy = clone(value);
    return copy;
}

此方法可以深复制一个嵌套了多个深度的对象。
由于Symbol属性的隐藏性,对for...in不可见,导致不能复制Symbol类型的属性。
Object.assign属性却可以复制Symbol属性