成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

【進(jìn)階4-2期】Object.assign 原理及其實現(xiàn)

layman / 2775人閱讀

摘要:木易楊注意原始類型被包裝為對象木易楊原始類型會被包裝,和會被忽略。木易楊原因在于時,其屬性描述符為不可寫,即。木易楊解決方法也很簡單,使用我們在進(jìn)階期中介紹的就可以了,使用如下。

引言

上篇文章介紹了賦值、淺拷貝和深拷貝,其中介紹了很多賦值和淺拷貝的相關(guān)知識以及兩者區(qū)別,限于篇幅只介紹了一種常用深拷貝方案。

本篇文章會先介紹淺拷貝 Object.assign 的實現(xiàn)原理,然后帶你手動實現(xiàn)一個淺拷貝,并在文末留下一道面試題,期待你的評論。

淺拷貝 Object.assign

上篇文章介紹了其定義和使用,主要是將所有可枚舉屬性的值從一個或多個源對象復(fù)制到目標(biāo)對象,同時返回目標(biāo)對象。(來自 MDN)

語法如下所示:

Object.assign(target, ...sources)

其中 target 是目標(biāo)對象,sources 是源對象,可以有多個,返回修改后的目標(biāo)對象 target。

如果目標(biāo)對象中的屬性具有相同的鍵,則屬性將被源對象中的屬性覆蓋。后來的源對象的屬性將類似地覆蓋早先的屬性。

示例1

我們知道淺拷貝就是拷貝第一層的基本類型值,以及第一層的引用類型地址。

