Object.create()
經常會有這樣的疑問?Object.create()到底做了什麼工作? 像這樣兩行程式碼有什麼不同?
var obj ={a: 1}
var b = obj
var c = Object.create(obj)
複製程式碼
我們來做一點事情,
var obj ={a: 1}
var b = obj
console.log(obj.a) // 1
console.log(b.a) // 1
b.a = 2
console.log(obj.a) //2
複製程式碼
var obj ={a: 1}
var b = Object.create(obj)
console.log(obj.a) // 1
console.log(b.a) // 1
b.a = 2
console.log(obj.a) //1
複製程式碼
所以我們立馬可以想到Object.create貌似建立了一個新的物件,這個物件繼承(關聯)了obj的屬性,改變新物件的同名屬性並不會影響原物件。
如果直接用“=”來賦值,只是一個物件的引用。
那麼,為什麼會這樣呢?是因為Object.create()複製了一個新物件麼?實際上並不是,只是Object.create()返回了一個新的空物件,並且這個空物件的建構函式的原型(prototype)是指向obj的。所以當我們訪問新物件b.a的時候實際上是通過原型鏈訪問的obj中的a。
當我們試圖修改b.a的時候,這裡有一個知識點(物件的遮蔽效應,如果修改物件的一個與原型鏈同名屬性,那麼會在當前物件中新建一個改屬性,這個屬性擁有更高階的訪問優先順序,所以就會遮蔽原型鏈中的同名屬性)
所以Object.create的具體內部實現模擬
_create = function (o) {
let F = function () {}
F.prototype = o
return new F()
}
複製程式碼
再來看這個例子
var person = {
friends : ["Van","Louis","Nick"]
};
var anotherPerson = _create(person);
anotherPerson.friends.push("Rob");
var yetAnotherPerson = _create(person);
yetAnotherPerson.friends.push("Style");
alert(person.friends);//"Van,Louis,Nick,Rob,Style"
複製程式碼
相當於做了一次淺複製,新建立的各個物件實際上是會共享原始物件中的引用型別的值,這意味著person.friends不僅屬於person所有,而且也會被anotherPerson以及yetAnotherPerson共享
實際上真正的Object.create()還可以傳入第二個引數,這個引數與Object.defineProperties方法的第二個引數格式相同, 通過第二個引數是會在新物件中重新建立一個屬性的,然後通過屬性遮蔽原理避免修改原物件。
var person = {
name : "Van"
};
var anotherPerson = Object.create(person, {
name : {
value : "Louis"
}
});
alert(anotherPerson.name);//"Louis"
複製程式碼
Object.create(null) 會建立一個真正的空物件,並沒有繼承Object原型鏈上的方法
var a = {} 這並不是一個純粹的空物件,它會繼承原型鏈上的很多方法
new()
關於new的內部實現模擬
function _new () {
// arguments實際上是一個類陣列物件,需要轉成陣列
let args = [].slice.call(arguments)
// 第一個引數是建構函式,把它拿出來
let constructor = args.shift()
// Object.create()返回一個新物件,這個物件的建構函式的原型指向Foo
let context = Object.create(constructor.prototype)
// 在返回的context物件環境中執行建構函式,為新的context新增屬性
let result = constructor.apply(context, args)
// 如果Foo顯示的返回了一個物件,那麼應該直接返回這個物件,而不用理會以上所有的操作,一般不會發生這種情況,但是new的實現的確是這樣的邏輯
// 這裡之所以判斷型別是否為object還要新增 != null 的判斷,是因為null的typeof結果也是‘object’
// 不同的物件在底層都表示為二進位制,在Javascript中二進位制前三位都為0的話會被判斷為Object型別,null的二進位制表示全為0,自然前三位也是0,所以執行typeof時會返回"object"
return (typeof result === 'object' && result != null) ? result : context
}
function Foo (name) {
this.name = name
}
Foo.prototype.getName = function() {
console.log(this.name)
}
var a = _new(Foo, 'tom')
a.getName()
複製程式碼
實際上new操作符, 就是通過Object.ctreate()建立一個新的物件,這個物件的原型指向建構函式,並且在新建物件的上下文環境中執行建構函式,初始化新建物件的屬性。
總結
當然這裡的實現只是一個模擬實現,至於就是內部真正的實現方式必然是複雜得多。比如說這裡的new方法和Object.create()必然不會相互引用,這樣會產生一個無限迴圈的函式,所以說這裡只是一個大概思路上的引導,對於理解js的物件繼承,原型鏈的概念會有幫助。