一般來講,建立物件的方法有以下四種:
- 物件字面量的方式
- 通過Object.create()
- 通過new關鍵字建立一個Object建構函式的例項物件
- 通過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的錯誤 -
properties_object:可選,與傳給Object.defineProperties()方法的引數一樣,通過配置屬性的特性來給被建立的物件新增屬性,可配置的屬性特性是
value
、writable
、enumerable
、configurable
以及get
、set
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()的效果
從圖中可以看出如果傳的非物件,則對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()一個可選實現方案的。
其實它的操作與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__
剛被列入標準,所以建議在現階段避免直接在業務中使用
三、new一個Object的例項
使用Object建立物件的有兩種情況
-
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 複製程式碼
-
如果不通過new關鍵字呼叫,表現跟以建構函式方式呼叫是一致的
四、new 建立自定義建構函式的例項
通過自定義建構函式建立例項的的好處比較明顯,可以封裝通用的方法和屬性,以建立同一型別的不同例項物件
參考:
- JavaScript instanceof 運算子深入剖析
- javascript高階程式設計第三版