在前端开发中,我们经常需要对对象进行克隆操作。克隆操作可以帮助我们避免不必要的副作用,保证程序的正确性。然而,在 ECMAScript 2015 (ES6) 之前,克隆对象并不是一件容易的事情。常见的克隆方法,如 Object.assign 和 JSON.parse(JSON.stringify),都存在一些问题。在 ECMAScript 2020 (ES11) 中,这些问题终于得到了彻底的解决。
Object.assign 的问题
Object.assign 是 ECMAScript 2015 (ES6) 中新增的一个方法,用于将多个对象合并成一个对象。它的语法如下:
Object.assign(target, ...sources)
其中,target 是目标对象,sources 是一个或多个源对象。Object.assign 会将源对象的属性复制到目标对象中,并返回目标对象。例如:
const obj1 = { a: 1 }; const obj2 = { b: 2 }; const obj3 = Object.assign({}, obj1, obj2); console.log(obj3); // { a: 1, b: 2 }
Object.assign 的问题在于,它只能复制对象的自身属性,不能复制继承属性。例如:
-- -------------------- ---- ------- ----- ------ - ------------- - ------ - -- - - ----- ----- ------- ------ - ------------- - -------- ------ - -- - - ----- ----- - --- -------- ----- ----------- - ----------------- ------- ------------------------- -- - -- - -
在上面的例子中,Child 类继承自 Parent 类,Parent 类有一个属性 a。然而,使用 Object.assign 克隆 Child 实例时,并没有复制属性 a。这是因为 Object.assign 只能复制对象的自身属性,不能复制继承属性。
JSON.parse(JSON.stringify) 的问题
为了解决 Object.assign 不能复制继承属性的问题,很多人开始使用 JSON.parse(JSON.stringify) 进行克隆。例如:
const child = new Child(); const clonedChild = JSON.parse(JSON.stringify(child)); console.log(clonedChild); // { a: 1, b: 2 }
JSON.parse(JSON.stringify) 的原理是先将对象序列化成 JSON 字符串,然后再将 JSON 字符串解析成对象。这样做可以复制对象的所有属性,包括继承属性。然而,JSON.parse(JSON.stringify) 也存在一些问题。
首先,它不能复制函数和 undefined 值。例如:
const obj = { a: 1, b: undefined, c: () => {} }; const clonedObj = JSON.parse(JSON.stringify(obj)); console.log(clonedObj); // { a: 1 }
在上面的例子中,对象 obj 中有一个属性 b 的值为 undefined,还有一个属性 c 的值为函数。使用 JSON.parse(JSON.stringify) 克隆时,并没有复制属性 b 和 c。
其次,它不能处理循环引用的对象。例如:
const obj = { a: 1 }; obj.self = obj; const clonedObj = JSON.parse(JSON.stringify(obj)); // 报错
在上面的例子中,对象 obj 中有一个属性 self 的值为 obj 自身。使用 JSON.parse(JSON.stringify) 克隆时,会抛出错误。
ECMAScript 2020 (ES11) 中的解决方案
在 ECMAScript 2020 (ES11) 中,新增了两个方法,用于解决克隆问题。它们分别是 Object.hasOwn 和 Object.getOwnPropertyDescriptors。
Object.hasOwn
Object.hasOwn 是一个实例方法,用于判断一个对象是否有指定的自身属性。它的语法如下:
Object.hasOwn(obj, prop)
其中,obj 是要检查的对象,prop 是要检查的属性名。如果 obj 有 prop 属性,则返回 true,否则返回 false。例如:
-- -------------------- ---- ------- ----- ------ - ------------- - ------ - -- - - ----- ----- ------- ------ - ------------- - -------- ------ - -- - - ----- ----- - --- -------- -------------------------------- ------ -- ---- -------------------------------- ------ -- ---- -------------------------------- ------------- -- -----
在上面的例子中,使用 Object.hasOwn 判断 Child 实例是否有属性 a、b 和 toString。
Object.getOwnPropertyDescriptors
Object.getOwnPropertyDescriptors 是一个静态方法,用于获取一个对象的所有自身属性的描述符。它的语法如下:
Object.getOwnPropertyDescriptors(obj)
其中,obj 是要获取描述符的对象。它返回一个对象,该对象的属性名是 obj 的所有自身属性名,属性值是对应属性的描述符对象。例如:
-- -------------------- ---- ------- ----- ------ - ------------- - ------ - -- - - ----- ----- ------- ------ - ------------- - -------- ------ - -- - - ----- ----- - --- -------- -----------------------------------------------------
在上面的例子中,使用 Object.getOwnPropertyDescriptors 获取 Child 实例的所有自身属性的描述符。
使用 Object.fromEntries 和 Object.create 进行克隆
有了 Object.hasOwn 和 Object.getOwnPropertyDescriptors,我们可以使用 Object.fromEntries 和 Object.create 进行克隆了。例如:
-- -------------------- ---- ------- ----- ------ - ------------- - ------ - -- - - ----- ----- ------- ------ - ------------- - -------- ------ - -- - - ----- ----- - --- -------- ----- ----------- - -------------- ----------------------------- ------------------- --------------------------------------- - -- -------------------------
在上面的例子中,使用 Object.create 创建一个与 Child 实例原型相同的对象,然后使用 Object.fromEntries 和 Object.getOwnPropertyDescriptors 复制 Child 实例的所有自身属性和描述符。这样做可以复制对象的所有属性,包括继承属性,也可以复制函数和 undefined 值,还可以处理循环引用的对象。
总结
在 ECMAScript 2020 (ES11) 中,新增了 Object.hasOwn 和 Object.getOwnPropertyDescriptors 两个方法,用于解决克隆对象时的一些问题。使用 Object.fromEntries 和 Object.create 可以利用这两个方法进行克隆,可以复制对象的所有属性,包括继承属性,也可以复制函数和 undefined 值,还可以处理循环引用的对象。这些方法的出现,为前端开发带来了更多的可能性,也提高了开发效率。
来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/65d07c77add4f0e0ff975523