該系列文章將帶你全面理解js對象和原型鏈,並用es5去實現類以及認識es6中class
的美妙。該系列一共有3篇文章:javascript
__proto__
和prototype
來深刻理解JS對象和原型鏈javascript
實現類與繼承ES6
的class
此篇爲第一篇從__proto__
和prototype
來深刻理解JS對象和原型鏈java
prototype
和__proto__
引用《JavaScript權威指南》的一段描述es6
Every JavaScript object has a second JavaScript object (or null ,
but this is rare) associated with it. This second object is known as a prototype, and the first object inherits properties from the prototype.
複製代碼
翻譯過來就是:每個JS對象必定關聯着另一個JS對象(也許是null
,可是它必定是惟一的)。這個另外的對象就是所謂的原型對象。每一個對象從它的原型對象中繼承屬性和方法。瀏覽器
若是你是初學者,這句話確定很繞吧(不過它確實描述得很精闢)。不要緊,你只須要先把握如下兩點就行了:bash
在JS裏,萬物皆對象。方法(Function
)是一個對象,方法的原型(Function.prototype
)是對象。函數
JS有三種構造對象的方式ui
經過對象字面量this
var person1 = {
name: 'Jzin',
sex: 'male'
}
複製代碼
經過構造函數es5
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
var person1 = new Person('Jzin', 'male');
複製代碼
所謂構造函數就是:能夠經過它來new出一個對象實例的函數。一般構造函數裏都用了this
,由於這樣子纔會給調用它的對象綁定屬性。spa
由函數Object.create
構造
var person1 = {
name: 'Jzin',
sex: 'male'
}
var person2 = Object.create(person1);
複製代碼
這三種方法的異同到後面還會繼續分析,如今你只需掌握如何構建對象就好啦。
以上兩點就是這小結你要掌握的東西:
至此,我還沒介紹什麼是原型,不過不要緊,咱們先看看原型的分類,慢慢你就會理解了。
JS的原型分紅兩類:隱式原型和顯示原型
當你建立一個函數時,JS會爲這個函數(別忘了:JS一切皆對象)自動添加prototype
屬性,這個屬性的值是一個對象,也就是原型對象(即函數名.prototype
)。這個對象並非空對象,它擁有constructor
屬性,屬性值就是原函數。固然,你也能夠本身在原型對象中添加你須要的屬性(即函數名.prototype.屬性名=屬性值
).
那麼原型對象(prototype
)的做用是什麼呢?能夠用原型對象來實現繼承,即經過函數構造出來的實例能夠直接訪問其構造函數的原型對象中的屬性。可能有點繞,可是讀到後面你就會理解啦。
須要注意的是:
prototype
)只有函數才擁有。咱們後面講的隱式原型則是全部對象都有。Function.prototype.bind
方法構造出來的函數是個例外,它沒有prototype
屬性。JavaScript中任意對象都有一個內置屬性[[prototype]],在ES5以前沒有標準的方法訪問這個內置屬性,可是大多數瀏覽器都支持經過__proto__
來訪問。如今,所謂的隱式原型就是__proto__
了。
隱式原型的指向
隱式原型指向建立這個對象的函數的prototype
(Object.create
函數構造出來的實例有點例爲,後面會說明。其實也不是例爲,只是它通過了必定的封裝)。看下面的例子
function person(name) {
this.name = name;
}
person.prototype.class = 'Human';
var person1 = new person('Jzin');
console.log(person1.__proto__); //person { class: 'Human' }
console.log(person.__proto__); //[Function]
複製代碼
person1
的__proto__
很容易理解:它是由person
方法構造的實例,它的__proto
天然就是person.prototype
person
的__proto__
呢?其實每個方法的構造方法都是Function
方法,也就是全部方法的__proto__
都是Function.prototype
。若是如今還不理解也不要緊,後面會有一副圖幫你理解。
隱式原型的做用
__proto__
依次查找。這也是protorype
能夠實現繼承的緣由。L.__proto__.__proto__ ..... === R.prototype
這個是否爲真就好了。這也是instanceof
運算符的原理。後面會講到。__proto__
和prototype
先上圖,若是圖片顯示不了能夠點擊這裏:傳送門。
咱們來理解一下這幅圖:
Foo()
Foo
的原型屬性prototype
指向了它的原型對象Foo.prototype
。原型對象Foo.protoype
中有默認屬性constructor
指向了原函數Foo
。Foo
建立的實例f2,f1
的__proto__
指向了其構造函數的原型對象Foo.prototype
,因此Foo
的全部實例均可以共享其原型對象的屬性。Foo
實際上是Function
函數建立的實例對象,因此它的__proto__
就是Function
函數的原型對象Function.prototype
。Foo
的原型對象實際上是Object
函數建立的實例對象,因此它的__proto__
就是Object
函數的原型對象Object.prototype
。Function
函數構造的實例對象。因此全部函數的__proto__
都指向Fucntion.prototype
。Function
函數對象是由它自己建立(姑且能夠這麼理解),因此Function.__proto__d等
於Function_prototype
Function
函數的原型對象實際上是Object
函數建立的實例對象,因此它的__proto__
就是Object
函數的原型對象Object.prototype
。Object
函數實際上是Function
函數建立的實例對象,因此它的__proto__
就是Function
函數的原型對象Function.prototype
。Object.prototype
的__proto__
是指向null
的!!!相信你經過這幅圖,已經對原型有本身的理解了,咱們來總結一下:
__proto__
屬性,指向該對象的構造函數的原型對象。__proto__
屬性,還有prototype
屬性,prototype
指向該方法的原型對象。__proto__
的指向相信通過上面的介紹,你已經能很好地掌握__proto__
的指向了。本節經過一些實際的例子讓你更加深刻地理解__proto__
的指向。
在一開始,咱們瞭解了構造對象的三種方式:(1)對象字面量的方式 (2)new的方式 (3)ES5中的Object.create()。其實,這三種方式在我看來都是一種的,即經過new來構建。爲何這麼說呢?咱們來仔細分析分析:
經過字面量構造對象
var person1 = {
name: 'Jzin',
sex: 'male'
}
複製代碼
其實這種方式只是爲了開發人員更方便建立對象的一個語法糖(語法糖:顧名思義,就是很甜的糖,通過代碼封裝,讓語法更加人性化,實際的內部實現是同樣的)。
上面也就等價於:
var person1 = new Object();
person1.name = 'Jzin';
person1.sex = 'male';
複製代碼
person1
是Object
函數構造的對象,因此person1.__ptoto__
就指向Object.prototype
。
也就是說,經過對象字面量構造出來的對象,其__proto__
都是指向Object.prototype
經過構造函數
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
var person1 = new Person('Jzin', 'male');
複製代碼
經過new操做符調用的函數就是構造函數。由構造函數構造的對象,其__proto__
指向其構造函數的原型對象。
在本例中,person1.__proto__
就指向Person.prototype
。
由函數Object.create
構造
var person1 = {
name: 'Jzin',
sex: 'male'
}
var person2 = Object.create(person1);
複製代碼
由函數Object.create(obj)
構造出來的對象,其隱式原型有點特殊:指向obj.prototype
。在本例中,person2.__proto__
指向person1
。
這是爲何呢?咱們來分析一下。在沒有Object.create
函數的日子裏,爲了實現這一功能,咱們須要這樣子作:
Object.create = function(p) {
function F(){}
F.prototype = p;
return new F();
}
var f = Object.create(p);
複製代碼
這樣子也就是實現了其功能,分析以下:
// 如下是用於驗證的僞代碼
var f = new F(); //var f = Object.create(p);
// 因而有
f.__proto__ === F.prototype //true
// 又由於
F.prototype === p; //true
// 因此
f.__proto__ === o //true
複製代碼
所以由Object.create(p)
建立出來的對象它的隱式原型指向p。
經過上面的分析,相信你對原型又進一步理解啦。咱們再來幾題玩玩。
構造函數的顯式原型的隱式原型
內建對象(built-in object)的的隱式原型
好比Array()
,Array.prototype.__proto__
指向什麼?
Array.prototype.__proto__ === Object.prototype //true
複製代碼
好比Function()
,Function.prototype.__proto__
指向什麼?
Function.prototype.__proto__ === Object.prototype //true
複製代碼
根據上面那幅圖,這些也很簡單啦。
自定義對象
默認狀況下
function Foo(){}
var foo = new Foo()
Foo.prototype.__proto__ === Object.prototype //true
foo.prototype.__proto__ === Foo.prototype //true
複製代碼
理由,就沒必要解釋了吧
其餘狀況
function Bar(){}
function Foo(){}
//這時咱們想讓Foo繼承Bar
Foo.prototype = new Bar()
Foo.prototype.__proto__ === Bar.prototype //true
console.log(Foo.prototype.constructor); //[Function: Bar]
複製代碼
function Foo(){}
//咱們不想讓Foo繼承誰,可是咱們要本身從新定義Foo.prototype
Foo.prototype = {
a:10,
b:-10
}
//這種方式就是用了對象字面量的方式來建立一個對象,根據前文所述
Foo.prototype.__proto__ === Object.prototype
console.log(Foo.prototype.constructor); //[Function: Object]
複製代碼
注意:以上兩種狀況都等於徹底重寫了Foo.prototype
,因此Foo.prototype.constructor
也跟着改變了,因而constructor
這個屬性和原來的構造函數Foo
也就切斷了聯繫。
instanceof
的左值通常是一個對象,右值通常是一個構造函數,用來判斷左值是不是右值的實例。instanceof
操做符的內部實現機制和隱式原型、顯式原型有直接的關係,它的內部實現原理是這樣的:
//設 L instanceof R
//經過判斷
L.__proto__.__proto__ ..... === R.prototype ?
//最終返回true or false
複製代碼
也就是沿着L的__proto__
一直尋找到原型鏈末端,直到等於R.prototype
爲止。知道了這個也就知道爲何如下這些奇怪的表達式爲何會獲得相應的值了
Function instanceof Function //true
Function instanceof Object // true
Object instanceof Function // true
Object instanceof Object // true
Number instanceof Number //false
Number instanceof Function //true
Number instanceof Object //true
複製代碼
你發現沒有:這就是原型鏈啊!!!
L1.__proto__
指向R1.prototype
,
R1.prototype.__proto__
指向R2.prototype
...
Rn.prototype.__proto__
指向Object.prototype
Object.prototype.__proto__
指向null
這樣子就把原型串起來啦,也就是實現了繼承。也就是爲何全部對象都要toString
方法,由於這個方法在Object.prototype
上面啊啊啊啊。
至此,相信你已經徹底理解原型和原型鏈了。固然,只是理解不實踐是沒用的。在下一篇,咱們將利用原型來實現類與繼承。