JavaScript 中的对象拷贝

2023-05-27 0 334

第一类是 JavaScript 的基本块。第一类是特性的子集,特性是数组对。JavaScript 中的基本上大部份第一类都是坐落于蓝本链顶端 Object 的示例。

如是说

如你知晓,表达式操作方式符不能建立两个第一类的复本,它只重新分配两个提及,他们上看下面的标识符:

let obj = { a: 1, b: 2,};let copy = obj;obj.a = 5;console.log(copy.a);// 结论 // copy.a = 5;

obj 表达式是两个新第一类调用的罐子。copy 表达式对准同两个第一类,是对该第一类的提及。因此那时有三种方式能出访那个 { a: 1, b: 2, } 第一类。你要透过 obj 表达式或 copy 表达式,不论你是透过哪种方式对那个第一类展开的任何人操作方式单厢负面影响该第一类。

相对性(Immutability)前段时间被广为地谈及,那个很关键!下面示例的方式消解了任何人方式的相对性,假如原初第一类被你的代码的另一小部分采用,则可能将引致bug。

复本第一类的原初方式

复本第一类的原初方式是循环式结点原初第一类,接着两个接两个地复本每一特性。他们上看一看这段标识符:

function copy(mainObj) {let objCopy = {}; // objCopy 将储存 mainObj 的复本let key;for (key in mainObj) { objCopy[key] = mainObj[key]; // 将每一特性复本到objCopy第一类}return objCopy;}const mainObj = { a: 2, b: 5, c: { x: 7, y: 4,},}console.log(copy(mainObj));

存有的难题

objCopy 第一类具备两个捷伊 Object.prototype方式,这与 mainObj 第一类的蓝本方式相同,这并非他们想的。他们须要准确的复本原初第一类。特性描述符不能被复本。值为 false 的 “可写(writable)” 描述符在 objCopy 第一类中为 true 。下面的标识符只复本了 mainObj 的可枚举特性。假如原初第一类中的两个特性本身就是一个第一类,那么复本和原初第一类之间将共享那个第一类,从而使其各自的特性对准同两个第一类。

关于第2点中的 writable 特性:

当 writable 设置为false时,表示不可写,也就是说特性不能被修改。

var o = {}; // Creates a new objectObject.defineProperty(o, a, {value: 37, writable: false});console.log(o.a); // logs 37o.a = 25; // No error thrown// (it would throw in strict mode,// even if the value had been the same)console.log(o.a); // logs 37. The assignment didnt work.// strict mode(function() {use strict;var o = {};Object.defineProperty(o, b, {value: 2, writable: false}); o.b = 3; // throws TypeError: “b” is read-onlyreturn o.b; // returns 2 without the line above}());

正如上例中看到的,修改两个 non-writable 的特性不能改变特性的值,同时也不能报异常。

详细查看:MDN 文档

浅复本第一类

当复本源第一类的顶级特性被复本而没有任何人提及,并且复本源第一类存有两个值为第一类的特性,被复本为两个提及时,那么我说那个第一类被浅复本。假如复本源第一类的特性值是第一类的提及,则只将该提及值复本到目标第一类。

浅层复本将复本顶级特性,但是嵌套第一类将在原初(源)第一类和复本(目标)第一类之间是共享。

采用 Object.assign() 方式

Object.assign() 方式用于将从两个或多个源第一类中的大部份可枚举的特性值复本到目标第一类。

let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy);// Result – { a: 1, b: 2 }

到目前为止。他们建立了两个 obj 的复本。让他们看一看是否存有相对性:

let obj = { a: 1, b: 2,};let objCopy = Object.assign({}, obj);console.log(objCopy); // result – { a: 1, b: 2 }objCopy.b = 89;console.log(objCopy); // result – { a: 1, b: 89 }console.log(obj); // result – { a: 1, b: 2 }

在下面的标识符中,他们将 objCopy 第一类中的特性 b 的值更改为 89 ,并且当他们在控制台中 log 修改后的 objCopy 第一类时,这些更改仅应用于 objCopy 。他们能看到最后一行标识符检查 obj 第一类并没有被修改。这意味着他们已经成功地建立了复本源第一类的复本,而且它没有提及。

Object.assign() 的陷阱

不要高兴的太早! 虽然他们成功地建立了两个复本,一切似乎都正常工作,记得他们讨论了浅复本? 他们上看一看那个例子:

let obj = { a: 1, b: { c: 2,},}let newObj = Object.assign({}, obj);console.log(newObj); // { a: 1, b: { c: 2} }obj.a = 10;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 1, b: { c: 2} }newObj.a = 20;console.log(obj); // { a: 10, b: { c: 2} }console.log(newObj); // { a: 20, b: { c: 2} }newObj.b.c = 30;console.log(obj); // { a: 10, b: { c: 30} }console.log(newObj); // { a: 20, b: { c: 30} }// 注意: newObj.b.c = 30; 为什么呢..

