JavaScript必須掌握的基礎 ---> 原型&原型鏈

原型和原型鏈的主要做用:javascript

  • 實現屬性和方法的公用
  • 繼承

因此下面的例子全是以構造函數爲例。java

原型

函數是也是對象,是一個屬性的集合,因此函數下也有屬性,也能夠自定義屬性。當咱們建立一個函數時就默認會有一個prototype屬性,這個屬性是一個對象(屬性的集合)。這個東西就是原型---經過調用構造函數而建立的那個對象實例的原型對象。prototype裏也有個屬性constructor,指向的是函數自己。git

prototype
function Person() {
}
Person.prototype.name='erdong';
var p1=new Person();
var p2=new Person();
console.log(p1.name); // erdong
console.log(p2.name); // erdong

函數Person有個prototype屬性,給這個屬性添加一個name的屬性。p1 和 p2 爲這個函數的實例,當訪問 p1.name 和 p2.name 時其值都是 prototype下面 name的值。這個prototype對象就是 p1 和 p2的實例原型,它下面的因此屬性和方法 p1 和 p2 均可以獲取並使用。github

看一下原型對象與構造函數的關係:瀏覽器

那麼實例是怎樣與原型對象作關聯的呢?函數

__proto__

每一個JavaScript對象都具備的一個屬性 -- __proto__ 這個屬性指向該對象的原型。不過它是一個隱式屬性,並非全部瀏覽器都支持它,咱們能夠把它看作一種實例與實例原型之間的聯繫橋樑。學習

function Person() {
}
var p1 = new Person();

console.log(p1.__proto__==Person.prototype); // true

上述p1.__proto__與原型對象時相等的,因而可知p1.__proto__指向的是原型對象。this

constructor

每一個函數都有一個prototype屬性,而prototype下都有一個constructor屬性,它指向prototype所在函數。spa

function Person() {
}
console.log(Person.prototype.constructor==Person) // true

以上就是關於原型幾個重要的"屬性"已經說完了,下面來說講原型連。prototype

實例與原型

JavaScript規定,當讀取對象的某個屬性或方法時,先從自身查找,若是找不到就去其__proto__指向的原型對象上去找,若是找不到就去原型對象的原型對象上查找,若是再找不到就去原型對象的原型對象上去找... , 就這樣直到找到最上層,至於哪裏是最上層,下面會提到。

function Person() {
}

var p1 = new Person();

console.log(p1.name);// undefined

p1.show();// Uncaught TypeError: p1.show is not a function

上述例子,p1 爲構造函數 Person的實例,當訪問 p1 的 name 屬性和 show 方法時,由於 p1 是剛 new 出來的實例,因此並無找到。看下面的例子:

function Person() {
}

Person.prototype.name='erdong';

Person.prototype.show=function () {
    console.log(this.name);
}

var p1=new Person();

console.log(p1.name);// erdong

p1.show();// erdong

當在 prototype上添加 name 屬性和 show方法後,p1 就能夠正確的訪問,這就說明 p1 在查找屬性(方法)時,在自身沒有找到 就會去__proto__所指的原型對象上去查找。再來看一個例子:

function Person() {
}

Person.prototype.name='erdong';

Person.prototype.show=function () {
    console.log(this.name);
}

var p1=new Person();

p1.name = 'chen'

console.log(p1.name);// chen

當咱們在 p1(對象) 上添加一個屬性 name 這個時候再去訪問 p1.name 那麼輸出的就是 "chen" 而不是 "erdong"。 這就是一個對象查找屬性(方法)時的一個規則。

原型的頂層

咱們在上述例子查找 p1的name 時當查找到 Person.prototype 還未找到時,咱們應該還往下查找,下一級是誰呢? 由於 Person.prototype是對象,那麼他就有一個__proto__屬性,指向的是其原型對象--也就是其對應構造函數的 prototype。那麼Person.prototype 是誰呢?是Object,由於對象能夠經過 new Object()建立:

var obj = new Object();
// 咱們平時都是經過字面量的形式來寫:var obj = { }; 其實就至關於 new Object(); 只不過是javascript在內部執行了。
obj.name = 'erdong';
console.log(obj.name); // erdong

