美文网首页前端学习
Javascript的对象拷贝

Javascript的对象拷贝

作者: 1194b60087a9 | 来源:发表于2019-04-18 15:31 被阅读0次

在开始之前,我先普及一些基础知识。Javascript 的对象只是指向内存中某个位置的指针。这些指针是可变的,也就是说,它们可以重新被赋值。所以仅仅复制这个指针,其结果是有两个指针指向内存中的同一个地址。

1varfoo = {

2a:"abc"

3}

4console.log(foo.a);

5// abc

6

7varbar = foo;

8console.log(bar.a);

9// abc

10

11foo.a ="yo foo";

12console.log(foo.a);

13// yo foo

14console.log(bar.a);

15// yo foo

16

17bar.a ="whatup bar?";

18console.log(foo.a);

19// whatup bar?

20console.log(bar.a);

21// whatup bar?    

通过上面的例子可以看到,对象 foo 和 bar 都能随着对方的变化而变化。所以在拷贝 Javascript 中的对象时,要根据实际情况做一些考虑。

浅拷贝

如果要操作的对象拥有的属性都是值类型,那么可以使用扩展语法或 Object.assign(...)

1varobj = {foo:"foo",bar:"bar"};

2varcopy = { ...obj };

3// Object { foo: "foo", bar: "bar" }

4varobj = {foo:"foo",bar:"bar"};

5varcopy =Object.assign({}, obj);

6// Object { foo: "foo", bar: "bar" }

可以看到上面两种方法都可以把多个不同来源对象中的属性复制到一个目标对象中。

1varobj1 = {foo:"foo"};

2varobj2 = {bar:"bar"};

3varcopySpread = { ...obj1, ...obj2 };

4// Object { foo: "foo", bar: "bar" }

5varcopyAssign =Object.assign({}, obj1, obj2);

6// Object { foo: "foo", bar: "bar" }

上面这种方法是存在问题的,如果对象的属性也是对象,那么实际被拷贝的只是那些指针,这跟执行 var bar = foo; 的效果是一样的,和第一段代码中的做法一样。

1varfoo = {a:0,b: {c:0} };

2varcopy = { ...foo };

3copy.a =1;

4copy.b.c =2;

5console.dir(foo);

6// { a: 0, b: { c: 2 } }

7console.dir(copy);

8// { a: 1, b: { c: 2 } }

深拷贝(有限制)

想要对一个对象进行深拷贝,一个可行的方法是先把对象序列化为字符串,然后再对它进行反序列化。

1varobj = {a:0,b: {c:0} };

2varcopy =JSON.parse(JSON.stringify(obj));

不幸的是,这个方法只在对象中包含可序列化值,同时没有循环引用的情况下适用。常见的不能被序列化的就是日期对象 —— 尽管它显示的是字符串化的 ISO 日期格式,但是 JSON.parse 只会把它解析成为一个字符串,而不是日期类型。

深拷贝 (限制较少)

对于一些更复杂的场景,我们可以用 HTML5 提供的一个名为结构化克隆【https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification】的新算法。不过,截至本文发布为止,有些内置类型仍然无法支持,但与 JSON.parse 相比较而言,它支持的类型要多的多:Date、RegExp、 Map、 Set、 Blob、 FileList、 ImageData、 sparse 和 typed Array。它还维护了克隆对象的引用,这使它可以支持循环引用结构的拷贝,而这些在前面所说的序列化中是不支持的。

目前还没有直接调用结构化克隆的方法,但是有些新的浏览器特性的底层用了这个算法。所以深拷贝对象可能需要依赖一系列的环境才能实现。

Via MessageChannels: 其原理是借用了通信中用到的序列化算法。由于它是基于事件的,所以这里的克隆也是一个异步操作。

1classStructuredCloner{

2constructor() {

3this.pendingClones_ =newMap();

4this.nextKey_ =0;

5

6constchannel =newMessageChannel();

7this.inPort_ = channel.port1;

8this.outPort_ = channel.port2;

9

10this.outPort_.onmessage =({data: {key, value}}) =>{

11constresolve =this.pendingClones_.get(key);

12resolve(value);

13this.pendingClones_.delete(key);

14};

15this.outPort_.start();

16}

17

18cloneAsync(value) {

19returnnewPromise(resolve=>{

20constkey =this.nextKey_++;

21this.pendingClones_.set(key, resolve);

22this.inPort_.postMessage({key, value});

23});

24}

25}

26

27conststructuredCloneAsync =window.structuredCloneAsync =

28StructuredCloner.prototype.cloneAsync.bind(newStructuredCloner);

29

30constmain =async() => {

31constoriginal = {date:newDate(),number:Math.random() };

32original.self = original;

33

34constclone =awaitstructuredCloneAsync(original);

35

36// different objects:

37console.assert(original !== clone);

38console.assert(original.date !== clone.date);

39

40// cyclical:

41console.assert(original.self === original);

42console.assert(clone.self === clone);

43

44// equivalent values:

45console.assert(original.number === clone.number);

46console.assert(Number(original.date) ===Number(clone.date));

47

48console.log("Assertions complete.");

49};

