深拷贝中容易被忽略的问题-原型链

499 阅读2分钟

前言

前言

总所周知, 实现深拷贝最知名的两种方案:JSON.parse(JSON.stringify()) 和 递归拷贝

JSON拷贝会破坏原型链结构,导致拷贝后的自定义对象实例无法使用类的静态方法(非内置对象)

下面先进行测试(请略过),再改造

JSON深拷贝原型链测试

  1. 创建Person类

    class Person {
      constructor(uname, uage) {
        this.uname = uname
        this.uage = uage
      }
    }
    
  2. 创建person实例,测试原型指向

    
    const person = new Person('zhangsan', 18)
    ​
    console.log(person instanceof Person);  // true
    console.log(person.__proto__ === Person.prototype) //true
    console.log(person.constructor === Person) // true
    

    可以看到person实例的构造器指向Person类

  3. 然后,我们通过JSON.parse()、JSON.stringify() 拷贝一份实例,测试原型指向

    
    const clonePer = JSON.parse(JSON.stringify(person))
    ​
    console.log(clonePer instanceof Person) // false
    console.log(clonePer.__proto__ === Person.prototype) // false
    console.log(clonePer.constructor === Person)    // falseconsole.log(clonePer.__proto__ === Object.prototype) // true
    console.log(clonePer.constructor === Object)    // true
    

    JSON克隆的对象原型链已经被改变, 直接指向了Object

可以看到,使用JSON克隆的对象,其原型链已经被破坏。

下面使用递归克隆测试下原型链是否被破坏。

递归深拷贝原型链测试

  1. 创建递归深拷贝函数

    function deepClone(target) {
      // 对象类型
      if (target instanceof Object) {
        let newObj = {} // new Object()的语法糖, 破坏原型链的罪魁祸首
    ​
        // 处理symbol/bigint/内置对象/循环引用等,就不写了...
    ​
        for (const key in target) {
          if (Object.hasOwnProperty.call(target, key)) {
            newObj[key] = deepClone(target[key])
          }
        }
        return newObj
      } else {
        return target
      }
    }
    
  2. 使用递归克隆person实例,

    const clonePer = deepClone(person);
    ​
    console.log(clonePer instanceof Person) // false
    console.log(clonePer.__proto__ === Person.prototype) // false
    console.log(clonePer.constructor === Person)  // falseconsole.log(clonePer.__proto__ === Object.prototype) // true
    console.log(clonePer.constructor === Object)  // true
    

    可以发现使用new Object()创建的对象, 原型指向了Object

* 改造深拷贝函数,设置原型指向

function deepClone(target) {
  // 对象类型
  if (target instanceof Object) {
    // 以下几种方式, 都可改变原型指向
      
    // let newObj = {}
    // Object.setPrototypeOf(newObj, target.__proto__)
​
    // let newObj = {}
    // newObj.__proto__ = target.__proto__
​
    // let newObj = Object.create(target.__proto__);
​
    let newObj = Reflect.construct(target.constructor, {})
    // 等同于 Object.create(target.__proto__),
    // Reflect更像是使用new创建的对象,不兼容IE
​
    // 处理symbol/bigint/内置对象/循环引用等, 忽略...
​
    for (const key in target) {
      if (Object.hasOwnProperty.call(target, key)) {
        newObj[key] = deepClone(target[key])
      }
    }
    return newObj
  } else {
    return target
  }
}

重新进行原型链测试

const clonePer = deepClone(person);
​
console.log(clonePer instanceof Person) // true
console.log(clonePer.__proto__ === Person.prototype) // true
console.log(clonePer.constructor === Person)  // trueconsole.log(clonePer.__proto__ === Object.prototype) // false
console.log(clonePer.constructor === Object)  // false

下面测试继承的情况:

class Person {
  constructor(uname, uage) {
    this.uname = uname
    this.uage = uage
  }
}
class Student extends Person {
  constructor(uname, uage, school) {
    super(uname, uage)
    this.school = school
  }
}
​
// 创建子类实例
const student = new Student('lisi', 8, '三年级一班')
// 深拷贝
const cloneStu = deepClone(student)
​
console.log(cloneStu instanceof Student) // true
console.log(cloneStu instanceof Person) // true
console.log(cloneStu.__proto__ === Student.prototype) // true
console.log(cloneStu.constructor === Student) // trueconst proto = cloneStu.__proto__
console.log(proto.__proto__ === Person.prototype) // true
console.log(proto.__proto__.constructor === Person) // true
console.log(proto.__proto__.__proto__ === Object.prototype) // true

参考

如何写出一个惊艳面试官的深拷贝?

实现深拷贝

Reflect