看圖:

爲何當查找到 Object.prototype 找不到就輸出 undefined 了呢?
由於當在 Object.prototype 也找不到 name 屬性,就會去 Object.prototype 指向的原型對象上查找,咱們在上面提到,對象與其原型對象是經過 __proto__作關聯的,可是javascript中規定,Object.prototype.__proto__是不存在的 也就是null

console.log(Object.prototype.__proto__ === null); // true

這一點要牢記。

原型鏈

原型鏈也是JavaScript中很重要的一個概念,之因此說是一個概念,是由於它是不存在的,不像一個對象的屬性,或者是一個對象的方法同樣實例存在。
個人理解就是--一個(實例)對象的屬性或者方法的查找規則。這個規則能夠很簡單,也能夠很複雜。

咱們把上面全部的知識總結一下:
每一個函數都有一個原型對象(prototype),原型對象又包含一個屬性(constructor),指向的是函數自己,函數的實例都有一個隱式原型(__proto__),指向的是構造函數的原型對象(prototype)。

查找規則:當咱們訪問實例的一個屬性時,先從實例自身查找,若是找不到就去其內部指向的原型對象上去查找,若是再找不到,就去其內部指向的原型對象內部指向的原型對象上去查找,就這樣一直找到原型的最頂端。

看圖:

藍色的線就表示一條原型鏈。

改變prototype

function Person() {
}
Person.prototype={
    name: 'erdong',
    sex: '男',
    doSoming: function() {
        console.log(this.name);
    }
}

var p1=new Person();
p1.doSoming();
console.log(p1.__proto__==Person.prototype); // true
console.log(Person.prototype.constructor==Person);// false

上面的例子,將構造函數的prototype屬性重寫了。雖然p1也能找的到name,可是prototype下的constructor屬性再也不指向Person了。實際上指向了Object

console.log(Person.prototype.constructor==Object); //true

是由於咱們重寫了Personprototype,此時Person.prototype只是一個普通的對象。即:

Person.prototype.constructor = Person.prototype.__proto__.constructor = Object.prototype.constructor = Object

constructor屬性很重要時,咱們能夠這樣作:

function Person() {
}
Person.prototype={
    constructor: Person,  // 主動加上constructor屬性
    name: 'erdong',
    sex: '男',
    doSoming: function() {
        console.log(this.name);
    }
}
console.log(Person.prototype.constructor==Person);// true

上述代碼實例能夠適用於當構造函數擁有不少方法或者屬性時的寫法。

繼承

--

原型鏈是實現繼承的一種方式。這裏只是略提一下,下面的文章會詳細理解。

當咱們不去徹底重寫函數的prototype屬性,而是讓它等於另外一個構造函數的實例時結果會怎樣呢?

function SuperType() {
}
SuperType.prototype.name = 'erdong';
SuperType.prototype.getName=function() {
    return this.name;
}

function SubType() {
}
SubType.prototype=new SuperType();

var instance=new SubType();

console.log(instance.name); // erdong
console.log(instance.getName()); // erdong

上述例子中有兩個構造函數SuperTypeSubTypeSuperType原型上有個name屬性和getName 方法。instance是另外一個構造函數SubType的實例,本來instanceSuperType是沒有關係的。可是如今instance能夠獲取到 name 屬性和 getName 方法。緣由就是SubType重寫了prototype屬性,讓它的值等於SuperType的實例。因此存在SuperType.prototype中的屬性和方法,如今也存在與SubType.prototype中。

看下面的圖:

藍色的線爲SubType.prototype改變後的原型( __proto__ 和prototype)的指向(也是實例查找屬性的路線),紅色爲原來的原型( __proto__和prototype)的指向。

JavaScript高級程序設計一書中解釋上述的示例爲原型鏈的基本概念--當咱們讓原型對象等於另外一個構造函數的實例,此時的原型對象將包含一個指向另外一個原型的指針,相應的,另外一個原型中也包含着一個指向另外一個構造函數的指針。加入另外一個原型又是另外一個構造函數的實例,那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條。這就是所謂原型鏈的基本概念。