50

51main();

Via the history API:history.pushState() 和 history.replaceState() 都会给它们的第一个参数做一个结构化克隆!需要注意的是,此方法是同步的,因为对浏览器历史记录进行操作的速度不是很快,假如频繁调用这个方法,将会导致浏览器卡死。

1conststructuredClone =obj=>{

2constoldState = history.state;

3history.replaceState(obj,null);

4constclonedObj = history.state;

5history.replaceState(oldState,null);

6returnclonedObj;

7};

Via notification API:【https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification】当创建一个 notification 实例的时候,构造器为它相关的数据做了结构化克隆。需要注意的是,它会尝试向用户展示浏览器通知,但是除非它收到了用户允许展示通知的请求,否则它什么都不会做。一旦用户点击同意的话,notification 会立刻被关闭。

1conststructuredClone =obj=>{

2constn =newNotification("", {data: obj,silent:true});

3n.onshow = n.close.bind(n);

4returnn.data;

5};

用 Node.js 进行深拷贝

Node.js 的 8.0.0 版本提供了一个 序列化 api【https://nodejs.org/api/v8.html#v8_serialization_api】可以和结构化克隆相媲美. 不过这个 API 在本文发布的时候,还只是被标记为试验性的:

1constv8 =require('v8');

2constbuf = v8.serialize({a:'foo',b:newDate()});

3constcloned = v8.deserialize(buf);

4cloned.b.getMonth();

在 8.0.0 版本以下比较稳定的方法,可以考虑用 lodash 的 cloneDeep函数,它的思想多少也基于结构化克隆算法。

结论

Javascript 中最好的对象拷贝的算法,很大程度上取决于其使用环境,以及你需要拷贝的对象类型。虽然 lodash 是最安全的泛型深拷贝函数,但是如果你自己封装的话,也许能够获得效率更高的实现方法,以下就是一个简单的深拷贝,对 Date 日期对象也同样适用:

1functiondeepClone(obj){

2varcopy;

3

4// Handle the 3 simple types, and null or undefined

5if(null== obj ||"object"!=typeofobj)returnobj;

6

7// Handle Date

8if(objinstanceofDate) {

9copy =newDate();

10copy.setTime(obj.getTime());

11returncopy;

12}

13

14// Handle Array

15if(objinstanceofArray) {

16copy = [];

17for(vari =0, len = obj.length; i < len; i++) {

18copy[i] = deepClone(obj[i]);

19}

20returncopy;

21}

22

23// Handle Function

24if(objinstanceofFunction) {

25copy =function(){

26returnobj.apply(this,arguments);

27}

28returncopy;

29}

30

31// Handle Object

32if(objinstanceofObject) {

33copy = {};

34for(varattrinobj) {

35if(obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);

36}

37returncopy;

38}

39

40thrownewError("Unable to copy obj as type isn't supported "+ obj.constructor.name);

41}

我很期待可以随便使用结构化克隆的那一天的到来,让对象拷贝不再令人头疼

十五年编程经验,今年1月整理了一批2019年最新WEB前端教学视频,不论是零基础想要学习前端还是学完在工作想要提升自己,这些资料都会给你带来帮助,从HTML到各种框架,帮助所有想要学好前端的同学,学习规划、学习路线、学习资料、问题解答。只要加入WEB前端学习交流Q群:296212562,即可免费获取。

相关文章

  • JavaScript深拷贝、浅拷贝

    JavaScript深拷贝、浅拷贝 浅拷贝:浅拷贝只是复制了内存地址,如果原地址中的对象改变了,浅拷贝出来的对象也...

  • JavaScript对象02

    JavaScript对象02 对象浅拷贝 浅拷贝:不会考虑对象的层次结构,不会考虑任何引用类型, 换句话说,浅拷贝...

  • JavaScript对象的拷贝

    本文主要介绍js对象的拷贝,包括浅拷贝和深拷贝,侧重实际方式,简单介绍概念。 一、js数据类型 js数据有不同的划...

  • Javascript的对象拷贝

    在开始之前,我先普及一些基础知识。Javascript 的对象只是指向内存中某个位置的指针。这些指针是可变的,也就...

  • Javascript实现对象拷贝

    在日常开发中,当想用某个变量去获取另外一个对象变量数据时,最直接的方法是: let obj2 = obj1 由于对...

  • Javascript完美拷贝对象

  • JavaScript 对象深浅拷贝

    浅拷贝: 深拷贝:

  • JavaScript对象深浅拷贝

    首先对象属性和方法的简写方法。 Object.assign()Object.assign方法用于对象的合并。当对象...

  • JavaScript复制数组方式

    浅拷贝 这种数组拷贝方式就是浅拷贝,因为JavaScript存储对象都是存地址的,所以浅复制会导致 arr1 和 ...

  • Javascript对象的深浅拷贝

    开门见山,有人叫对象的复制为深复制浅复制,也有人叫深拷贝浅拷贝。其实都是copy。 深拷贝(递归复制,复制所有层级...

网友评论

    本文标题:Javascript的对象拷贝

    本文链接:https://www.haomeiwen.com/subject/idltgqtx.html