// 木易楊
// 第一步
let a = {
    name: "advanced",
    age: 18
}
let b = {
    name: "muyiy",
    book: {
        title: "You Don"t Know JS",
        price: "45"
    }
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "muyiy",
//  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: "muyiy",
//  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ā)生了改變,因為 Object.assign() 拷貝的是屬性值。假如源對象的屬性值是一個指向?qū)ο蟮囊?,它?strong>只拷貝那個引用地址。

示例2

String 類型和 Symbol 類型的屬性都會被拷貝,而且不會跳過那些值為 nullundefined 的源對象。

// 木易楊
// 第一步
let a = {
    name: "muyiy",
    age: 18
}
let b = {
    b1: Symbol("muyiy"),
    b2: null,
    b3: undefined
}
let c = Object.assign(a, b);
console.log(c);
// {
//     name: "muyiy",
//  age: 18,
//     b1: Symbol(muyiy),
//     b2: null,
//     b3: undefined
// } 
console.log(a === c);
// true
Object.assign 模擬實現(xiàn)

實現(xiàn)一個 Object.assign 大致思路如下:

1、判斷原生 Object 是否支持該函數(shù),如果不存在的話創(chuàng)建一個函數(shù) assign,并使用 Object.defineProperty 將該函數(shù)綁定到 Object 上。

2、判斷參數(shù)是否正確(目標(biāo)對象不能為空,我們可以直接設(shè)置{}傳遞進(jìn)去,但必須設(shè)置值)。

3、使用 Object() 轉(zhuǎn)成對象,并保存為 to,最后返回這個對象 to。

4、使用 for..in 循環(huán)遍歷出所有可枚舉的自有屬性。并復(fù)制給新的目標(biāo)對象(使用 hasOwnProperty 獲取自有屬性,即非原型鏈上的屬性)。

實現(xiàn)代碼如下,這里為了驗證方便,使用 assign2 代替 assign。注意此模擬實現(xiàn)不支持 symbol 屬性,因為ES5 中根本沒有 symbol 。

// 木易楊
if (typeof Object.assign2 != "function") {
  // Attention 1
  Object.defineProperty(Object, "assign2", {
    value: function (target) {
      "use strict";
      if (target == null) { // Attention 2
        throw new TypeError("Cannot convert undefined or null to object");
      }

      // Attention 3
      var to = Object(target);
        
      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource != null) {  // Attention 2
          // Attention 4
          for (var nextKey in nextSource) {
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}

測試一下

// 木易楊
// 測試用例
let a = {
    name: "advanced",
    age: 18
}
let b = {
    name: "muyiy",
    book: {
        title: "You Don"t Know JS",
        price: "45"
    }
}
let c = Object.assign2(a, b);
console.log(c);
// {
//     name: "muyiy",
//  age: 18,
//     book: {title: "You Don"t Know JS", price: "45"}
// } 
console.log(a === c);
// true

針對上面的代碼做如下擴(kuò)展。

注意1:可枚舉性

原生情況下掛載在 Object 上的屬性是不可枚舉的,但是直接在 Object 上掛載屬性 a 之后是可枚舉的,所以這里必須使用 Object.defineProperty,并設(shè)置 enumerable: false 以及 writable: true, configurable: true。

// 木易楊
for(var i in Object) {
    console.log(Object[i]);
}
// 無輸出

Object.keys( Object );
// []

上面代碼說明原生 Object 上的屬性不可枚舉。

我們可以使用 2 種方法查看 Object.assign 是否可枚舉,使用 Object.getOwnPropertyDescriptor 或者 Object.propertyIsEnumerable 都可以,其中propertyIsEnumerable(..) 會檢查給定的屬性名是否直接存在于對象中(而不是在原型鏈上)并且滿足 enumerable: true。具體用法如下:

// 木易楊
// 方法1
Object.getOwnPropertyDescriptor(Object, "assign");
// {
//     value: ?, 
//  writable: true,     // 可寫
//  enumerable: false,  // 不可枚舉,注意這里是 false
//  configurable: true    // 可配置
// }

// 方法2
Object.propertyIsEnumerable("assign");
// false

上面代碼說明 Object.assign 是不可枚舉的。

介紹這么多是因為直接在 Object 上掛載屬性 a 之后是可枚舉的,我們來看如下代碼。

// 木易楊
Object.a = function () {
    console.log("log a");
}

Object.getOwnPropertyDescriptor(Object, "a");
// {
//     value: ?, 
//  writable: true, 
//  enumerable: true,  // 注意這里是 true
//  configurable: true
// }

Object.propertyIsEnumerable("a");
// true

所以要實現(xiàn) Object.assign 必須使用 Object.defineProperty,并設(shè)置 writable: true, enumerable: false, configurable: true,當(dāng)然默認(rèn)情況下不設(shè)置就是 false。

// 木易楊
Object.defineProperty(Object, "b", {
    value: function() {
        console.log("log b");
    }
});

Object.getOwnPropertyDescriptor(Object, "b");
// {
//     value: ?, 
//  writable: false,     // 注意這里是 false
//  enumerable: false,  // 注意這里是 false
//  configurable: false    // 注意這里是 false
// }

所以具體到本次模擬實現(xiàn)中,相關(guān)代碼如下。

// 木易楊
// 判斷原生 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
  });
}
注意2:判斷參數(shù)是否正確

有些文章判斷參數(shù)是否正確是這樣的。

// 木易楊
if (target === undefined || target === null) {
    throw new TypeError("Cannot convert undefined or null to object");
}

這樣肯定沒問題,但是這樣寫沒有必要,因為 undefinednull 是相等的(高程 3 P52 ),即 undefined == null 返回 true,只需要按照如下方式判斷就好了。

// 木易楊
if (target == null) { // TypeError if undefined or null
    throw new TypeError("Cannot convert undefined or null to object");
}
注意3:原始類型被包裝為對象
// 木易楊
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 實際上被忽略了,原因在于他們自身沒有可枚舉屬性。

// 木易楊
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.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

但是下面的代碼是可以執(zhí)行的。

// 木易楊
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 
// }

原因很簡單,因為此時 undefinedtrue 等不是作為對象,而是作為對象 b 的屬性值,對象 b 是可枚舉的。

// 木易楊
// 接上面的代碼
Object.keys( b ); // [ "v1", "v2", "v3", "v4", "v5", "v6" ]

這里其實又可以看出一個問題來,那就是目標(biāo)對象是原始類型,會包裝成對象,對應(yīng)上面的代碼就是目標(biāo)對象 a 會被包裝成 [String: "abc"],那模擬實現(xiàn)時應(yīng)該如何處理呢?很簡單,使用 Object(..) 就可以了。

// 木易楊
var a = "abc";
console.log( Object(a) );
// [String: "abc"]

到這里已經(jīng)介紹很多知識了,讓我們再來延伸一下,看看下面的代碼能不能執(zhí)行。

// 木易楊
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: false。

// 木易楊
var myObject = Object( "abc" );

Object.getOwnPropertyNames( myObject );
// [ "0", "1", "2", "length" ]

Object.getOwnPropertyDescriptor(myObject, "0");
// { 
//   value: "a",
//   writable: false, // 注意這里
//   enumerable: true,
//   configurable: false 
// }

同理,下面的代碼也會報錯。

// 木易楊
var a = "abc";
var b = {
  0: "d"
};
Object.assign(a, b); 
// TypeError: Cannot assign to read only property "0" of object "[object String]"

但是并不是說只要 writable: false 就會報錯,看下面的代碼。

// 木易楊
var myObject = Object("abc"); 

Object.getOwnPropertyDescriptor(myObject, "0");
// { 
//   value: "a",
//   writable: false, // 注意這里
//   enumerable: true,
//   configurable: false 
// }

myObject[0] = "d";
// "d"

myObject[0];
// "a"

這里并沒有報錯,原因在于 JS 對于不可寫的屬性值的修改靜默失?。╯ilently failed),在嚴(yán)格模式下才會提示錯誤。

// 木易楊
"use strict"
var myObject = Object("abc"); 

myObject[0] = "d";
// TypeError: Cannot assign to read only property "0" of object "[object String]"

所以我們在模擬實現(xiàn) Object.assign 時需要使用嚴(yán)格模式。

注意4:存在性

如何在不訪問屬性值的情況下判斷對象中是否存在某個屬性呢,看下面的代碼。

// 木易楊
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ū)別如下(你不知道的JS上卷 P119):

