Javascript 中的對象拷貝

原文:COPYING OBJECTS IN JAVASCRIPT
在這篇文章中咱們將會討論 Javascript 中對象拷貝的多種方式。包括深拷貝和淺拷貝。
開始以前,先談一些基礎知識: Javascript 中的對象只是對內存中某個地址的引用。這些引用是可變的,即它們能夠從新分配。所以,簡單製做一個引用的副本只會致使2個引用指向內存中相同的地址: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的對象拷貝。java

淺拷貝

若是你的對象只有值類型的屬性,則能夠用拓展語法或者算法

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:它的想法是利用通訊功能使用的序列化算法。因爲這個功能是基於事件的,所以生成的拷貝也是異步操做。app

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() 都建立了它們第一個參數的結構化拷貝!注意當這個方法是異步的時候,操縱瀏覽器歷史記錄再也不是一個快速操做,反覆調用此方法可能會致使瀏覽器無響應。dom

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 方法,該方法也是基於結構化拷貝方法。async

結論

總而言之,在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);
}

就我的而言,我期待可以在任何地方使用結構化拷貝,最後把這個問題放一邊,快樂的拷貝:)注:如有問題,請聯繫我修改=。=

相關文章
相關標籤/搜索