博客 / 詳情

返回

【前端Talkking】JS-一步一步掌握Javascript中的原型與原型鏈

0.寫在前面

如果大家想深入學習Javascript編程語言,Javascript中的原型及原型鏈是必須掌握的。當初我在學習原型及原型鏈的時候,就遇到過不少阻礙,希望通過我的這篇文章,能夠讓你真正的掌握JavaScript中的原型及原型鏈。好啦,開始我們的原型及原型鏈的旅途吧~
在介紹Javascript原型之前,我們先來了解一段歷史。

1.Javascript繼承機制的設計思想

1994年,網景公司發佈了Navigator瀏覽器0.9版,當時這個版本的瀏覽器只能用來瀏覽,並不具有與用户用户進行互動的功能,比如説,要判斷用户是否填寫了表單數據,只能通過服務器來進行判斷,這樣會帶來一個弊端:極大的浪費了帶寬以及服務器資源。在這種情況下,就需要一種腳本語言,這種語言能夠與瀏覽器進行交互,Brendan Eich負責開發這種腳本語言(也就是Javascript),當時,這位工程師認為這種腳本語言不需要設計的太複雜,只需完成簡單操作即可,比如判斷用户是否填寫了表單數據。此時,我們還要了解下當時的編程語言背景,在1994年的時候,C++是最興盛的面向對象的編程語言,Java1.0也將於第二年推出,Brendan Eich也將Javascript設計為面向對象的語言,在Javascript中一切皆對象。當時,他遇到了一個難題,到底需不需要將繼承機制引入Javascript中?最終的結果是,也許他受到了C++和Java的影響,繼承機制最終被引入到Javascript編程語言中。

下面我們來看看在C++中生成一個對象的方法:

A *a=new A(param);

而在Java中生成一個對象的方法:

Foo foo=new Foo();

我們再來看看Javascript生成一個對象的方法:

function Dog(name){
    this.name=name;
}

var dogA = new Dog("旺旺");
alert(dogA.name);//旺旺
我們再來看看Javascript中另外一種寫法:

function Dog(name){
    this.name=name;
    this.species ="犬科";
}
var dogA = new Dog("旺旺");
dogA.species="貓科";
var dogB = new Dog("旺旺2");
alert(dogB.species);//犬科

在這個例子中,生成了兩個對象dogA與dogB,dogA修改了species,但是我們訪問dogB的species,還是原來的值。於是,我們可以看出通過這種方法生成的實例對象,每個對象都有自己的屬性和方法的副本,實例對象間不能做到屬性的方法的共享,這樣帶來的一個缺點就是極大的浪費了系統的資源。有沒有改進的方法呢?有,肯定有!

2.Javascript中原型(prototype)的引入

考慮到上面的不足,這位工程師決定給每個構造函數添加一個prototype屬性,這個屬性指向一個對象,稱為prototype對象。在Javascript中,一切皆對象,對象可以分為三類:實例對象(通過new和構造函數創建出來的對象)、函數對象(一般也稱為函數)、原型對象(函數對象的prototype屬性所指向的對象)。我們首先來看下實例對象中的屬性和方法,如下圖:

圖片描述

實例一旦創建,將自動引用prototype對象的方法和屬性,也就是説,針對實例對象而言,它的屬性和方法可以分為兩種:一種是本地的,另外一種是引用的。

prototype、_proto_和constructor三角關係

我將用下面的一副圖來描述三者的關係:

圖片描述

我將上幅圖總結為下面幾點內容:

  • 任何函數對象都有prototype屬性,它指向對應的原型對象,表示其實例對象的原型對象;
  • 任何原型對象都有一個constructor屬性,它指向對應的函數對象;

任何對象都有一個隱藏的_proto屬性,它是對原型對象的引用;

  • _proto_屬性不是一個規範的屬性,只是部分瀏覽器實現了此屬性(如chrome和Firefox),如果想訪問對象的原型,可以使用Object.getPrototype(object)訪問。

一定要牢記上面的幾點內容,它將對後面的內容非常重要。

3 原型實例分析

