浅拷贝

复制基本类型和值

var a = 1;
b = a;
a = 2;
console.log(a, b);

2 1

深拷贝

对引用类型拷贝,会进行地址的拷贝
引用类型

var a = { c: 1 };
b = a;
a.c = 2;
console.log(a.c, b.c);

2 2

浅拷贝的实现

function shallowClone(source) {
    var target = {};
    for (var i in source) {
        if (source.hasObject(i)) {
            target[i] == source[i];
        }
    }
    return target;
}

上述代码相关:for in, Object.keys和Object.getOwnPropertyNames的区别

const linglingkwong = Object.create({ age: 30 });
Object.defineProperty(linglingkwong, 'sex', { value: 'female', enumerable: false });
linglingkwong[Symbol('partner')] = 'orm';  // symbol 用来表示唯一的、不可变的值
linglingkwong.own = 1;

// for-in
for (const k in linglingkwong)
    console.log(k)  // own age -- 只枚举

// Object.keys
console.log(Object.keys(linglingkwong));  // ['own'] -- 自身,可枚举

// Object.getOwnPropertyNames
console.log(Object.getOwnPropertyNames(linglingkwong));  // ['sex', 'own']

为什么会出现不同的结果?
上述对象 linglingkwong 中有三种属性

  1. 原型链
  2. 不可枚举
  3. Symbol

如何区分这三种属性呢?
JavaScript 中的 Object.create() 方法用于创建一个新对象,并指定该对象的原型。
即,age 是 linglingkwong 的原型。

const linglingkwong = Object.create({ age: 30 });

创建了一个空对象,它的原型指向 {age : 30}
{age : 30}创建了一个新的匿名对象,它有一个属性age,值为30。
Object.create(...)以这个匿名对象为原型,创建了一个新对象。
const linglingkwong = ... 将这个新对象赋值给 linglingkwong

即 age 为原型。

enumerable:true时,为可枚举。它是描述内部属性的。

Symbol是对象属性的属性,它表示该属性是唯一的。

由上述结果可知:
三种方式种只有 for-in 可以访问原型链。
当内部属性为不可枚举时,不能用for-in遍历到,也不能用Object.keys访问。
上述三种方法中,只有Object.getOwnPropertySymbols(obj) 可以访问Symbol属性。

深拷贝的实现

弱点:存在爆栈问题,循环引用问题

function clone(source) {
    var target = {};
    for (var i in source) {
        if (source.hasOwnProperty(i)) {
            if (typeof source[i] === 'object') {
                target[i] = clone(source[i]);
            } else {
                target[i] = source[i];
            }
        }
    }
    return target;
}

不过,上述代码中仍存在一些问题。

没有对参数做检验

函数需要校验参数,不是对象的话直接返回。

if (!isObject(source)) return source;

不过上述判断是否为对象的逻辑不严谨,如果遇到null,会误判成object。
因此,采用:

if (source == null || !isObject(source)) return source;

解决循环引用的问题

if (hash.has(source)) return hash.get(source);

没有考虑数组的兼容

需要区分数组和普通对象

const  target = Array.isArray(source) ? [] : {};
hash.set (source, target);
for (const key of Reflect.ownKeys(source)){
	target[key] = clone(source[key], hash);
}

一行代码的深拷贝

弱点:存在循环引用问题,爆栈问题

function cloneJSON(source) {
    return JSON.parse(JSON.stringify(source));
}

破解循环引用

// 遍历uniqueList
function find(arr, item) {
    for (let i = 0; i < arr.length; i++) {
        if (arr[i].source === item) {
            return arr[i]
        }
    }
    return null;
}
function cloneForce(x) {
    const uniqueList = []; // 用来去重

    let root = {};
    // 循环数组
    const loopList = [
        {
            parent: root,
            key: undefined,
            data: x,
        }
    ];
    while (loopList.length) {
        // 深度优先
        const code = loopList.pop();
        const parent = node.parent;
        const key = node.key;
        const data = node.data;

        // 初始化赋值目标 key为undefined则拷贝到父元素,否则拷贝到子元素
        let res = parent;
        if (typeof key !== 'undefined') {
            res = parent[key] = {};
        }


        let uniqueData = find(uniqueList, data);
        // 数据已经存在
        if (uniqueData) {
            parent[key] = uniqueData.target;
            break;
        }

        // 数据不存在
        // 保存数据源 在拷贝数据中对应呃引用
        uniqueList.push({
            source: data,
            target: res,
        })
        for (let k in data) {
            if (data.hasOwnProperty(k)) {
                if (typeof data[k] === 'object') {
                    // 下一次循环
                    loopList.push({
                        parent: res,
                        key: k,
                        data: data[k],
                    });
                } else {
                    res[k] = data[k];
                }
            }
        }
    }
    return root;
}

更多推荐