這樣理解原型與原型鏈比較簡單

原型

在JavaScript中,有兩個原型,分別是 prototype_proto_
注:在ECMA-262第5版中管這個 _proto_ 叫 [[Prototype]]

prototype 屬性:
這是一個顯式原型屬性,只有函數才擁有該屬性。
_proto_ 屬性:
這是每個對象都有的隱式原型屬性,指向了創建該對象的構造函數的原型。

什麼是函數?什麼是對象?

創建函數有兩種方式:
①、通過 function 關鍵字定義
②、通過 new Function
其中函數又可以分爲普通函數和構造函數,兩者唯一的區別就是調用的方式不同,語法上沒有差異

function normalFn(){}   // 普通函數(函數名首字母小寫)
function StructFn(){}   // 構造函數(函數名首字母大寫)

創建對象的方式多種多樣,其中傳統方法是通過構造函數來創建對象,使用 new 關鍵字即可創建

let obj = new StructFn()  // obj就是一個對象

在JS中,萬物都是對象,函數也屬於對象,只不過函數相對於對象有着更爲精確的定義

原型初探

爲了證明 prototype屬性 是函數獨有,而__proto__是每個對象都有的,我們可以測試以下代碼:

function a(){}
console.log(a.prototype);    // {constructor: ƒ}
console.log(a.__proto__);    // ƒ () { [native code] }

let obj = new Object()
console.log(obj.prototype)  // undefined
console.log(obj.__proto__)  // {constructor: ƒ, __defineGetter__: ƒ,  …}

可以看到對象的 顯式原型(prototype)爲undefined

*注意:
undefinednull 同屬於對象,但是它們都沒有原型,爲什麼? 在JavaScript中,目前只有兩個只有一個值的數據類型,那就是 undefined 和 null*
由於這兩種數據類型有且只有一個值,並且沒有方法,所以自然而然就沒有原型了。
有的同學會問,那NaN呢?NaN是屬於Number數據類型的,屬於對象,只有_proto_ 屬性
console.log(undefined.__proto__);  // 報錯:Uncaught TypeError: Cannot read property '__proto__' of undefined
console.log(null.__proto__);       // 報錯:Uncaught TypeError: Cannot read property '__proto__' of null
console.log(NaN.__proto__) ;       // Number {0, constructor: ƒ, toExponential: ƒ,  …}

構造函數創建對象,其中發生什麼?

1.創建了一個新對象
2.將新創建的對象的隱式原型指向其構造函數的顯式原型。
3.將this指向這個新對象
4.返回新對象

注意看第二條:將新創建的對象的隱式原型指向其構造函數的顯式原型
也就是說 對象.__prototype === 構造函數.prototype

function fn(){}
let obj = new fn();
console.log(obj.__proto__ === fn.prototype);  // true

那麼這樣,我們在爲構造函數添加原型方法時,就可以通過兩種方法取訪問了

fn.prototype.more = function(){console.log('fn-prototype-more')}

// 1、通過構造函數的顯式原型
fn.prototype.more()

// 2、通過對象的隱式原型
obj.__proto__.more()

原型鏈

原型鏈:實例與原型之間的鏈接

先來看一個例子:

function Abc(){}
Abc.prototype.fn = function(){console.log("Abc-prototype-fn")};
let obj = new Abc()
obj.fn() // Abc-prototype-fn

從上面的代碼我們可以看到。構造函數 Abc 和對象實例 obj 都沒有fn這個方法,之所以對象obj能夠訪問到Abc的原型方法,是通過原型鏈來尋找,借用了 _proto_ 這個屬性

圖片描述

所以以下的代碼也是等價的

obj.fn()             // Abc-prototype-fn
obj.__proto__.fn()   // Abc-prototype-fn

再來看一個複雜的例子:

function Abc(){
    this.fn = function(){console.log("Abc-fn")};
}
Abc.prototype.fn = function(){console.log("Abc-prototype-fn")};
Object.prototype.fn = function(){console.log("Object-fn")};

let obj = new Abc()
obj.fn = function(){console.log("obj-fn")};
obj.fn()

這裏有4個重名的fn函數,分別是:
①、實例對象obj的方法
②、構造函數Abc的方法
③、構造函數Abc原型上的方法
④、Obecjt對象原型上的方法

爲了表示方法的尋找過程,我畫了一幅很醜陋的圖,大家不要介意哈!

圖片描述

在尋找某個方法或者屬性的時候,會先從自身對象尋找;
如果沒有,則會去構造函數尋找;注意這裏還沒用到原型鏈;
緊接着,會到構造函數的原型上尋找,此時就是對象Abc通過 _proto_ 屬性進行尋找
接下來,會到Object對象的原型尋找,Object對象是所有對象之父,可以說所有的對象都繼承了Object,此時構造函數通過_proto_ 屬性找到了Object的prototype
最後的最後,由於Object._proto_ 指向了null,這也就是原型鏈的末端


第一道原型鏈是 obj._proto_ ,訪問到了Abc的prototype

console.log(obj.__proto__ === Abc.prototype)  // true

第二道原型鏈是 obj.__proto__.__proto__ ,訪問到了Object的prototype

console.log(obj.__proto__.__proto__ === Object.prototype) //true

第三道原型鏈是 obj.__proto__.__proto__.__proto__ 訪問到了Object的prototype.__proto__,,最後指向了null

console.log(obj.__proto__.__proto__.__proto__ === null) //true

這樣我們就可以看到了整個原型鏈的流程了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章