關於原型的方法

isPrototypeOf、getPrototypeOf、instanceof、in、hasOwnProperty

isPrototypeOf

用於判斷傳入的對象內部是否有一個原型對象的指針。

function Person(){
}
var p = new Person();
console.log( Person.prototype.isPrototypeOf( p ) );//  true

咱們上面講到實例與原型對象是經過__proto__作關聯的,__proto__並非Javascript規範,因此咱們現實中不能使用它來判斷實例與原型對象的關係,這個時候就用isPrototypeOf

getPrototypeOf

ES6 Object新增方法,返回的是傳入對象的原型。

function Person(){
}
var p = new Person();
console.log( Object.getPrototypeOf(p)===Person.prototype );//  true

上述代碼輸出的是 true 證實 Object.getPrototypeOf(p) 獲取到的就是 p 的原型。

instanceof

判斷前者是不是後者的一個實例。

function Person(){
}
var p = new Person();

console.log(p instanceof Person); // true
console.log(p instanceof Object); // true

因爲 p 既是 Person的實例,同時它也是一個對象,因此也是Object的實例。

看下面的示例:

console.log(Function instanceof Object); // true
console.log(Object instanceof Function); // true

Function既是Object的實例,Object又是Function的實例。是有點繞了,下面會說明這一狀況。

in

判斷前者是不是後者原型鏈中的一個屬性。

function Person() {
}

Person.prototype.name='erdong';

var p=new Person();
p.sex='男';

console.log('sex' in p); // true
console.log('name' in p); // true
console.log('address' in p); // false
hasOwnProperty

檢測傳入的字符串是不是調用者的自身屬性,若是是自身的屬性,返回true,若是是原型中的屬性或者不存在,返回false。

function Person() {
}

Person.prototype.name='erdong';

var p=new Person();
p.sex='男';

console.log(p.hasOwnProperty('name'));// false
console.log(p.hasOwnProperty('sex')); // true
console.log(p.hasOwnProperty('address')); // false

與 in 不一樣的是若是該屬性存在於實例上包括原型鏈上,就返回true,而hasOwnProperty只有是自身的屬性,纔會返回true。

思考

咱們(構造)函數也是對象,上面說過對象下面都會有一個__proto__屬性,那麼函數的__proto__指向誰呢?

console.log(Person.__proto__===Function.prototype); // true

函數都是經過 new Function()來建立的,雖然咱們平時建立函數並非經過 new

下面這個函數:

function sum (num1, num2) {
    return num1 + num2;
}

其實在JavaScript內部應該是這樣實現的:

var sum = new Function("num1", "num2", "return num1 + num2");

因此Person對應的構造函數應該是Function

那麼新的問題又來了?

Function也一個函數,也是一個對象,那麼他一樣也有__proto__屬性,也有prototype屬性,它們分別指向什麼呢?

console.log(typeof Function); // 'function'

console.log(Function.__proto__ === Function.prototype); // true

看到上面是否是會很奇怪?下面解釋一下:

Function是一個函數,它也是經過new Function建立的,因此它是被自身建立的,它的__proto__指向的自身的prototype--也就是Function.prototype

那麼Function.prototype.__proto__又指向誰呢?

console.log(Function.prototype.__proto__ === Object.prototype)

很顯然,Function.prototype.__proto__是一個對象,因此它指向的是Object.prototype

還有一個問題,Object也是一個構造函數,也是一個對象,那麼它應該也有prototype__proto__屬性,咱們在上面說到 Object.prototype. __proto__ null,那麼Object.__proto__指向誰呢?

console.log(Object.__proto__ === Function.prototype); // true

上面Object.__proto__ 又指向了Function.prototype,是由於Object是函數,因此它的原型就是Function.prototype

最後總結一下:

看似關係很複雜,其實一條一條捋清楚就有一種恍然大悟的感受。

寫在最後

若是文中有錯誤,請務必留言指正,萬分感謝。

點個贊哦,讓咱們共同窗習,共同進步。

GitHub

相關文章
相關標籤/搜索