JS原型及原型鏈的理解

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'

通過上述代碼就會幫助理解:

  • 對象被誰構造,那麼對象就會繼承誰原型上的屬性

此例中:

  1. fn由Fn構造,所以fn繼承Fn原型上的flag屬性,值爲456
  2. 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'

理解:

  1. 所有的原型對象都是由Object函數對象創建的
  2. fn這個實例通過fn.__proto__訪問到Fn.prototype的屬性
  3. Fn.prototype通過Fn.prototype.__proto__訪問到Object.prototype的屬性
  4. fn.__proto__通過fn.proto.__proto__訪問到Object.prototype的屬性
  5. 當把上例中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;

先寫到這裏…
下次再更…

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