资讯专栏INFORMATION COLUMN

Javascript 中的对象拷贝

lieeps / 1151人阅读

摘要:原文在这篇文章中我们将会讨论中对象拷贝的多种方式。因此,根据你的用法需要特别注意的对象拷贝。在拥有权限的情况下,通知立即关闭。在中深拷贝不幸的是,结构化拷贝算法目前仅适用于基于浏览器的应用。

原文:COPYING OBJECTS IN JAVASCRIPT
在这篇文章中我们将会讨论 Javascript 中对象拷贝的多种方式。包括深拷贝和浅拷贝。
开始之前,先谈一些基础知识: Javascript 中的对象只是对内存中某个地址的引用。这些引用是可变的,即它们可以重新分配。因此,简单制作一个引用的副本只会导致2个引用指向内存中相同的地址:

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 } }
深拷贝(警告)

对于更加复杂的情况,可以使用较新的被称为“结构化拷贝”的HTML5拷贝算法。不幸的是,它仍局限于某些内置类型,但它支持的类型比 JSON.parse 多得多:Date, RegExp, Map, Set, Blob, FileList, ImageData, 稀疏和类型化的Array。它还保留了拷贝数据中的引用,允许它支持不能与上述序列化方法一起使用的循环和递归结构。
目前没有直接的方法来调用结构化拷贝算法,但是有一些较新的浏览器功能在引擎帮助下使用了这个算法。因此有几个可以用来深拷贝的解决方法。
通过 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:在创建新通知时,构造函数会创建它关联数据的结构化拷贝。注意它还会尝试向用户显示浏览器通知,但是除非应用程序已经请求到了显示通知的权限,否则这个操作将会失败。在拥有权限的情况下,通知立即关闭。

const structuredClone = obj => {
  const n = new Notification("", {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};
在 NODE.JS 中深拷贝

不幸的是,结构化拷贝算法目前仅适用于基于浏览器的应用。对于服务端,可以使用 lodashcloneDeep 方法,该方法也是基于结构化拷贝方法。

结论

总而言之,在Javascript中拷贝对象的最佳算法很大程度上取决于你要复制的对象的内容和类型。虽然 lodash 是最安全的通用深拷贝方法,但如果你自己动手,可能会获得更高效的实现,以下是一个适用于日期的简单深拷贝的例子:

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);
}

就个人而言,我期待能够在任何地方使用结构化拷贝,最后把这个问题放一边,快乐的拷贝:)
注:若有问题,请联系我修改=。=

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108659.html

相关文章

  • JavaScript中的拷贝与深拷贝

    摘要:所以,深拷贝是对对象以及对象的所有子对象进行拷贝实现方式就是递归调用浅拷贝对于深拷贝的对象,改变源对象不会对得到的对象有影响。 上一篇 JavaScript中的继承 前言 文章开始之前,让我们先思考一下这几个问题: 为什么会有浅拷贝与深拷贝 什么是浅拷贝与深拷贝 如何实现浅拷贝与深拷贝 好了,问题出来了,那么下面就让我们带着这几个问题去探究一下吧! 如果文章中有出现纰漏、错误之处...

    AZmake 评论0 收藏0
  • JavaScript中的拷贝与深拷贝

    摘要:所以,深拷贝是对对象以及对象的所有子对象进行拷贝实现方式就是递归调用浅拷贝对于深拷贝的对象,改变源对象不会对得到的对象有影响。 为什么会有浅拷贝与深拷贝什么是浅拷贝与深拷贝如何实现浅拷贝与深拷贝好了,问题出来了,那么下面就让我们带着这几个问题去探究一下吧! 如果文章中有出现纰漏、错误之处,还请看到的小伙伴多多指教,先行谢过 以下↓ 数据类型在开始了解 浅拷贝 与 深拷贝 之前,让我们先...

    546669204 评论0 收藏0
  • JavaScript中的拷贝和深拷贝

    摘要:在中可以通过添加一个参数来实现递归,调用就可以实现一个深拷贝。利用序列化实现一个深拷贝 在JavaScript中,对于Object和Array这类引用类型值,当从一个变量向另一个变量复制引用类型值时,这个值的副本其实是一个指针,两个变量指向同一个堆对象,改变其中一个变量,另一个也会受到影响。 这种拷贝分为两种情况:拷贝引用和拷贝实例,也就是我们说的浅拷贝和深拷贝 浅拷贝(shallow...

    ernest.wang 评论0 收藏0
  • JavaScript的深浅拷贝

    摘要:实际上,是禁止这样做的。传值和传址基本数据类型赋值基本数据类型的赋值是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。结果见输出,可以看出来,无论是修改赋值得到的对象和浅拷贝得到的都会改变原始数据。 存储问题:深拷贝和浅拷贝的主要区别:在内存中的存储类型(堆和栈)不同堆:动态分配的内存,大小不定也不会自动释放栈:自动分配的内存,由系统自动释放数据类型: 基本数据类型: jav...

    zhjx922 评论0 收藏0
  • JavaScript的赋值、深拷贝和浅拷贝

    摘要:内存空间分为两种,栈内存与堆内存栈是系统自动分配的内存空间,由系统自动释放,堆则是动态分配的内存,大小不定不会自动释放。 JavaScript的内存空间 在JavaScript中,每一个数据都需要一个内存空间。内存空间分为两种,栈内存(stack)与堆内存(heap) 栈是系统自动分配的内存空间,由系统自动释放,堆则是动态分配的内存,大小不定不会自动释放。 基本数据类型 JavaScr...

    godlong_X 评论0 收藏0
  • JavaScript基础心法——深浅拷贝

    摘要:原文地址基础心法深浅拷贝欢迎。上面的代码是最简单的利用赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着和改变,和也随着发生了变化。展开运算符结论实现的是对象第一层的深拷贝。 原文地址:JavaScript基础心法——深浅拷贝 欢迎star。 如果有错误的地方欢迎指正。 浅拷贝和深拷贝都是对于JS中的引用类型而言的,浅拷贝就只是复制对象的引用,如果拷贝后的对象发生变化,原对象也会发生...

    keithxiaoy 评论0 收藏0

发表评论

0条评论

lieeps

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<