Object.create(..)和new(..)的內部實現

zifeiyu發表於2018-11-20

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原型鏈上的方法

Object.create(..)和new(..)的內部實現

var a = {} 這並不是一個純粹的空物件,它會繼承原型鏈上的很多方法

Object.create(..)和new(..)的內部實現

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的物件繼承,原型鏈的概念會有幫助。

相關文章