Javascript的对象拷贝

时间: 2018-12-03阅读: 1111标签: 对象

本篇文章将会说说JavaScript中对象拷贝的多种方式,以及探究一下深拷贝和浅拷贝。

在开始之前,我先提一下一些基础知识:JavaScript 的对象只是指向内存中某个位置的指针。这些指针是可变的,也就是说,它们可以重新被赋值。因此,单单复制这个指针的结果是,有两个指针指向内存中的同一块地址。

var foo = {
    a : "abc"
}
console.log(foo.a);
// abc

var bar = foo;
console.log(bar.a);
// abc

foo.a = "yo foo";
console.log(foo.a);
// yo foo
console.log(bar.a);
// yo foo

bar.a = "whatup bar?";
console.log(foo.a);
// whatup bar?
console.log(bar.a);
// whatup bar?    


如上面的例子可见,foo和bar对象都能根据对方的变化而变化。因此,在拷贝Javascript中的对象时,我们要根据实际使用情况,而做一些考虑。


浅拷贝

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

var obj = { foo: "foo", bar: "bar" };
var copy = { ...obj };
// Object { foo: "foo", bar: "bar" }


var obj = { foo: "foo", bar: "bar" };
var copy = Object.assign({}, obj);
// Object { foo: "foo", bar: "bar" }


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

var obj1 = { foo: "foo" };
var obj2 = { bar: "bar" };
var copySpread = { ...obj1, ...obj2 };
// Object { foo: "foo", bar: "bar" }
var copyAssign = Object.assign({}, obj1, obj2);
// Object { foo: "foo", bar: "bar" }


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

var foo = { a: 0 , b: { c: 0 } };
var copy = { ...foo };
copy.a = 1;
copy.b.c = 2;
console.dir(foo);
// { a: 0, b: { c: 2 } }
console.dir(copy);
// { a: 1, b: { c: 2 } }


深拷贝(有限制)

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

var obj = { a: 0, b: { c: 0 } };
var copy = jsON.parse(jsON.stringify(obj));


不幸的是,这个方法只在对象包含可序列化值,并且没有循环引用的时候有用。其中一个不可序列化的类型的就是日期对象 - 尽管它显示出来是字符串化的ISO格式,JSON.parse只会把它解析成为一个字符串,而不是日期类型

深拷贝 (少一点限制)

对于一些更复杂的情景,我们可以使用html5的一个新算法,叫做结构化克隆。不过,截至本篇文章发表为止,有些内置类型还是无法支持,但相比JSON.parse,它支持的类型要多的多:日期类型,正则表达式,Map,集合,二进制大对象,文件集合,图像数据,sparse函数和数组。 它还维护克隆对象的引用,使得他可以支持循环引用结构的拷贝,而这些在上面的序列化例子中是不支持的。

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