説明:後面的實例,如果看不懂,可以再看看前面的內容,好好理解下前面的內容,一定可以理解後面的例子的。同時,下面的例子運行結果我會有一定的解釋。
step1:查看對象的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

var will = new Person("Will", 28);
console.log(will.__proto__);
console.log(will.constructor);

運行結果:
圖片描述

解釋:will對象本身並沒有"constructor"這個屬性,但是通過原型鏈查找,找到了will原型(will.__proto__)的"constructor"屬性,並得到了Person函數
對象之間的關係:

step2:查看對象will原型的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

console.log(will.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.constructor);
console.log(Person.prototype.constructor === Person);

運行結果:
圖片描述

對象之間的關係

圖片描述

step3:查看對象Object的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

console.log(Person.prototype.__proto__ === Object.prototype);
console.log(typeof Object);
console.log(Object);
console.log(Object.prototype);
console.log(Object.prototype.__proto__);
console.log(Object.prototype.constructor);

運行結果:
圖片描述
對象之間的關係:
圖片描述

step4:查看函數對象的原型

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

console.log(Person.__proto__ === Function.prototype);
console.log(Person.constructor === Function)
console.log(typeof Function);
console.log(Function);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.prototype.constructor);

運行結果:
圖片描述

對象之間的關係:

圖片描述

4 通過原型改進實例-實現繼承

step1:最老的方式

function Person(name, age){
    this.name = name;
    this.age = age; 
    this.getInfo = function(){
        console.log(this.name + " is " + this.age + " years old");
    }
}

var will = new Person('Will',28);
var wilber = new Person("Will", 20);

對象之間的關係:

step2:通過原型prototype實現繼承

function Person(name, age){
    this.name = name;
    this.age = age; 
   
}
Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
}

對象之間的關係:
圖片描述

5 原型鏈

什麼是原型鏈?

由於_proto_是任何對象都有的屬性,而Javascript中萬物皆對象,所以會形成一條_proto_連接起來的鏈條,遞歸訪問_proto_必須最終到頭,並且值為null。
原型鏈有什麼作用?
屬性查找與隱藏:當Javascript引擎查找對象的屬性時,先查找對象本身是否存在該屬性,如果不存在,再沿着_proto_這條鏈向上查找,但不會查找自身的prototype。

var A = function(){};
var a = new A();

以上面的這幅圖為例,查找某個屬性的時候,會沿着這條原型鏈進行查找,直到為null。
原型鏈之屬性查找

  function Person(name, age){
        this.name = name;
        this.age = age; 
    }
    Person.prototype.MaxNumber = 9999;
    Person.__proto__.MinNumber = -9999;
    var will = new Person("Will", 28);
    console.log(will.MaxNumber);// 9999
    console.log(will.MinNumber);//undefined


原型鏈之屬性隱藏


function Person(name, age){
    this.name = name;
    this.age = age; 
}
Person.prototype.getInfo = function(){
    console.log(this.name + " is " + this.age + " years old");
    
}

var will = new Person("Will", 28);
will.getInfo = function(){
    console.log("getInfo method from will instead of prototype");//
};
will.getInfo();

對象創建方式影響原型鏈的構成

var July = {
    name: "July",
    age: 28,
    getInfo: function(){
        console.log(this.name + " is " + this.age + " years old");
    },
}
console.log(July.getInfo());


hasOwnProperty

var will = new Person('Will',28);
    var wilber = new Person("Will", 20);

    for(var attr in will){
        console.log(attr);
    }    
    console.log('---------------');
    for(var attr in wilber){
        if(will.hasOwnProperty(attr)){
            console.log(attr);
        }
    }

6.總結

  • 在Javascript中,通過原型(prototype)實現了對象的繼承;
  • 在Javascript中,一切皆對象,prototype、_proto_與constructorJavascript中所有的對象關聯起來;
  • 原型鏈可以實現對象屬性的查找和隱藏; hasOwnProterty函數可以用來判斷屬性是對象本地屬性還是原型鏈上的屬性;
    相信到這裏,你已經掌握了Javascript中的原型和原型鏈的知識點了。

歡迎關注我的微信公眾號。您的支持將鼓勵我繼續創作!

圖片描述

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.