JS原型及原型鏈的理解
在Java或C語言中,本身都會提供一個class實作,在ES5/ES6的時候JavaScript引入了class關鍵字,但這種只是語法糖的形式,JavaScript本身還是依賴于原型的,
說到原型與原型鏈,我們首先要知道的幾個點,
- JavaScript中只有一個結構:物件,每個實體物件都有一個私有屬性(proto),實體物件通過這個屬性可以訪問到它對應建構式的原型物件prototype
- 原型物件也是一個物件,所以它也有私有屬性(proto),可以訪問到它對應的建構式的原型物件prototype
- 函式(function)是允許擁有屬性的,所有的函式會有一個特別的屬性(prototype)
例子1(實體物件與建構式):
var a = new A();
此時a為A的實體物件,A為a的建構式
根據上面三點我們可以得出:
1、a有一個__proto__可以訪問到建構式A的原型物件
2、建構式有一個屬性prototype可以訪問到自己的原型物件
所以:a.__proto__ === A.prototype
例子2(建構式的prototype):
function Fa() {};
var Fb = function() {};
var Fc = new Function();
console.log(Fa.prototype);
console.log(Fb.prototype);
console.log(Fc.prototype);
上述代碼分別列印:

我們可以看到這三個函式都由自己的原型物件,并且這個原型物件里有一個construcor和*proto*屬性
- 首先:construcor屬性指向建構式,建構式通過prototype屬性訪問到自己的原型物件,原型物件通過construcor屬性訪問建構式,
- 其次:原型物件上也有__proto__屬性,那這個是怎么來的呢,開篇的時候,提到過,原型物件也是一個物件,可以理解為原型物件也是一個被一個建構式構造出來的實體,所以原型物件的__proto__屬性指向構造它的建構式的原型物件,這里需要提出第一個重點:
所有原型物件都是由Object函式物件構造出來的
值得注意的是:
- 上面列印出來的construct屬性第一個是可以看的很明顯是指向建構式Fa(){}的,那么下面兩種為什么沒有名字呢?,這里不得不提到函式創建的幾種方式和區別:
- 第一種創建時函式的宣告式,具名函式,
- 第二種是運算式,不具名,
- 第三種是構造式函式,不遵循典型作用域,會被一直當作頂級函式來執行,有興趣的伙伴可以多做了解,
例子三(實體的__proto__與建構式的prototype關系)
function Fa() {};
var Fb = function() {};
var Fc = new Function();
console.log(Fa.__proto__);
console.log(Fb.__proto__);
console.log(Fc.__proto__);
列印效果:

可以看到上述所有函式列印出來的都是一個函式,但是我們并不知道這個函式到底是誰,所以這里需要提出第二個重點:
- 所有的函式都是由Function函式物件創建的
所以此時我們把上述三個函式都當作是Function的實體物件,那么實體物件Fa Fb Fc的__proto__就應當指向它們的建構式Function的原型物件,也就是Function的prototype屬性所指向的地方,那么這兩個所指向的地址是一樣的,
- 結論:
Fa.__proto__ === Function.prototype;
Fb.__proto__ === Function.prototype;
Fc.__proto__ === Function.prototype;
毫無疑問的是,這三個都是true
代碼結果:

由例二中重點可以得出
Function.prototype.__proto__ === Object.prototype;
Function.__proto__.__proto__ === Object.prototype;
代碼結果:

重點三:
Function函式的prototype屬性可以訪問到Function函式物件的__proto__屬性
怎么理解這句話很重要,函式只有一個,但實體卻有很多個,Function頂層函式構造出這個Function函式物件實體,所以:
Function.__proto__ === Function.prototype
代碼結果:

