【面试】深浅拷贝与循环处理

110 阅读2分钟

题目一 实现浅拷贝

js库提供的方法基本是浅拷贝,Object.assign、扩展运算符、数组slice、concat,如果里面的元素是对象类型,则是共享的,会相互影响。

手写浅拷贝实现如下:

function shallowCopy(obj) {
    if (!obj || typeof obj !== 'object') {
        return;
    }
    let result = Array.isArray(obj) ? [] : {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            result[key] = obj[key];
        }
    }
    return result;
}

题目二 实现深拷贝

拷贝的对象值之间不能相互影响。实现深拷贝的方式有:

1. JSON.stringify

但有如下问题:

  1. 不能拷贝function、symbol、undefined类型, 执行后都会消失,如果值在数组中,则变为null
  2. 如果有循环引用会直接报错
  3. date类型会变为字符串
  4. 除数组外属性顺序不保证

image.png

2. 手写深拷贝

  1. map处理循环引用,存储已经cp过的对象,如果处理过直接返回,否则会无限递归,结果对应的循环属性会变为undefined
  2. 初始化要判断是对象还是数组
function deepCopy(obj) {
    const map = new Map();
    const clone = (obj) => {
        if (!obj || typeof obj !== 'object') {
            return;
        }
        console.log(map);
        if (map.has(obj)) {
            return map.get(obj);
        }
        let result = Array.isArray(obj) ? [] : {};
        map.set(obj, result);
        for (let key in obj) {
            if (obj.hasOwnProperty(key)) {
                const val = obj[key];
                if (typeof val === 'object') {
                    result[key] = clone(val);
                } else {
                    result[key] = val;
                }
            }
        }
        return result;
    }
    return clone(obj);
}

var obj1 = {a: 1, b:2, c: [1, 2], d: {a: 1, b: 1}};
obj1.e = obj1;
deepCopy(obj1);

image.png

题目三 手写Object.assign

  1. 过滤空对象
  2. 只拷贝自身属性
  3. 过滤undefined属性
function myAssign(target, ...source) {
    if (target === null) {
        throw new TypeError('not null or undefined');
    }
    source.forEach(obj => {
        if (!obj) {
            return;       
        }
        for (let key in obj) {
            if (obj.hasOwnProperty(key) && obj[key] !== undefined) {
                target[key] = obj[key];
            }
        }
    });
    return target;
}

题目四 手写extend实现深拷贝

这个其实和assign类似,但是本次我们实现深拷贝,和上面题目三注意点都要有:

  1. 类型判断
  2. 循环引用
  3. 过滤空对象,过滤undefined属性
function extend(target, ...source) {
    if (typeof target !== 'object') {
        return;
    }
    source.forEach(obj => {
        if (!obj) {
            return;
        }
        for (let key in obj) {
            const sourceVal = obj[key];
            if (sourceVal === undefined) {
                return;
            }
            if (typeof sourceVal !== 'object') {
                target[key] = sourceVal;
            } else {
                // 处理a.b = a的循环引用情况
                if (sourceVal === target[key]) {
                    continue;
                }
                target[key] = Array.isArray(sourceVal) ? [] : {};
                target[key] = extend(target[key], sourceVal);
            }
        }
    });
    return target;
}

var a = {
    a:1,
    b: 2
}
var b = {
    c: [1, 2],
    d: {a: 'a'},
    e: 3
}

// 形成自身循环引用
b.f = b;
c = extend(a, b);
console.log(c)
b.c.push(3)
console.log(b.c, c.c)

题目五 判断两个对象相等

function equal(obj1, obj2) {
    if (obj1 === obj2) {
        return true;
    } 
    if (Object.keys(obj1).length !== Object.keys(obj2).length) {
        return false;
    }
    for (let key in obj1) {
        if (!obj1.hasOwnProperty(key)) {
            continue;
        }
        if (!obj2.hasOwnProperty(key)) {
            return false;
        }
        const val = obj1[key];
        if (typeof val !== 'object') {
             if (val !== obj2[key]) {
                 return false;
             }
        } else if (!equal(val, obj2[key])){
            return false;
        }
    }
    return true
}

相关知识点

说到循环引用处理,就想到

  1. node和es6的模块引用,对循环引用的处理是不一样的。
  2. js中垃圾回收机制:引用计数与标记清除

引用计数: