js中__proto__和prototype的區別和關係?
正好這段時間在重新看這部分,寫一篇回答來梳理一下吧。
proto(隱式原型)與prototype(顯式原型)
1.是什麼
- 顯式原型 explicit prototype property:
每一個函式在建立之後都會擁有一個名為prototype的屬性,這個屬性指向函式的原型物件。
Note:通過Function.prototype.bind方法構造出來的函式是個例外,它沒有prototype屬性。(感謝 @陳禹魯 同學的答案讓我知道這一點)
NOTE Function objects created using Function.prototype.bind do not
have a prototype property or the [[Code]], [[FormalParameters]], and
[[Scope]] internal properties. —– ECMAScript Language Specification
- 隱式原型 implicit prototype link:
JavaScript中任意物件都有一個內建屬性[[prototype]],在ES5之前沒有標準的方法訪問這個內建屬性,但是大多數瀏覽器都支援通過proto來訪問。ES5中有了對於這個內建屬性標準的Get方法Object.getPrototypeOf().
Note: Object.prototype 這個物件是個例外,它的proto值為null
- 二者的關係:
隱式原型指向建立這個物件的函式(constructor)的prototype
2. 作用是什麼
- 顯式原型的作用:用來實現基於原型的繼承與屬性的共享。
ECMAScript does not use classes such as those in C++, Smalltalk, or
Java. Instead objects may be created in various ways including via a
literal notation or via constructors which create objects and then
execute code that initialises all or part of them by assigning initial
values to their properties. Each constructor is a function that has a
property named “prototype” that is used to implement prototype-based
inheritance and shared properties.Objects are created by using
constructors in new expressions; for example, new Date(2009,11)
creates a new Date object. —-ECMAScript Language Specification
- 隱式原型的作用:構成原型鏈,同樣用於實現基於原型的繼承。舉個例子,當我們訪問obj這個物件中的x屬性時,如果在obj中找不到,那麼就會沿著proto依次查詢。
Every object created by a constructor has an implicit reference
(called the object’s prototype) to the value of its constructor’s
“prototype” —-ECMAScript Language Specification
3. _proto_的指向
proto的指向到底如何判斷呢?根據ECMA定義 ‘to the value of its constructor’s “prototype” ’ —-指向建立這個物件的函式的顯式原型
所以關鍵的點在於找到建立這個物件的建構函式,接下來就來看一下JS中物件被建立的方式,一眼看過去似乎有三種方式:
(1)物件字面量的方式
(2)new 的方式
(3)ES5中的Object.create()
但是我認為本質上只有一種方式,也就是通過new來建立。為什麼這麼說呢,首先字面量的方式是一種為了開發人員更方便建立物件的一個語法糖,本質就是 var o = new Object(); o.xx = xx;o.yy=yy;
再來看看Object.create(),這是ES5中新增的方法,在這之前這被稱為原型式繼承,道格拉斯在2006年寫了一篇文章,題為 Prototypal Inheritance In JavaScript。在這篇文章中,他介紹了一種實現繼承的方法,這種方法並沒有使用嚴格意義上的建構函式。他的想法是藉助原型可以基於已有的物件建立新物件,同時還不比因此建立自定義型別,為了達到這個目的,他給出瞭如下函式:
function object(o){
function F(){}
F.prototype = o;
return new F()
}
- 1
- 2
- 3
- 4
- 5
—– 《JavaScript高階程式設計》P169
所以從實現程式碼 return new F() 中我們可以看到,這依然是通過new來建立的。不同之處在於由Object.create() 創建出來的物件沒有建構函式,看到這裡你是不是要問,沒有建構函式我怎麼知道它的proto指向哪裡呢,其實這裡說它沒有建構函式是指在 Object.create() 函式外部我們不能訪問到它的建構函式,然而在函式內部實現中是有的,它短暫地存在了那麼一會兒。假設我們現在就在函式內部,可以看到物件的建構函式是F, 現在
//以下是用於驗證的虛擬碼
var f = new F();
//於是有
f.__proto__ === F.prototype //true
//又因為
F.prototype === o;//true
//所以
f.__proto__ === o;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
因此由Object.create(o)創建出來的物件它的隱式原型指向o。好了,物件的建立方式分析完了,現在你應該能夠判斷一個物件的proto指向誰了。
好吧,還是舉一些一眼看過去比較疑惑的例子來鞏固一下。
- 建構函式的顯示原型的隱式原型
1.內建物件(built-in object):比如Array(),Array.prototype.proto指向什麼?Array.prototype也是一個物件,物件就是由 Object() 這個建構函式建立的,因此Array.prototype.proto === Object.prototype //true,或者也可以這麼理解,所有的內建物件都是由Object()建立而來。
- 自定義物件
- 預設情況下:
function Foo(){}
var foo = new Foo()
Foo.prototype.__proto__ === Object.prototype //true 理由同上
- 1
- 2
- 3
- 其他情況:
(1)
function Bar(){}
//這時我們想讓Foo繼承Bar
Foo.prototype = new Bar()
Foo.prototype.__proto__ === Bar.prototype //true
- 1
- 2
- 3
- 4
(2)
//我們不想讓Foo繼承誰,但是我們要自己重新定義Foo.prototype
Foo.prototype = {
a:10,
b:-10
}
//這種方式就是用了物件字面量的方式來建立一個物件,根據前文所述
Foo.prototype.__proto__ === Object.prototype
- 1
- 2
- 3
- 4
- 5
- 6
- 7
注: 以上兩種情況都等於完全重寫了Foo.prototype,所以Foo.prototype.constructor也跟著改變了,於是乎constructor這個屬性和原來的建構函式Foo()也就切斷了聯絡。
- 建構函式的隱式原型
既然是建構函式那麼它就是Function()的例項,因此也就指向Function.prototype,比如 Object.proto=== Function.prototype
4. instanceofinstanceof
//設 L instanceof R
//通過判斷
L.__proto__.__proto__ ..... === R.prototype ?
//最終返回true or false
- 1
- 2
- 3
- 4
也就是沿著L的proto一直尋找到原型鏈末端,直到等於R.prototype為止。知道了這個也就知道為什麼以下這些奇怪的表示式為什麼會得到相應的值了
Function instanceof Object // true
Object instanceof Function // true
Function instanceof Function //true
Object instanceof Object // true
Number instanceof Number //false
- 1
- 2
- 3
- 4
- 5
文章參考:JavaScript instanceof 運算子深入剖析