詳解Object.create(null)

司想君發表於2018-04-11


在Vue和Vuex的原始碼中,作者都使用了Object.create(null)來初始化一個新物件。為什麼不用更簡潔的{}呢?

SegmentFaultStack Overflow等開發者社群中也有很多人展開了討論,在這裡總結成文,溫故知新。

Object.create()的定義

照搬一下MDN上的定義:

Object.create(proto,[propertiesObject])
複製程式碼
  • proto:新建立物件的原型物件
  • propertiesObject:可選。要新增到新物件的可列舉(新新增的屬性是其自身的屬性,而不是其原型鏈上的屬性)的屬性。

舉個例子(惡改了一下MDN的官方例子,看懂的點贊):

const car = {
  isSportsCar: false,
  introduction: function () {
    console.log(`Hi girl, this is a ${this.name}. 
    Do you like to have a drink with me ? ${this.isSportsCar}`);
  }
};

const porsche = Object.create(car,{
    //color成為porsche的資料屬性
    //顏色不喜歡,可以改色或貼膜,所以可修改
    color:{
        writable:true,
        configurable:true,
        value:'yellow'
    },
    //type成為porsche的訪問器屬性
    type:{
        // writable、configurable等屬性,不顯式設定則預設為false
        // 想把普通車改成敞篷,成本有點大了,所以就設成不可配置吧
        get:function(){return 'convertible'},
        set:function(value){"change this car to",value}
    }
});

porsche.name = "Porsche 911"; // "name""porsche"的屬性, 而不是"car"的
porsche.isSportsCar = true; // 繼承的屬性可以被覆寫

porsche.introduction();
// expected output: "Hi girl, this is a Porsche 911. Do you like to have a drink with me ? true"
複製程式碼

Object.create()的定義其實很簡單,弄清楚上面這個例子就可以了。

Object.create()、{…}的區別

先看看我們經常使用的{}建立的物件是什麼樣子的:

var o = {a:1};
console.log(o)
複製程式碼

在chrome控制檯列印如下:

詳解Object.create(null)

從上圖可以看到,新建立的物件繼承了Object自身的方法,如hasOwnPropertytoString等,在新物件上可以直接使用。

再看看使用Object.create()建立物件:

var o = Object.create(null,{
    a:{
           writable:true,
        configurable:true,
        value:'1'
    }
})
console.log(o)
複製程式碼

在chrome控制檯列印如下:

詳解Object.create(null)

可以看到,新建立的物件除了自身屬性a之外,原型鏈上沒有任何屬性,也就是沒有繼承Object的任何東西,此時如果我們呼叫o.toString()會報Uncaught TypeError的錯誤。

大家可能會注意到,第一個引數使用了null。也就是說將null設定成了新建立物件的原型,自然就不會有原型鏈上的屬性。我們再把上面的例子改一改:

var o = Object.create({},{
    a:{
           writable:true,
        configurable:true,
        value:'1'
    }
})
console.log(o)
複製程式碼

null改為{},結果是怎樣的?在chrome控制檯列印如下:

詳解Object.create(null)

我們看到,這樣建立的物件和使用{}建立物件已經很相近了,但是還是有一點區別:多了一層proto巢狀。

我們最後再來改一下:

var o = Object.create(Object.prototype,{
    a:{
           writable:true,
        configurable:true,
        value:'1'
    }
})
console.log(o)
複製程式碼

chrome控制檯列印如下:

詳解Object.create(null)

這次就和使用{}建立的物件一模一樣了。至此,我相信大家已經對兩者的區別十分清楚了。

Object.create(null)的使用場景

再回到文章開頭的問題,為什麼很多原始碼作者會使用Object.create(null)來初始化一個新物件呢?這是作者的習慣,還是一個最佳實踐?

其實都不是,這並不是作者不經思考隨便用的,也不是javascript程式設計中的最佳實踐,而是需要因地制宜,具體問題具體分析。

我們進一步比較一下Object.create(null){}建立控物件的區別:

在chrome列印如下:

詳解Object.create(null)

從上圖可以看到,使用create建立的物件,沒有任何屬性,顯示No properties,我們可以把它當作一個非常純淨的map來使用,我們可以自己定義hasOwnPropertytoString方法,不管是有意還是不小心,我們完全不必擔心會將原型鏈上的同名方法覆蓋掉。舉個例子:

//Demo1:
var a= {...省略很多屬性和方法...};
//如果想要檢查a是否存在一個名為toString的屬性,你必須像下面這樣進行檢查:
if(Object.prototype.hasOwnProperty.call(a,'toString')){
    ...
}
//為什麼不能直接用a.hasOwnProperty('toString')?因為你可能給a新增了一個自定義的hasOwnProperty
//你無法使用下面這種方式來進行判斷,因為原型上的toString方法是存在的:
if(a.toString){}

//Demo2:
var a=Object.create(null)
//你可以直接使用下面這種方式判斷,因為存在的屬性,都將定義在a上面,除非手動指定原型:
if(a.toString){}
複製程式碼

另一個使用create(null)的理由是,在我們使用for..in迴圈的時候會遍歷物件原型鏈上的屬性,使用create(null)就不必再對屬性進行檢查了,當然,我們也可以直接使用Object.keys[]

總結:

  1. 你需要一個非常乾淨且高度可定製的物件當作資料字典的時候;
  2. 想節省hasOwnProperty帶來的一丟丟效能損失並且可以偷懶少些一點程式碼的時候

Object.create(null)吧!其他時候,請用{}

以上


相關文章