這樣理解原型與原型鏈比較簡單
原型
在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
*注意:
undefined和 null 同屬於物件,但是它們都沒有原型,為什麼? 在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
這樣我們就可以看到了整個原型鏈的流程了