1、in 操作符會檢查屬性是否在對象及其 [[Prototype]] 原型鏈中。

2、hasOwnProperty(..) 只會檢查屬性是否在 myObject 對象中,不會檢查 [[Prototype]] 原型鏈。

Object.assign 方法肯定不會拷貝原型鏈上的屬性,所以模擬實現(xiàn)時需要用 hasOwnProperty(..) 判斷處理下,但是直接使用 myObject.hasOwnProperty(..) 是有問題的,因為有的對象可能沒有連接到 Object.prototype 上(比如通過 Object.create(null) 來創(chuàng)建),這種情況下,使用 myObject.hasOwnProperty(..) 就會失敗。

// 木易楊
var myObject = Object.create( null );
myObject.b = 2;

("b" in myObject); 
// true

myObject.hasOwnProperty( "b" );
// TypeError: myObject.hasOwnProperty is not a function

解決方法也很簡單,使用我們在【進(jìn)階3-3期】中介紹的 call 就可以了,使用如下。

// 木易楊
var myObject = Object.create( null );
myObject.b = 2;

Object.prototype.hasOwnProperty.call(myObject, "b");
// true

所以具體到本次模擬實現(xiàn)中,相關(guān)代碼如下。

// 木易楊
// 使用 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];
    }
}
本期思考題
如何實現(xiàn)一個深拷貝?
參考
MDN 之 Object.assign()

ES2015系列(二) 理解 Object.assign

進(jìn)階系列目錄

【進(jìn)階1期】 調(diào)用堆棧

【進(jìn)階2期】 作用域閉包

【進(jìn)階3期】 this全面解析

【進(jìn)階4期】 深淺拷貝原理

【進(jìn)階5期】 原型Prototype

【進(jìn)階6期】 高階函數(shù)

【進(jìn)階7期】 事件機(jī)制

【進(jìn)階8期】 Event Loop原理

【進(jìn)階9期】 Promise原理

【進(jìn)階10期】Async/Await原理

【進(jìn)階11期】防抖/節(jié)流原理

【進(jìn)階12期】模塊化詳解

【進(jìn)階13期】ES6重難點

【進(jìn)階14期】計算機(jī)網(wǎng)絡(luò)概述

【進(jìn)階15期】瀏覽器渲染原理

【進(jìn)階16期】webpack配置

【進(jìn)階17期】webpack原理

【進(jìn)階18期】前端監(jiān)控

【進(jìn)階19期】跨域和安全

【進(jìn)階20期】性能優(yōu)化

【進(jìn)階21期】VirtualDom原理

【進(jìn)階22期】Diff算法

【進(jìn)階23期】MVVM雙向綁定

【進(jìn)階24期】Vuex原理

【進(jìn)階25期】Redux原理

