理解原型鏈

在JS中,原型鏈有時候讓人以爲很胡裏花哨,又是prototype__proto__又是各類指向什麼的,讓人以爲很頭疼。若是你也有這種感受,或許這篇文章能夠幫助到你

1、認識原型

一、先來一串代碼javascript

var Person = function(msg){
    this.msg = msg;
}
var person1 = new Person("wanger")

person1.constructor===Person;    //true
Person === Person.prototype.constructor; //true
person1.__proto__ === Person.prototype; //true
person1.__proto__.constructor === person1.constructor //true複製代碼

看暈了吧?是否是很胡裏花哨?不用擔憂,其實一張圖就能了明白這其中的關係:java

Alt text
Alt text

  • 藍色的是構造函數
  • 綠色的是構造函數實例出來的對象
  • 橙色的是構造函數的prototype,也是構造函數實例出來的對象的原型(它其實也是一個對象)

二、這裏特別要注意的是prototype__proto__的區別,prototype是函數纔有的屬性,而__proto__是每一個對象都有的屬性。(__proto__不是一個規範屬性,只是部分瀏覽器實現了此屬性,對應的標準屬性是[[Prototype]])。瀏覽器

2、認識原型鏈

一、咱們剛剛瞭解了原型,那原型鏈在哪兒呢?不要着急,再上一張圖:app

Alt text
Alt text

經過這張圖咱們能夠了解到,person1的原型鏈是:函數

person1 ----> Person.prototype ----> Object.prototype ----> null網站

二、事實上,函數也是一個對象,因此,Person的原型鏈是:ui

Person ----> Function.prototype ----> Object.prototype ----> nullthis

因爲Function.prototype定義了apply()等方法,所以,Person就能夠調用apply()方法。spa

三、若是把原型鏈的關係都顯示清楚,那會複雜一些,以下圖:prototype

Alt text
Alt text

這裏須要特別注意的是:全部函數的原型都是Function.prototype,包括Function構造函數和Object構造函數(如圖中的標紅部分)

3、原型鏈的繼承

一、假設咱們要基於Person擴展出Student,Student的構造以下:

function Student(props) {
    // 調用Person構造函數,綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}複製代碼

可是,調用了Person構造函數不等於繼承了PersonStudent建立的對象的原型是:

new Student() ----> Student.prototype ----> Object.prototype ----> null

示意圖以下所示:

Alt text
Alt text

必須想辦法把原型鏈修改成:

new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null

示意圖以下所示:

Alt text
Alt text

那咱們應該怎麼修改呢?仔細觀察兩張圖的差別,咱們會發現,若是咱們將Studentprototype改爲person1對象不就大功告成了?因而有了下面的代碼:

Student.prototype = person1 ;複製代碼

可是這時候有個問題:

Student.prototype.constructor === Student; //false複製代碼

原來Student.prototype(即person1)的constructor指向的仍是Person,這時候還須要咱們再改一下代碼:

Student.prototype.constructor = Student;複製代碼

這樣就能把Student的原型鏈順利的修改成: new Student() ----> Student.prototype ----> Person.prototype ----> Object.prototype ----> null 了;

完整的代碼顯示以下:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 調用Person構造函數,綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}
var person1 = new Person("wanger")
Student.prototype = person1 ;
Student.prototype.constructor = Student;複製代碼

3、用以上原型鏈繼承帶來的問題

一、若是在控制檯執行一遍上述的代碼,咱們會發現一些問題,如圖所示:

Alt text
Alt text

Student.prototype上含有以前person1帶有的屬性,那麼,這樣的繼承的方法就顯得不那麼完美了

二、這個時候,咱們能夠藉助一箇中間對象來實現正確的原型鏈,這個中間對象的原型要指向Person.prototype。爲了實現這一點,參考道爺(就是發明JSON的那個道格拉斯)的代碼,中間對象能夠用一個空函數F來實現:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 調用Person構造函數,綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}

// 空函數F:
function F() {
}

// 把F的原型指向Person.prototype:
F.prototype = Person.prototype;

// 把Student的原型指向一個新的F對象,F對象的原型正好指向Person.prototype:
Student.prototype = new F();

// 把Student原型的構造函數修復爲Student:
Student.prototype.constructor = Student;

// 繼續在Student原型(就是new F()對象)上定義方法:
Student.prototype.getGrade = function () {
    return this.grade;
};

// 建立wanger:
var wanger = new Student({
    name: '王二',
    grade: 9
});
wanger.msg; // '王二'
wanger.grade; // 9

// 驗證原型:
wanger.__proto__ === Student.prototype; // true
wanger.__proto__.__proto__ === Person.prototype; // true

// 驗證繼承關係:
wanger instanceof Student; // true
wanger instanceof Person; // true複製代碼

這其中主要用到了一個空函數F做爲過橋函數。爲何道爺會用過橋函數?用過橋函數F(){}主要是爲了清空構造的屬性。若是有些原Person的構造用不到,那麼過橋函數將是一個好的解決方案

這樣寫的話,Student.prototype上就沒有任何自帶的私有屬性,這是理想的繼承的方法

三、若是把繼承這個動做用一個inherits()函數封裝起來,還能夠隱藏F的定義,並簡化代碼:

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}複製代碼

封裝後,寫起來就像這樣:

var Person = function(msg){
    this.msg = msg;
}
var Student = function(props) {
    // 調用Person構造函數,綁定this變量:
    Person.call(this, props);
    this.grade = props.grade || 1;
}
inherits(Student,Person) ;複製代碼

這樣再一封裝的話,代碼就很完美了。

事實上,咱們也能夠在inherits中使用Object.create()來進行操做,代碼以下:

function inherits(Child, Parent) {
    Child.prototype = Object.create(Parent.prototype);
    Child.prototype.constructor = Child;
}複製代碼

若是有興趣瞭解Object.create()的其餘用法,能夠參考個人這篇博客JS中Object.create的使用方法;

4、ES6的新關鍵字class

在ES6中,新的關鍵字class,extends被正式被引入,它採用的相似java的繼承寫法,寫起來就像這樣:

class Student extends Person {
    constructor(name, grade) {
        super(msg); // 記得用super調用父類的構造方法!
        this.grade = grade || 1;
    }
    myGrade() {
        alert('I am at grade ' + this.grade);
    }
}複製代碼

這樣寫的話會更通俗易懂,繼承也至關方便。讀者能夠進入廖雪峯的官方網站詳細瞭解class的用法

參考文獻:廖雪峯的官方網站
原文地址:王玉略的我的網站

相關文章
相關標籤/搜索