js物件建立進階

william_zhou發表於2019-02-27

一般來講,建立物件的方法有以下四種:

  1. 物件字面量的方式
  2. 通過Object.create()
  3. 通過new關鍵字建立一個Object建構函式的例項物件
  4. 通過new關鍵字建立一個自定義建構函式的例項物件

在日常開發中,除了字面量方式,其他方式可能會偶爾用到,但畢竟少,瞭解得也未必夠深刻,這裡針對其中的一兩種做下淺談

一、字面量方式

const o = {
    key_one: `value`,
    key_two: 1234
}
複製程式碼

字面量方式是實際開發中使用最多的建立物件的方式,想比其他方式,優點在於簡便、靈活
值得注意的是,通過字面量建立的物件並沒有隱式的呼叫Object建構函式(見《javascript高階程式設計》,5.1 Object型別,p81),另一方面

const o = {}
o instanceof Object // true
複製程式碼

並沒有通過建構函式建立,但通過instanceof來判斷確為Object的例項,是什麼原因?

instanceof操作運算原理可以歸結為以下方法,雖然在ES2015對instanceof判斷的方式做了一些調整,但大體邏輯上還是可以沿用的

// L 表示左表示式,R 表示右表示式
function instance_of(L, R) {
    // 取 R 的顯示原型
    var O = R.prototype;
    // 取 L 的隱式原型
    L = L.__proto__;
    while (true) {
        if (L === null)
           return false
        // 這裡重點:當 O 嚴格等於 L 時,返回 true
        if (O === L)
            return true
        L = L.__proto__
     }
}
複製程式碼

總結為一句話就是,通過比較L instanceof R中L原型鏈中的原型物件是否與R的prototype物件嚴格相等,來判斷L是否為R的例項。(想更深入瞭解,請點選這裡

因此,雖然字面量形式建立的物件並沒有隱式呼叫Object建構函式,但在建立的時候,o.__proto__會預設指向Object.prototype,除非手動修改,否則是成立的

二、Object.create()方法建立物件

Object.create()接受兩個引數:

Object.create(prototype_object[, properties_object])
複製程式碼
  • prototype_object: 只能是一個物件或者null,將作為返回的新建物件的原型(即可通過新物件的**proto**訪問),如果傳入其他值或不傳值,則會報TypeError的錯誤

    prototype_object 引數
  • properties_object:可選,與傳給Object.defineProperties()方法的引數一樣,通過配置屬性的特性來給被建立的物件新增屬性,可配置的屬性特性是valuewritableenumerableconfigurable以及getset

    const prototype_object = {
        color: `red`,
        showColor () {
            return this.color
        }
    }
    const properties_object = {
        age: {
            value: 27,
            writable: false,
            enumerable: false,
            configurable: false
        },
        name: {
            value: `lan`,
            writable: true,
            enumerable: true,
            configurable: true
        }
    }
    const o = Object.create(prototype_object, properties_object)
    o.hasOwnProperty(`color`) // false
    o.hasOwnProperty(`age`) // true
    prototype_object.isPrototypeOf(o)  // true
    o.__proto__ === prototype_object // true
    o instanceof Object // true
    複製程式碼

❗️如果第一個引數為null,第二個引數不傳,則會建立一個沒有屬性、沒有原型的空物件

空物件
const o = Object.create(null)
o.__proto__ // undefined
o instanceof Object // false
複製程式碼

o.__proto__並不是指向null,而是undefined,如此一來,也就不再是Object的例項。
這樣的建立物件,通過字面量或者Object建構函式是不能很好實現的。

Object.create()的模擬實現

object()方法實現

在《javascript高階程式設計 第三版》,6.3.4 原型式繼承[p169],中提到道格拉斯·克勞克福德在2006年一篇題為Prototypal Inheritance in Javascript文章中,提出了一種使用原型實現繼承的方法。

function object(o) {
    function F () {}
    F.prototype = o
    return new F()
}
複製程式碼

這個方法所達到的效果與Object.create()沒傳第二個引數時的功能類似,把傳入的物件作為新物件的原型,後來被標準化實現,就是現在的Object.create()。當傳人的值為非物件(包括null)時,會返回一個普通物件,所以在這點上,還是無法達到Object.create()的效果

通過object建立物件

從圖中可以看出如果傳的非物件,則對prototype的賦值無效

修改__proto__

__proto__,在開始並沒有被標準支援,由於各現代瀏覽器都實現了(IE10及以下不支援)通過__proto__屬性訪問物件的[[Prototype]]即原型,為了統一相容性,__proto__方式在ES2015中被加入了標準,(詳請請檢視MDN中關於Object.prototype.__proto__部分),在標準中有關__proto__的賦值操作可點選這裡瞭解。

The proto property can also be used in an object literal definition to set the object [[Prototype]] on creation, as an alternative to Object.create()

在MDN關於__proto__的介紹中,說明__proto__是可以用來作為Object.create()一個可選實現方案的。

js物件建立進階

其實它的操作與Object.getPrototypeOf和Object.setPrototypeOf效果是一樣的。都是通過修改[[Prototype]]來改變物件的原型鏈。通過__proto__還可以使用更為快捷的方式

// 以下方法是有效的
const o = {
    __proto__: {
        color: `red`
    }
}
o.__proto__ // {color: `red`}
Object.getPrototypeOf(o)  // {color: `red`}

// 與Object.create() 只傳第一個引數時表現一致
// 第二個引數接受物件和null,不傳或為其他值會報錯
Object.setPrototypeOf(o, null)
o.__proto__ // null
複製程式碼

但在MDN中也明確警告,修改[[Prototype]]在任何一個js引擎中都是一個耗時的操作,所以並不建議通過__proto__或Object.setPrototypeOf()的方式去修改[[Prototype]],而是建議使用Object.create(),同時,由於__proto__剛被列入標準,所以建議在現階段避免直接在業務中使用

warning

三、new一個Object的例項

使用Object建立物件的有兩種情況

  1. Object作為建構函式,通過new關鍵字呼叫,Object([value]),value為可選,根據引數的型別,可以分為三種情況:

    • 沒有傳參或者為null、undefined,返回一個空的普通物件
    • 如果是物件,則會直接返回這個物件
    • 如果是其他型別,則會通過相應型別的建構函式去建立相對應的型別例項,並返回
    new Object(undefined) // {}
    let o = new Object(1) // Number{1}
    o instanceof Number // true
    Number.prototype.isPrototypeOf(o) // true
    o instanceof Object // true
    複製程式碼
  2. 如果不通過new關鍵字呼叫,表現跟以建構函式方式呼叫是一致的

四、new 建立自定義建構函式的例項

通過自定義建構函式建立例項的的好處比較明顯,可以封裝通用的方法和屬性,以建立同一型別的不同例項物件

參考:

相關文章