南昌IT培訓
美國上市IT培訓機構

13732203138

熱門課程

Javascript中對象拷貝,你了解嗎?

  • 時間:2019-06-26 16:33
  • 發布:南昌達內IT培訓
  • 來源:精品干貨

Javascript中對象拷貝,你了解嗎?本篇將會說說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});
.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);
} 

以上就是本文為大家分享的內容,希望對大家有所幫助。

預約申請免費試聽課

怕錢不夠?就業掙錢后再付學費!    怕學不會?從入學起,達內定制課程!     擔心就業?達內多家實踐企業供你挑選 !

上一篇:20款實用的CSS3工具,干貨
下一篇:前端新手需要了解的JavaScript開發技巧

前端新手需要了解的JavaScript開發技巧

Javascript中對象拷貝,你了解嗎?

20款實用的CSS3工具,干貨

入行物聯網的避坑指南

選擇城市和中心
江西省

貴州省

廣西省

海南省

茄子app