一篇文章讀懂JS原型和原型鏈

前言

你好! 歡迎閱讀本篇文章,今天我將要用一篇文章帶你深入理解JavaScript中的原型和原型鏈,讓我們一起揭開原型神祕的面試,一起窺探其中的奧妙。

一切皆是對象

看到這個標題你可能就有你可能就有疑問了,怎麼這篇文章一開始就要胡扯了,然後準備要關閉這篇這篇文章。別急、客官,接下來就讓我們仔細掰扯掰扯這句話

“一切皆是對象”是沒錯,關鍵在於你如何理解這個“對象”這個概念。這句話的重點也在於此。

在理解對象之前,我們先引用一個我們的老朋友 typeof ,相信很多前端的同學都應該知道這個JavaScript的常用運算符,如果你不知道,請仔細閱讀下面文字。

我們都知道 typeof 可以判斷一個變量的類型,返回值共有一下幾種( undefined、number、string、boolean、function、object、Symbol )其中 SymbolES6 引入的新類型。

function test() {
    var x;
    console.log(typeof x)             //undefined
    console.log(typeof 100)           //number
    console.log(typeof 'typeof')      //string
    console.log(typeof true)          //boolean


    console.log(typeof [1,2,3,4])       //object
    console.log(typeof {a:1,b:2})       //object
    console.log(typeof null)            //object null在這裏爲空對象(注意)
    console.log(typeof new Number(100)) //object

    console.log(typeof function(){})    //function
}

上面代碼列出的是 typeof 返回的幾種值,其中代碼前四種是值類型,餘下的幾種情況都是引用類型,他們也都是對象。關於引用類型和值類型的區別請看我們另一篇文章值類型和引用類型.

對象是若干屬性的集合,對象裏面的一切都是屬性,都是以鍵值對的形式出現,其中方法也是一種特殊的屬性。屬性的值也能是一個對象(禁止套娃qAq)對象可以用instanceof判斷,記住一切(應用類型)都是對象,對象是屬性的集合,一切對象都是通過函數來創建的

原型知多少

說到原型我們就不得不說 prototype 我們知道每個函數都有一個 prototype (原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。我放了兩張圖片幫你理解(qwq)
原型1
上圖SuperType是函數,他的原型就是SuperType Prototype,原型的constructor屬性指向它的構造函數SuperType
對象的原型
上圖是對象的Object的以及它的原型,我們可以看到原型上有很多我們常用的屬性和方法,應爲一切對象的原型都直接或間接的指向了它,因此可以使用上面的方法。

上面說到每一個函數都有自己的prototype,也就是原型,那麼通過 new 生成的函數實例呢?這裏就不禁要疑問了,其實每個new出來的實例,以及每一個對象都有一個__proto__,即隱式原型。
隱示原型
上面圖片顯示了對象obj的__proto__屬性,下面我再放一張Object的prototype
Object.prototype
從上面的截圖看出,obj.proto__和Object.prototype是一樣的
我們用一張圖表示。
原型2
從圖中我們得知每個對象都有都有一個__proto
,並且指向該對象函數的prototype

唯一的特例Object.prototype的__proto__指向的是null

還有就是,函數也是對象,函數也有一個__proto__,指向Function.prototype,函數是由Function創建出來的,而Function又被自身創建。

var foo = new Function("x","y","return x+y")

就是這樣,建議不要這樣創建函數

原型鏈和繼承

下面用一張詳細的圖,請仔細閱讀,我們根據這張圖講解原型鏈
原型3

要說起原型鏈,我們不得不用到一個工具instanceof,對於引用類型,我們想要知道引用類型的具體是數組還是對象,就要用到這個工具。如下所示

function Foo(){}
var f1 = new Foo()

console.log(foo instanceof Foo)      //true
console.log(foo instanceof Object)   //true

結合上圖我們我們來看看,instanceof運算符的規則就是,實例foo沿着__proto__這條線,同時Foo沿着prototype這條線來找,如果這兩條線找到了同一個對象,那麼就返回true,否則就返回false, instanceof表示一種繼承關係,或者原型鏈的結構

JavaScript是通過原型鏈的方式來實現繼承的,又被稱作爲原型鏈對象繼承,對於一個對象,它可以訪問它原型對象的屬性。同理,原型對象也是一個屬性,它也有自己的原型,因此也可以繼承它原型對象原型的屬性。

訪問一個對象的屬性時,現在基本的對象內找,如果找不到,就沿着__proto__這條鏈向上找。

幾種常見的繼承方式

組合繼承

組合繼承(combination inheritance),有時候也叫做僞經典繼承,指的是將原型鏈和借用構造函數的
技術組合到一塊,從而發揮二者之長的一種繼承模式。其背後的思路是使用原型鏈實現對原型屬性和方
法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數
複用,又能夠保證每個實例都有它自己的屬性。下面來看一個例子

function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
//繼承屬性
SuperType.call(this, name);
this.age = age;
}
//繼承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27

組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點,成爲 JavaScript 中最常用的繼
承模式。而且, instanceof 和 isPrototypeOf()也能夠用於識別基於組合繼承創建的對象。

寄生組合繼承

組合繼承最大的問題就是無論什麼情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。沒錯,子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。

function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //創建對象
prototype.constructor = subType; //增強對象
subType.prototype = prototype; //指定對象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
};

ES6中繼承

ES6是通過class實現了類,通過extends實現了標準化繼承,然而,class、extends本質上還是語法糖,通過bable可以將其轉譯爲ES5代碼執行。

ES6中的class、extends使用的是ES5中寄生組合繼承,並對寄生組合繼承作出了一下優化,如下所示

  1. ES6中繼承使用Object.create()以SuperClass的原型對象創建了一個新的原型對象,即SubClass.prototype.proto = SuperClass.prototype。 同時使其constructor屬性指向SubClass。
  2. ES5的繼承是子類的原型對象__proto__屬性指向父類的原型對象,而ES6中繼承是子類__proto__指向父類。

寫在最後

以上就是本人對原型和原型鏈的整理和記錄,因爲資料來源比較多,在整理和書寫是難免出現錯誤,如果你看到這裏,發現原文有錯誤的地方,或者有什麼想要分享的,也歡迎在評論區留言,一起學習,加油,奧利給

引用

  1. JavaScript高級程序設計 第三版
  2. js原型和原型鏈 博客園-王福朋
  3. js設計模式 繼承和原型鏈
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章