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;
先寫到這裏…
下次再更…