javascript之Object.assign()的痛點分析
最近也一直會用javascript,然后中間使用的一些組件,如Echarts 會有非常復(fù)雜的配置文件,而大部分配置可能都是一樣的,所以想著寫一份通用配置,然后,其他地方需要使用的時候,用這份配置深拷貝一份配置,然后在上面繼續(xù)改。
就如下:
const defaultOpt = { ? ? key1: xxx, ? ? key2: { ? ? ? ? dd: ee ? ? }, ? ? ..... }; // deepCopy為某個實現(xiàn)深拷貝的方法 const opt1 = deepCopy(defaultOpt); opt1..... const opt2 = deepCopy(defaultOpt); opt2.....
深拷貝和淺拷貝
這里也涉及到一個深拷貝和淺拷貝的概念。javascript中存儲對象都是存地址的,所以淺拷貝是都指向同一塊內(nèi)存區(qū)塊,而深拷貝則是另外開辟了一塊區(qū)域。
下面實例也可以看出這一點:
// 淺拷貝 const a = {t: 1, p: 'gg'}; const b = a; b.t = 3; console.log(a); // {t: 3, p: 'gg'} console.log(b); // {t: 3, p: 'gg'} //深拷貝 const c = {t: 1, p: 'gg'}; const d = deepCopy(c); d.t = 3; console.log(c); // {t: 1, p: 'gg'} console.log(d); // {t: 3, p: 'gg'}
可以明顯看出,淺拷貝在改變其中一個值時,會導(dǎo)致其他也一起改變,而深拷貝不會。
Object.assign()
我需要的是深拷貝的方法,然后發(fā)現(xiàn)原來es6 中有Object.assign() 這個方法,感覺可以拿來用了。
貼一下兩個官方例子:
// Cloning an object var obj = { a: 1 }; var copy = Object.assign({}, obj); console.log(copy); // { a: 1 }
// Merging objects var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); ?// { a: 1, b: 2, c: 3 }, target object itself is changed.
是不是很完美,又可以clone又可以merge。在我這種情況下,我覺得我的代碼量又可以減少了,比如:
const defaultOpt = { ? ? title: 'hello',? ? ? name: 'oo',? ? ? type: 'line' }; // 原來可能需要這樣 const opt1 = deepCopy(a); opt1.title = 'opt1'; opt1.type = 'bar'; opt1.extra = 'extra'; // 額外增加配置 // 現(xiàn)在只要這樣 const opt2 = Object.assign({}, a, { ? ? title: 'opt2',? ? ? type: 'bar',? ? ? extra: 'extra' });
不過,很快,問題出現(xiàn)了,那就是
merge和我想象的不一樣
且看例子:
const defaultOpt = { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'It\'s my world.' ? ? } }; const opt = Object.assign({}, defaultOpt, { ? ? title: { ? ? ? ? subtext: 'Yes, your world.' ? ? } }); console.log(opt); // 預(yù)期結(jié)果 { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'Yes, your world.' ? ? } } // 實際結(jié)果 { ? ? title: { ? ? ? ? subtext: 'Yes, your world.' ? ? } }
原本想的是它只會覆蓋subtext ,然而其實它直接覆蓋了整個title ,這個讓我比較郁悶,相當(dāng)于它只merge根屬性,下面的就不做處理了。
代碼只能重構(gòu)成相對麻煩一點的:
const defaultOpt = { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'It\'s my world.' ? ? } }; const opt = Object.assign({}, defaultOpt); opt.title.subtext = 'Yes, your world.'; console.log(opt); // 結(jié)果正常 { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'Yes, your world.' ? ? } }
這樣用雖然麻煩一點,但是也還好,可以用了。不過。。。很快,又出現(xiàn)問題了,如下:
const defaultOpt = { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'It\'s my world.' ? ? }? }; const opt1 = Object.assign({}, defaultOpt); const opt2 = Object.assign({}, defaultOpt); opt2.title.subtext = 'Yes, your world.'; console.log('opt1:'); console.log(opt1); console.log('opt2:'); console.log(opt2); // 結(jié)果 opt1: { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'Yes, your world.' ? ? } } opt2: { ? ? title: { ? ? ? ? text: 'hello world', ? ? ? ? subtext: 'Yes, your world.' ? ? } }
上面結(jié)果發(fā)現(xiàn)兩個配置變得一模一樣,而其實我們并沒有去更改opt1 的subtext ,只是改了opt2 的。
這說明一點:在title 這一層只是簡單的淺拷貝 ,而沒有繼續(xù)深入的深拷貝。
這里不經(jīng)讓我懷疑這個接口到底是怎么實現(xiàn)的,它到底是不是和我所想的一樣。
翻了一下官方文檔,發(fā)現(xiàn)它寫得一個Polyfill ,代碼我加了點注釋如下:
if (!Object.assign) { ? ? // 定義assign方法 ? Object.defineProperty(Object, 'assign', { ? ? enumerable: false, ? ? configurable: true, ? ? writable: true, ? ? value: function(target) { // assign方法的第一個參數(shù) ? ? ? 'use strict'; ? ? ? // 第一個參數(shù)為空,則拋錯 ? ? ? if (target === undefined || target === null) { ? ? ? ? throw new TypeError('Cannot convert first argument to object'); ? ? ? } ? ? ? var to = Object(target); ? ? ? // 遍歷剩余所有參數(shù) ? ? ? for (var i = 1; i < arguments.length; i++) { ? ? ? ? var nextSource = arguments[i]; ? ? ? ? // 參數(shù)為空,則跳過,繼續(xù)下一個 ? ? ? ? if (nextSource === undefined || nextSource === null) { ? ? ? ? ? continue; ? ? ? ? } ? ? ? ? nextSource = Object(nextSource); ? ? ? ? // 獲取改參數(shù)的所有key值,并遍歷 ? ? ? ? var keysArray = Object.keys(nextSource); ? ? ? ? for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) { ? ? ? ? ? var nextKey = keysArray[nextIndex]; ? ? ? ? ? var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey); ? ? ? ? ? // 如果不為空且可枚舉,則直接淺拷貝賦值 ? ? ? ? ? if (desc !== undefined && desc.enumerable) { ? ? ? ? ? ? to[nextKey] = nextSource[nextKey]; ? ? ? ? ? } ? ? ? ? } ? ? ? } ? ? ? return to; ? ? } ? }); }
上面的代碼可以直接說明它只對頂層屬性做了賦值,完全沒有繼續(xù)做遞歸之類的把所有下一層的屬性做深拷貝。
小結(jié)一下
Object.assign() 只是一級屬性復(fù)制,比淺拷貝多深拷貝了一層而已。用的時候,還是要注意這個問題的。
附:發(fā)現(xiàn)一個可以簡單實現(xiàn)深拷貝的方法,當(dāng)然,有一定限制,如下:
const obj1 = JSON.parse(JSON.stringify(obj));
思路就是將一個對象轉(zhuǎn)成json字符串,然后又將字符串轉(zhuǎn)回對象。
細(xì)說一下Object.assign()
Object.assign()
- Object.assign() 第一個參數(shù)是目標(biāo)對象,后面的都是源對象。 Object.assign (target, source1,source2, source3, …);
- 如果源對像與目標(biāo)對象有相同的屬性名,或源對象中有相同的屬性名,后面的會覆蓋前邊的值 。
- 如果參數(shù)傳入的不是Object,會轉(zhuǎn)成Object
- null和undefined 不能作為參數(shù)傳入,因為null和undefined 不能轉(zhuǎn)成Object
- 如果發(fā)生的值是一個對象,Object.assign的處理方法是直接替換,而不是添加。 如下面的 a 和 b
- 可以為類添加方法
const obj1 = {name:'小明', age:'18',education:'undergraduate'} const obj2 = {height:'180cm',hobby:'painting'} let obj = Object.assign({},obj1, obj2) console.log('合并后的類:'); console.log(JSON.stringify(obj)); Object.assign(obj, obj, {height:'170cm'}); console.log('修改過height后的類:'); console.log(JSON.stringify(obj)); Object.assign(obj, {arr:{index:1, name:'類'}}, {name:'加了一個類進(jìn)去'}) console.log(JSON.stringify(obj)); console.log("加一個類進(jìn)去后:"+obj.arr.index); // a. 這種修改方式,只會修改index 的值 Object.assign(obj, Object.assign(obj.arr, {index:2})) console.log(JSON.stringify(obj)); console.log("修改類index后:"+obj.arr.index); // b. 這種修改方式,arr只剩下index屬性 // Object.assign(obj, {arr:{index:2}}, {name:'修改類的index為:2'}) // console.log(JSON.stringify(obj)); // console.log("修改類index后:"+obj.arr.index); // Object.assign()做的是淺拷貝, 如果一個屬性是新合并進(jìn)來的對象,改變源對象的值,會影響合并后的值 。 let newObj = {type:{aa:'蔬菜'}}; Object.assign(obj, newObj); console.log("合并一個含屬性type的類后:"+JSON.stringify(obj)); // c. 這種不會影響obj中的type.aa // Object.assign(newObj, {type:{aa:'水果'}}); // d. 這種會影響obj中的type.aa newObj.type.aa = '水果'; console.log("修改newObj中的type.aa后:"+JSON.stringify(newObj)); console.log("修改newObj中的type.aa后:"+JSON.stringify(obj)); // e. 用Object.assign合并一個數(shù)組的時候,會把數(shù)組當(dāng)成一個屬性名為index的類 const arr1 = [1, 2, 3, 4, 5] ; // 在Object的眼里是這樣的: arr1={0:1, 1:2, 2:3,3:4, 4:5} const arr2 = [8, 9, 10]; // 在Object的眼里是這樣的: arr2={0:8, 1:9, 2:10} console.log("合并后的數(shù)組為:"+Object.assign(arr1, arr2)); // 得到的結(jié)果是:8, 9, 10, 4, 5 // f. Object.assign() 為類添加方法 Object.assign(UserInfo.prototype, { getUserName (){ return this.name; }, getUserGender (){ return this.gender ; } }) let user = new UserInfo("笑笑", '女'); console.log("userinfo中的信息為: "+ user.getUserName() +", "+user.getUserGender()); // 輸出的結(jié)果為:笑笑,女
輸出的結(jié)果:
ObjectAssignDemo.js:13 合并后的類:
ObjectAssignDemo.js:14 {"name":"小明","age":"18","education":"undergraduate","height":"180cm","hobby":"painting"}
ObjectAssignDemo.js:16 修改過height后的類:
ObjectAssignDemo.js:17 {"name":"小明","age":"18","education":"undergraduate","height":"170cm","hobby":"painting"}
ObjectAssignDemo.js:19 {"name":"加了一個類進(jìn)去","age":"18","education":"undergraduate","height":"170cm","hobby":"painting","arr":{"index":1,"name":"類"}}
ObjectAssignDemo.js:20 加一個類進(jìn)去后:1
ObjectAssignDemo.js:24 {"name":"類","age":"18","education":"undergraduate","height":"170cm","hobby":"painting","arr":{"index":2,"name":"類"},"index":2}
ObjectAssignDemo.js:25 修改類index后:2
ObjectAssignDemo.js:35 合并一個含屬性type的類后:{"name":"類","age":"18","education":"undergraduate","height":"170cm","hobby":"painting","arr":{"index":2,"name":"類"},"index":2,"type":{"aa":"蔬菜"}}
ObjectAssignDemo.js:40 修改newObj中的type.aa后:{"type":{"aa":"水果"}}
ObjectAssignDemo.js:41 修改newObj中的type.aa后:{"name":"類","age":"18","education":"undergraduate","height":"170cm","hobby":"painting","arr":{"index":2,"name":"類"},"index":2,"type":{"aa":"水果"}}
ObjectAssignDemo.js:46 合并后的數(shù)組為:8,9,10,4,5
ObjectAssignDemo.js:58 userinfo中的信息為: 笑笑, 女
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
javascript 操作Word和Excel的實現(xiàn)代碼
javascript 操作Word和Excel的實現(xiàn)代碼, 需要的朋友可以參考下。2009-10-10基于JS實現(xiàn)飛機(jī)大戰(zhàn)游戲的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用JS實現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06JS求1到任意數(shù)之間的所有質(zhì)數(shù)的方法詳解
這篇文章主要介紹了JS求1到任意數(shù)之間的所有質(zhì)數(shù),本文通過實例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價值 ,需要的朋友可以參考下2019-05-05clipboard.js無需Flash無需依賴任何JS庫實現(xiàn)文本復(fù)制與剪切
這篇文章主要實現(xiàn)了無需Flash無需依賴任何JS庫實現(xiàn)文本復(fù)制與剪切,是一款極現(xiàn)代的,不需要flash,不依賴任何其他js庫的非常小的插件,叫clipboard.js,感興趣的小伙伴們可以參考一下2015-10-10javascript日期對象格式化為字符串的實現(xiàn)方法
本篇文章主要是對javascript日期對象格式化為字符串的實現(xiàn)方法進(jìn)行了詳細(xì)的介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-01-01javascript?變量聲明?var,let,const?的區(qū)別
這篇文章主要介紹了javascript?變量聲明?var,let,const?的區(qū)別,變量聲明,每種編程語言必不可少的語法,在javascript中,變量的聲明相對其他語言來說,算是比較簡單的。更多相關(guān)的具體內(nèi)容需要的小伙伴可以參考一下2022-06-06javascript判斷元素存在和判斷元素存在于實時的dom中的方法
本文主要介紹了javascript判斷元素存在和判斷元素存在于實時的dom中的方法。具有一定的參考價值,下面跟著小編一起來看下吧2017-01-01javascript 面向?qū)ο骹unction詳解及實例代碼
這篇文章主要介紹了javascript 面向?qū)ο骹unction詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02