【進(jìn)階26期】路由原理

【進(jìn)階27期】VueRouter源碼解析

【進(jìn)階28期】ReactRouter源碼解析

交流

進(jìn)階系列文章匯總?cè)缦?,?nèi)有優(yōu)質(zhì)前端資料,覺得不錯點個star。

https://github.com/yygmind/blog

我是木易楊,網(wǎng)易高級前端工程師,跟著我每周重點攻克一個前端面試重難點。接下來讓我?guī)阕哌M(jìn)高級前端的世界,在進(jìn)階的路上,共勉!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/104636.html

相關(guān)文章

  • 進(jìn)階4-1】詳細(xì)解析賦值、淺拷貝和深拷貝的區(qū)別

    摘要:展開語法木易楊通過代碼可以看出實際效果和是一樣的。木易楊可以看出,改變之后的值并沒有發(fā)生變化,但改變之后,相應(yīng)的的值也發(fā)生變化。深拷貝使用場景木易楊完全改變變量之后對沒有任何影響,這就是深拷貝的魔力。木易楊情況下,轉(zhuǎn)換結(jié)果不正確。 一、賦值(Copy) 賦值是將某一數(shù)值或?qū)ο筚x給某個變量的過程,分為下面 2 部分 基本數(shù)據(jù)類型:賦值,賦值之后兩個變量互不影響 引用數(shù)據(jù)類型:賦址,兩個...

    silvertheo 評論0 收藏0
  • 進(jìn)階4-3】面試題之如何實現(xiàn)一個深拷貝

    摘要:今天這篇文章我們來看看一道必會面試題,即如何實現(xiàn)一個深拷貝。木易楊注意這里使用上面測試用例測試一下一個簡單的深拷貝就完成了,但是這個實現(xiàn)還存在很多問題。 引言 上篇文章詳細(xì)介紹了淺拷貝 Object.assign,并對其進(jìn)行了模擬實現(xiàn),在實現(xiàn)的過程中,介紹了很多基礎(chǔ)知識。今天這篇文章我們來看看一道必會面試題,即如何實現(xiàn)一個深拷貝。本文會詳細(xì)介紹對象、數(shù)組、循環(huán)引用、引用丟失、Symbo...

    longmon 評論0 收藏0
  • 進(jìn)階3-4】深度解析bind原理、使用場景及模擬實現(xiàn)

    摘要:返回的綁定函數(shù)也能使用操作符創(chuàng)建對象這種行為就像把原函數(shù)當(dāng)成構(gòu)造器,提供的值被忽略,同時調(diào)用時的參數(shù)被提供給模擬函數(shù)。 bind() bind() 方法會創(chuàng)建一個新函數(shù),當(dāng)這個新函數(shù)被調(diào)用時,它的 this 值是傳遞給 bind() 的第一個參數(shù),傳入bind方法的第二個以及以后的參數(shù)加上綁定函數(shù)運行時本身的參數(shù)按照順序作為原函數(shù)的參數(shù)來調(diào)用原函數(shù)。bind返回的綁定函數(shù)也能使用 n...

    guyan0319 評論0 收藏0
  • 進(jìn)階1-1】理解JavaScript 中的執(zhí)行上下文和執(zhí)行棧

    摘要:首次運行代碼時,會創(chuàng)建一個全局執(zhí)行上下文并到當(dāng)前的執(zhí)行棧中。執(zhí)行上下文的創(chuàng)建執(zhí)行上下文分兩個階段創(chuàng)建創(chuàng)建階段執(zhí)行階段創(chuàng)建階段確定的值,也被稱為。 (關(guān)注福利,關(guān)注本公眾號回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實戰(zhàn)、面試指導(dǎo)) 本周正式開始前端進(jìn)階的第一期,本周的主題是調(diào)用堆棧,,今天是第一天 本計劃一共28期,每期重點攻克一個面試重難點,如果你還不了解本進(jìn)...

    import. 評論0 收藏0
  • 進(jìn)階2-1】深入淺出圖解作用域鏈和閉包

    摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問外鏈,點擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對象,在全局環(huán)境中定義的變量就會綁定到全局對象中。 (關(guān)注福利,關(guān)注本公眾號回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實戰(zhàn)、面試指導(dǎo)) 本周開始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第6天。 本...

    levius 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<