对象引用和复制 - Object 基础

  1. 字面量对象的相等性
  2. 克隆与合并
    1. Object.assign
    2. 深层克隆

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

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 属性


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论。
我的空间