obj.b.c = 30 ?

这就是 Object.assign() 的陷阱。Object.assign 只是浅复本。 newObj.b 和 obj.b 都提及同两个第一类,没有单独复本,而是复本了对该第一类的提及。任何人对第一类特性的更改都适用于采用该第一类的大部份提及。他们如何解决那个难题?继续阅读…他们会在下一节给出修复方案。

注意:蓝本链上的特性和不可枚举的特性不能复本。 看这里:

let someObj = { a: 2,}let obj = Object.create(someObj, { b: {value: 2, }, c: {value: 3, enumerable: true, },});let objCopy = Object.assign({}, obj);console.log(objCopy); // { c: 3 }

someObj 是在 obj 的蓝本链,因此它不能被复本。

特性 b 是不可枚举特性。

特性 c 具备 可枚举(enumerable) 特性描述符,因此它能枚举。 这就是为什么它会被复本。

深度复本第一类

深度复本将复本遇到的每一第一类。复本和原初第一类不能共享任何人东西,因此它将是原件的复本。以下是采用 Object.assign() 遇到难题的修复方案。让他们探索一下。

采用 JSON.parse(JSON.stringify(object));

这能修复了他们之前提出的难题。那时 newObj.b 有两个复本而并非两个提及!这是深度复本第一类的一种方式。 这里有两个例子:

let obj = { a: 1, b: { c: 2,},}let newObj = JSON.parse(JSON.stringify(obj));obj.b.c = 20;console.log(obj); // { a: 1, b: { c: 20 } }console.log(newObj); // { a: 1, b: { c: 2 } } (两个捷伊第一类)

陷阱:不幸的是,此方式不能用于复本用户定义的第一类方式。 见下文。

复本第一类方式

方式是两个第一类的特性,它是两个函数。在以上的示例中,他们还没有复本第一类的方式。现在让他们尝试一下,采用他们学过的方式来建立复本。

let obj = { name: scotch.io,exec: function exec() {return true;},}let method1 = Object.assign({}, obj);let method2 = JSON.parse(JSON.stringify(obj));console.log(method1); //Object.assign({}, obj)/* result{ exec: function exec() { return true; }, name: “scotch.io”}*/console.log(method2); // JSON.parse(JSON.stringify(obj))/* result{ name: “scotch.io”}*/

结论表明,Object.assign() 能用于复本第一类的方式,而采用 JSON.parse(JSON.stringify(obj)) 则不行。

复本循环式提及第一类

循环式提及第一类是具备提及自身特性的第一类。让他们采用已学的复本第一类的方式来复本两个循环式引用第一类的复本,看一看它是否有效。

采用 JSON.parse(JSON.stringify(object))

让他们尝试采用 JSON.parse(JSON.stringify(object)):

// 循环式提及第一类let obj = { a: a, b: { c: c, d: d,},}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj = JSON.parse(JSON.stringify(obj));console.log(newObj);
JavaScript 中的对象拷贝

很明显,JSON.parse(JSON.stringify(object)) 不能用于复本循环式提及第一类。

采用 Object.assign()

让他们尝试采用 Object.assign():

// 循环式提及第一类let obj = { a: a, b: { c: c, d: d,},}obj.c = obj.b;obj.e = obj.a;obj.b.c = obj.c;obj.b.d = obj.b;obj.b.e = obj.b.c;let newObj2 = Object.assign({}, obj);console.log(newObj2);

结论是:

JavaScript 中的对象拷贝

Object.assign() 适用于浅复本循环式提及第一类,但不适用于深度复本。随意浏览浏览器控制台上的循环式提及第一类树。我相信你会发现很多有趣的工作在那里。

采用展开操作方式符(…)

ES6已经有了用于数组解构表达式的 rest 元素,和实现的数组字面展开的操作方式符。看一看这里的数组的展开操作方式符的实现:

const array = [“a”,”c”,”d”, { four: 4},];const newArray = […array];console.log(newArray);// 结论 // [“a”, “c”, “d”, { four: 4 }]

第一类字面量的展开操作方式符目前是ECMAScript 的第 3 阶段提案。第一类字面量的展开操作方式符能将源第一类中的可枚举的特性复本到目标第一类上。下面的例子展示了在提案被接受后复本两个第一类是多么的容易。

let obj = { one: 1, two: 2,}let newObj = { …z };// { one: 1, two: 2 }

注意:这将只对浅复本有效。

举报/反馈

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务