通过 MessageChannels: 这样做的原理是,借用通讯的一个特性中使用到的序列化算法。由于那个特性是基于事件的,所以这里的克隆也是一个异步操作。

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
this.nextKey_ = 0;
const channel = new MessageChannel();
this.inPort_ = channel.port1;
this.outPort_ = channel.port2;
this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
resolve(value);
this.pendingClones_.delete(key);
};
this.outPort_.start();
}

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
const main = async () => {
  const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// different objects:
  console.assert(original !== clone);
console.assert(original.date !== clone.date);
// cyclical:
  console.assert(original.self === original);
console.assert(clone.self === clone);
// equivalent values:
  console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
main();


通过history对象API:history.pushState() 和history.replaceState() 都会给它们的第一个参数做一个结构化克隆!要注意的是,这个方法是同步的,操作浏览器历史这个操作速度不是非常快,如果频繁调用这个方法,会导致浏览器卡死。

const structuredClone = obj => {
  const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};


通过 notification API: 当创建一个notification实例的时候,构造器为它相关的数据做了结构化克隆。需要注意的是,它会尝试向用户展示浏览器通知,但是,除非它接收到用户允许展示通知的请求,否则,它什么也不会做。一旦用户点击同意的话,notification 会立刻被关闭。

const structuredClone = obj => {
  const n = new Notification("", {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};



使用Node.js进行深拷贝

Node.js的8.0.0版本提供了一个 序列化 api 可以跟结构化克隆媲美. 不过这个API在本文发布的时候,还只是被认为是试验性的:

const v8 = require('v8');
const buf = v8.serialize({a: 'foo', b: new Date()});
const cloned = v8.deserialize(buf);
cloned.b.getMonth();


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


结论

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

function deepClone(obj) {
  var copy;
// Handle the 3 simple types, and null or undefined
  if (null == obj || "object" != typeof obj) return obj;
// Handle Date
  if (obj instanceof Date) {
    copy = new Date();
copy.setTime(obj.getTime());
return copy;
}

  // Handle Array
  if (obj instanceof Array) {
    copy = [];
for (var i = 0, len = obj.length;
i < len;
i++) {
        copy[i] = deepClone(obj[i]);
}
    return copy;
}

  // Handle Function
  if (obj instanceof Function) {
    copy = function() {
      return obj.apply(this, arguments);
}
    return copy;
}

  // Handle Object
  if (obj instanceof Object) {
      copy = {};
for (var attr in obj) {
          if (obj.hasOwnProperty(attr)) copy[attr] = deepClone(obj[attr]);
}
      return copy;
}

  throw new Error("Unable to copy obj as type isn't supported " + obj.constructor.name);
}


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


原文链接: smalldata.tech 
翻译来源:https://www.zcfy.cc/article/copying-objects-in-javascript

 

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/1476

关闭

js获取object对象的长度

我们都知道必须是具体数据类型才有长度,所以size和length都无法测量object对象的长度,那么如何计算对象的长度,即获取对象属性的个数呢?

javascript中内置对象和浏览器对象的区别是什么?

JavaScript对象是包含相关属性和方法的集合体。在 JavaScript 中,对象是数据(变量),拥有属性和方法。在javascript中对象通常包括两种类型:内置对象和浏览器对象,此外,用户还可以自定义对象。

如何在JavaScript中访问暂未存在的嵌套对象

JavaScript 是个很神奇的东西。但是 JavaScript中的一些东西确实很奇怪,让人摸不着头脑。其中之一就是当你试图访问嵌套对象时,会遇到这个错误

js组合模式和寄生组合模式的区别研究

结合了构造函数继承时可以为每个属性重新初始化,构造一个副本的优点,以及原型链继承时一次定义处处共享的优点。寄生组合式继承是在原型式继承的基础上做的。主要目的是可以基于已有的对象创建新的对象,而不必因此创建自定义类型。

JS数组或对象中的内容间隔显示

会在5秒中之后几个数字几乎一起显示,并不是我们希望的间隔5秒显示一个数字。间隔显示,不要使用for 循环,原因是for循环是同步,setTimeout是异步,同步执行完再执行异步。

利用 WeakMap 对 Vue 新建数组中的对象赋予 :key

在 Vue 中,对组件进行循环都需要加入key以便“就地复用”,可是在某些情况下,我们需要新建多个对象,而这些对象不是从后端获取到的,而是前端生成的,没有唯一值,且 Vue 目前版本只允许字符串,数字作为组件的 key

js中Object.create vs new Function() 的区别

之前学习JavaScript 权威指南,说以后在JavaScript建立对象请使用Object.create() 尽量少使用 new Function() .这两个新建对象的方法到底有什么不同呢?new 方式实现方式,实际上是先新建了一个Object对象,再用这个对象继承至Base,最后再绑定this.

原来JS是这样的_对象属性

同样的字符串赋值到对象,一会儿是字符串类型一会儿是对象,而明明不是对象类型的变量还是可以使用对象属性,为什么会这样呢?JavaScript 中一共有六种主要(语言)类型

js对象的封装、继承和多态

面向对象三大特性就是封装继承和多态,简单理解,对于猫这种动物,它本身就是一个封装好的类,你只需要供它吃喝(输入),它就能表现猫的行为(输出),同时它继承了动物所具有的习性(吃东西等~)

比较 JavaScript 对象的四种方式

比较 JavaScript 中的原始值非常简单。只需使用任何一种可用的相等运算符即可,例如严格相等运算符:但是对象却有结构化数据,所以比较起来比较困难。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!