摘要:傳送門本文會介紹淺拷貝的實(shí)現(xiàn)原理,然后咱們試著實(shí)現(xiàn)一個淺拷貝。返回的對象就是目標(biāo)對象。使用轉(zhuǎn)成對象,并保存為,最后返回這個對象。
一、前言
之前在前面一篇學(xué)習(xí)了賦值,淺拷貝和深拷貝。介紹了這三者的相關(guān)知識和區(qū)別。
傳送門:https://www.mwcxs.top/page/59...
本文會介紹淺拷貝Object.assign()的實(shí)現(xiàn)原理,然后咱們試著實(shí)現(xiàn)一個淺拷貝。
二、淺拷貝Object.assign()什么是淺拷貝?淺拷貝就是創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。
淺拷貝Object.assign()是什么?主要將所有可枚舉屬性的值從一個或者多個數(shù)據(jù)源對象復(fù)制到目標(biāo)對象,同時返回目標(biāo)對象。
語法規(guī)則:
Object.assign(target,...sources)
其中target是目標(biāo)對象,source是源對象,可以是多個,修改返回的是目標(biāo)對象target。
1、如果目標(biāo)對象中的屬性具有相同的屬性鍵,則屬性將被源對象中的屬性覆蓋;
2、源對象的屬相將類似覆蓋早先的屬性。
強(qiáng)調(diào)兩點(diǎn):
1、可枚舉的屬性(自有屬性)
2、string或者symbol類型是可以被直接分配的
2.1栗子1淺拷貝就是拷貝第一層的基本類型值,以及第一層的引用類型地址。
// saucxs // 第一步 let a = { name: "advanced", age: 18 } let b = { name: "saucxs", book: { title: "You Don"t Know JS", price: "45" } } let c = Object.assign(a, b); console.log(c); // { // name: "saucxs", // age: 18, // book: {title: "You Don"t Know JS", price: "45"} // } console.log(a === c); // true // 第二步 b.name = "change"; b.book.price = "55"; console.log(b); // { // name: "change", // book: {title: "You Don"t Know JS", price: "55"} // } // 第三步 console.log(a); // { // name: "saucxs", // age: 18, // book: {title: "You Don"t Know JS", price: "55"} // }
分析:
1、第一步中,使用Object.assign把源對象b的值復(fù)制到目標(biāo)對象a中,這里把返回值定義為對象c,可以看出b會替換掉a中具有相同鍵的值,即如果目標(biāo)對象a中的屬性具有相同的鍵,則屬相將被源對象b中的屬性覆蓋。返回的對象c就是目標(biāo)對象a。
2、第二步中,修改源對象b的基本類型值(name)和引用類型值(book)。
3、第三步中,淺拷貝之后目標(biāo)對象a的基本類型值沒有改變,但是引用類型值發(fā)生了改變,因?yàn)镺bject.assign()拷貝的是屬性值。加入源對象的屬性值是一個指向?qū)ο蟮囊茫豢截惸莻€引用地址。
2.2栗子2string類型和symbol類型的屬性都會被拷貝,而且不會跳過那些值為null或undefined的源對象。
// saucxs // 第一步 let a = { name: "saucxs", age: 18 } let b = { b1: Symbol("saucxs"), b2: null, b3: undefined } let c = Object.assign(a, b); console.log(c); // { // name: "saucxs", // age: 18, // b1: Symbol(saucxs), // b2: null, // b3: undefined // } console.log(a === c); // true三、Object.assign模擬實(shí)現(xiàn)
實(shí)現(xiàn)Object.assign模擬實(shí)現(xiàn)大致思路:
1、判斷原生的Object是否支持assign這個函數(shù),如果不存在的話就會創(chuàng)建一個assign函數(shù),并使用Object.defineProperty將函數(shù)綁定到Object上。
2、判斷參數(shù)是否正確(目標(biāo)參數(shù)不能為空,可以直接設(shè)置{}傳遞進(jìn)去,但是必須有值)。
3、使用Object()轉(zhuǎn)成對象,并保存為to,最后返回這個對象to。
4、使用for in 循環(huán)遍歷出所有的可枚舉的自有屬性,并復(fù)制給新的目標(biāo)對象(使用hasOwnProperty獲取自有屬性,即非原型鏈上的屬性)
參考原生,實(shí)現(xiàn)代碼如下,使用assign2代替assign。此處的模擬不支持symbol屬性,因?yàn)閑s5中沒有symbol。
// saucxs if (typeof Object.assign2 != "function") { // 注意 1 Object.defineProperty(Object, "assign2", { value: function (target) { "use strict"; if (target == null) { // 注意 2 throw new TypeError("Cannot convert undefined or null to object"); } // 注意 3 var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource != null) { // 注意 2 // 注意 4 for (var nextKey in nextSource) { if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true }); }
測試一下:
// saucxs // 測試用例 let a = { name: "advanced", age: 18 } let b = { name: "saucxs", book: { title: "You Don"t Know JS", price: "45" } } let c = Object.assign2(a, b); console.log(c); // { // name: "saucxs", // age: 18, // book: {title: "You Don"t Know JS", price: "45"} // } console.log(a === c); // true3.1 注意1:可枚舉性
原生情況下掛載在Object上的屬性時不可枚舉的,但是直接在Object上掛載屬性a之后就可以枚舉的,所以必須使用Object.defineProperty,并設(shè)置enumerable: false 以及 writable: true,configurable: true。
// saucxs for(var i in Object) { console.log(Object[i]); } // 無輸出 Object.keys( Object ); // []
上面說明,原生的Object上的屬性不可枚舉。
我們可以使用2種方法查看Object.assign是否可枚舉,使用Object.getOwnPropertyDescriptor或者Object.propertyIsEnumberable都可以,其中propertyIsEnumerable(..)會檢查給定的屬性名是否直接存在于對象中(而不是在原型鏈上)并且滿足enumerable:true。具體用法如下:
// saucxs Object.getOwnPropertyDescriptor(Object, "assign"); // { // value: ?, // writable: true, // 可寫 // enumerable: false, // 不可枚舉,注意這里是 false // configurable: true // 可配置 // } // saucxs Object.propertyIsEnumerable("assign"); // false
說明Object.assign是不可枚舉的。
直接在Object上掛載屬性a之后是可以枚舉的。我們來看一下代碼:
// saucxs Object.a = function () { console.log("log a"); } Object.getOwnPropertyDescriptor(Object, "a"); // { // value: ?, // writable: true, // enumerable: true, // 注意這里是 true // configurable: true // } Object.propertyIsEnumerable("a"); // true
所以要實(shí)現(xiàn) Object.assign 必須使用 Object.defineProperty,并設(shè)置 writable: true, enumerable: false, configurable: true,當(dāng)然默認(rèn)情況下不設(shè)置就是 false。
// saucxs Object.defineProperty(Object, "b", { value: function() { console.log("log b"); } }); Object.getOwnPropertyDescriptor(Object, "b"); // { // value: ?, // writable: false, // 注意這里是 false // enumerable: false, // 注意這里是 false // configurable: false // 注意這里是 false // }
模擬實(shí)現(xiàn)涉及到代碼
// saucxs // 判斷原生 Object 中是否存在函數(shù) assign2 if (typeof Object.assign2 != "function") { // 使用屬性描述符定義新屬性 assign2 Object.defineProperty(Object, "assign2", { value: function (target) { ... }, // 默認(rèn)值是 false,即 enumerable: false writable: true, configurable: true }); }3.2 注意2:判斷參數(shù)是否正確
有些文章判斷參數(shù)是否正確是這樣的。
// saucxs if (target === undefined || target === null) { throw new TypeError("Cannot convert undefined or null to object"); }
這樣肯定沒問題,但是這樣寫沒有必要,因?yàn)?undefined 和 null 是相等的(高程 3 P52 ),即 undefined == null 返回 true,只需要按照如下方式判斷就好了。
// saucxs if (target == null) { // TypeError if undefined or null throw new TypeError("Cannot convert undefined or null to object"); }3.3 注意3:原始類型被包裝為對象
// saucxs var v1 = "abc"; var v2 = true; var v3 = 10; var v4 = Symbol("foo"); var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); // 原始類型會被包裝,null 和 undefined 會被忽略。 // 注意,只有字符串的包裝對象才可能有自身可枚舉屬性。 console.log(obj); // { "0": "a", "1": "b", "2": "c" }
上面代碼中的源對象 v2、v3、v4 實(shí)際上被忽略了,原因在于他們自身沒有可枚舉屬性。
// saucxs var v1 = "abc"; var v2 = true; var v3 = 10; var v4 = Symbol("foo"); var v5 = null; // Object.keys(..) 返回一個數(shù)組,包含所有可枚舉屬性 // 只會查找對象直接包含的屬性,不查找[[Prototype]]鏈 Object.keys( v1 ); // [ "0", "1", "2" ] Object.keys( v2 ); // [] Object.keys( v3 ); // [] Object.keys( v4 ); // [] Object.keys( v5 ); // TypeError: Cannot convert undefined or null to object
上面代碼說明:Object.keys(..)返回一個數(shù)組,包含所有可枚舉的屬性,只會查找對象直接包含的屬性,而不會查找[[prototype]]鏈。
// Object.getOwnPropertyNames(..) 返回一個數(shù)組,包含所有屬性,無論它們是否可枚舉 // 只會查找對象直接包含的屬性,不查找[[Prototype]]鏈 Object.getOwnPropertyNames( v1 ); // [ "0", "1", "2", "length" ] Object.getOwnPropertyNames( v2 ); // [] Object.getOwnPropertyNames( v3 ); // [] Object.getOwnPropertyNames( v4 ); // [] Object.getOwnPropertyNames( v5 ); // TypeError: Cannot convert undefined or null to object
上面代碼說明:Object.getOwnPropertyNames(..)返回一個數(shù)組,保護(hù)焊所有屬性,無論他們是否可以枚舉,只會查找對象直接包含的屬性,不查找[[prototype]]鏈。
但是這樣是可以執(zhí)行的:
// saucxs var a = "abc"; var b = { v1: "def", v2: true, v3: 10, v4: Symbol("foo"), v5: null, v6: undefined } var obj = Object.assign(a, b); console.log(obj); // { // [String: "abc"] // v1: "def", // v2: true, // v3: 10, // v4: Symbol(foo), // v5: null, // v6: undefined // }
為什么?因?yàn)閡ndefined,true等不適作為對象,而是作為對象b的屬性值,對象b是可枚舉的。
// saucxs // 接上面的代碼 Object.keys( b ); // [ "v1", "v2", "v3", "v4", "v5", "v6" ]
這里其實(shí)又可以看出一個問題來,那就是目標(biāo)對象是原始類型,會包裝成對象,對應(yīng)上面的代碼就是目標(biāo)對象 a 會被包裝成 [String: "abc"],那模擬實(shí)現(xiàn)時應(yīng)該如何處理呢?很簡單,使用 Object(..) 就可以了。
// saucxs var a = "abc"; console.log( Object(a) ); // {0: "a", 1: "b", 2: "c"}
我們再來看看下面代碼能不能執(zhí)行:
// saucxs var a = "abc"; var b = "def"; Object.assign(a, b); // TypeError: Cannot assign to read only property "0" of object "[object String]"
還是會報錯的,原因在于:Object("abc")時候,其屬性描述符writable為不可寫,即writeable: false。
// saucxs var myObject = Object( "abc" ); Object.getOwnPropertyNames( myObject ); // [ "0", "1", "2", "length" ] Object.getOwnPropertyDescriptor(myObject, "0"); // { // value: "a", // writable: false, // 注意這里 // enumerable: true, // configurable: false // }3.4 注意4:存在性
如何在不訪問屬性值的情況下判斷對象中是否存在某個屬性,看下面代碼:
// saucxs var anotherObject = { a: 1 }; // 創(chuàng)建一個關(guān)聯(lián)到 anotherObject 的對象 var myObject = Object.create( anotherObject ); myObject.b = 2; ("a" in myObject); // true ("b" in myObject); // true myObject.hasOwnProperty( "a" ); // false myObject.hasOwnProperty( "b" ); // true
使用in和hasOwnProperty方法,區(qū)別如下:
1、in 操作符會檢查屬性是否在對象及其[[propertype]]原型鏈中;
2、hasOwnProperty(..)只會檢查是否在myObject對象中,不會檢查[[prototype]]原型鏈中。
Object.assign方法肯定是不會拷貝原型鏈上的屬性,所以模擬實(shí)現(xiàn)時需要用hasOwnProperty(..)判斷處理下,但是直接使用myObject.hasOwnProperty(..)是有問題的,因?yàn)橛械膶ο罂赡軟]有連接到Object.prototype上(通過Object.create(null)來創(chuàng)建),這種情況下,使用myObject.hasOwnProperty(..)就會失敗。
// saucxs var myObject = Object.create( null ); myObject.b = 2; ("b" in myObject); // true myObject.hasOwnProperty( "b" ); // TypeError: myObject.hasOwnProperty is not a function
解決辦法,使用call就可以了,如下:
// saucxs var myObject = Object.create( null ); myObject.b = 2; Object.prototype.hasOwnProperty.call(myObject, "b"); // true 所以具體到本次模擬實(shí)現(xiàn)中,相關(guān)代碼如下。 // saucxs // 使用 for..in 遍歷對象 nextSource 獲取屬性值 // 此處會同時檢查其原型鏈上的屬性 for (var nextKey in nextSource) { // 使用 hasOwnProperty 判斷對象 nextSource 中是否存在屬性 nextKey // 過濾其原型鏈上的屬性 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { // 賦值給對象 to,并在遍歷結(jié)束后返回對象 to to[nextKey] = nextSource[nextKey]; } }四、參考
1、MDN的Object.assign()
2、理解Object.assign()
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/109620.html
摘要:深拷貝與淺拷貝的出現(xiàn),就與這兩個數(shù)據(jù)類型有關(guān)。這時,就需要用淺拷貝來實(shí)現(xiàn)了。數(shù)據(jù)一但過多,就會有遞歸爆棧的風(fēng)險。這個方法是在解決遞歸爆棧問題的基礎(chǔ)上,加以改進(jìn)解決循環(huán)引用的問題。但如果你并不想保持引用,那就改用用于解決遞歸爆棧即可。 前言 這是前端面試題系列的第 9 篇,你可能錯過了前面的篇章,可以在這里找到: 數(shù)組去重(10 種濃縮版) JavaScript 中的事件機(jī)制(從原生到...
摘要:木易楊注意原始類型被包裝為對象木易楊原始類型會被包裝,和會被忽略。木易楊原因在于時,其屬性描述符為不可寫,即。木易楊解決方法也很簡單,使用我們在進(jìn)階期中介紹的就可以了,使用如下。 引言 上篇文章介紹了賦值、淺拷貝和深拷貝,其中介紹了很多賦值和淺拷貝的相關(guān)知識以及兩者區(qū)別,限于篇幅只介紹了一種常用深拷貝方案。 本篇文章會先介紹淺拷貝 Object.assign 的實(shí)現(xiàn)原理,然后帶你手動實(shí)...
摘要:它將返回目標(biāo)對象。有些文章說是深拷貝,其實(shí)這是不正確的。深拷貝相比于淺拷貝速度較慢并且花銷較大。拷貝前后兩個對象互不影響。使用深拷貝的場景完全改變變量之后對沒有任何影響,這就是深拷貝的魔力。 一、賦值(Copy) 賦值是將某一數(shù)值或?qū)ο筚x給某個變量的過程,分為: 1、基本數(shù)據(jù)類型:賦值,賦值之后兩個變量互不影響 2、引用數(shù)據(jù)類型:賦址,兩個變量具有相同的引用,指向同一個對象,相互之間有...
摘要:展開語法木易楊通過代碼可以看出實(shí)際效果和是一樣的。木易楊可以看出,改變之后的值并沒有發(fā)生變化,但改變之后,相應(yīng)的的值也發(fā)生變化。深拷貝使用場景木易楊完全改變變量之后對沒有任何影響,這就是深拷貝的魔力。木易楊情況下,轉(zhuǎn)換結(jié)果不正確。 一、賦值(Copy) 賦值是將某一數(shù)值或?qū)ο筚x給某個變量的過程,分為下面 2 部分 基本數(shù)據(jù)類型:賦值,賦值之后兩個變量互不影響 引用數(shù)據(jù)類型:賦址,兩個...
摘要:而在這個運(yùn)算符的相關(guān)用例中,往往會涉及到其他知識點(diǎn),深拷貝和淺拷貝就是其中之一。即對象的淺拷貝會對主對象的值進(jìn)行拷貝,而該值有可能是一個指針,指向內(nèi)存中的同一個對象。,可以看到深拷貝和淺拷貝是對復(fù)制引用類型變量而言的。 在ES6的系列文章中,基本都會提到Spread——擴(kuò)展運(yùn)算符(...)。而在這個運(yùn)算符的相關(guān)用例中,往往會涉及到其他知識點(diǎn),深拷貝和淺拷貝就是其中之一。 背景知識 在討...
閱讀 3189·2021-11-10 11:35
閱讀 1295·2019-08-30 13:20
閱讀 1117·2019-08-29 16:18
閱讀 2131·2019-08-26 13:54
閱讀 2155·2019-08-26 13:50
閱讀 955·2019-08-26 13:39
閱讀 2473·2019-08-26 12:08
閱讀 1951·2019-08-26 10:37