所以
Function.prototype.__proto__ === Object.prototype; // true
Function.__proto__.__proto__ === Object.prototype; // true
Function.__proto__ === Function.prototype // true
例四(原型鏈):
var Fn = new Function();
Function.prototype.flag = '123';
Fn.__proto__ === Function.prototype; // true
Fn.__proto__.flag // '123'
Fn.flag // '123'
上述代碼很明顯理清了建構式與實體物件之間的繼承關系
- 訪問實體物件的某個屬性時,如果這個屬性沒有,就會去實體物件.__proto__上去找
我們在下面的代碼中再添加一層
var Fn = new Function();
var fn = new Fn();
Function.prototype.flag = '123';
Fn.prototype.flag = '456';
fn.__proto__ === Fn.prototype; // true
fn.__proto__.flag; // '456'
fn.flag; // '456'
Fn.__proto__ === Function.prototype; // true
Fn.__proto__.flag; // '123'
Fn.flag; // '123'
通過上述代碼就會幫助理解:
- 物件被誰構造,那么物件就會繼承誰原型上的屬性
此例中:
- fn由Fn構造,所以fn繼承Fn原型上的flag屬性,值為456
- Fn由Function構造,所以Fn繼承Function原型上的flag屬性,值為123
理解到這里的時候接下來再看一下的代碼:
例五(函式):
var Fn = new Function();
var fn = new Fn();
Function.prototype.flag = '123';
Fn.prototype.flag = '456';
Object.prototype.flag = '789';
fn.__proto__ === Fn.prototype; // true
fn.__proto__.flag; // '456'
fn.flag; // '456'
// 因為所有的原型物件都是由Object函式物件創建的
Fn.prototype.__proto__ === Object.prototype; //true
// 相當于
fn.__proto__.__proto__ === Object.prototype; //true
// 換句話說:
// fn這個實體通過fn.__proto__訪問到Fn.prototype的屬性
// Fn.prototype(也就是fn.__proto__)通過Fn.prototype.__proto__也就是fn.__proto__.__proto__可以訪問到Object.prototype的屬性
// 這就是所謂的繼承
Fn.__proto__ === Function.prototype; // true
Fn.__proto__.flag; // '123'
Fn.flag; // '123'
理解:
- 所有的原型物件都是由Object函式物件創建的
- fn這個實體通過fn.__proto__訪問到Fn.prototype的屬性
- Fn.prototype通過Fn.prototype.__proto__訪問到Object.prototype的屬性
- fn.__proto__通過fn.proto.__proto__訪問到Object.prototype的屬性
- 當把上例中Fn.prototype.flag = ‘456’;注釋,此時訪問fn.flag就會通過fn.proto.proto 或者 fn.proto 或者 fn都可以訪問到Object.prototype上的flag屬性,值為’789’
發現1(我自己的理解):
- Fn是一個函式物件
- 作為物件時,繼承的是來自Function.prototype屬性
- 作為函式時,本身有prototype屬性,繼承給構造出來的新物件
發現2:
- fn為Fn函式構造出來的物件
- fn首先會繼承來自Fn建構式的原型物件上的屬性,即:Fn.prototype
- Fn.prototype會繼承來自Object建構式的原型物件上的屬性,即:Object.prototype
- 所以fn會繼承Fn.prototype 以及 Object.prototype
- 繼承方式:
fn.__proto__ 對應 Fn.prototype
fn.__proto__.__proto__ 對應 Object.prototype
- 而Function的原型只會被函式物件繼承,不會被物件繼承
此例中只有Fn是一個函式物件,所以Fn會繼承Function.prototype的屬性flag ‘123’
而fn為一個物件,不是函式物件,不會繼承Function.prototype的屬性
Object.prototype呢?:
Object.prototype物件也是被構造的,
Object.prototype.__proto__ === null; // true
fn.__proto__.__proto__.__proto__ === null; // true
例六(陣列):
var arr = [];
// 陣列繼承于Array函式物件
arr.__proto__ === Array.prototype; // true
// Array原型物件繼承于Object函式物件
Array.prototype.__proto__ === Object.prototype; // true
arr.__proto__.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
arr.__proto__.__proto__ .__proto_ === null;; // true
Array函式物件繼承于Function函式物件
Fucntion.prototype.flag = '666';
Array.__proto__.flag; // '666'
Array.flag; // '666'
例七(物件):
var obj = {};
// 物件繼承于Object函式物件
obj.__proto__ === Object.prototype; // true
Object.prototype.__proto__ === null; // true
obj.__proto__ .__proto__ === null; // true
// Object函式物件由Function函式創建的
Object.__proto__ === Function.prototype; //true
Function.prototype.flag; // '666'
Object.__proto__.flag; // '666'
Object.flag; // '666'
總結:
- 所有的函式包括Object函式物件到最后的頂層都繼承到了Function.prototype;
Object.__proto__ === Function.prototype;
- 所有的原型物件包括Function的原型物件到最后的頂層都繼承到了Object.prototype;
Function.prototype.__proto__ === Object.prototype;
先寫到這里…
下次再更…
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/255916.html
標籤:其他
上一篇:2021前端性能優化手段總結
