巧解 JS 原型鏈

leiting1998發表於2018-03-29

巧解 JS 原型鏈

前言:

本文章帶有強烈的個人風格主義,我以自己的方式理解原型鏈,還請各位路過的大佬們,多多指點,有啥不懂直接提出來,大家多多交流。

建構函式:

什麼是建構函式?

var afunc = function (name) {		// afunc就是建構函式!!!
  this.name = name
  
  this.get = function() {
    return this.name;
  };
}

var robotA = new afunc('html');

console.log( robotA.get() );					// "html"

var robotB = new afunc('css');

console.log( robotB.get() );					// "css"

var robotC = new afunc('javascript');

console.log( robotC.get() );					// "javascript"

複製程式碼

建構函式像一個克隆機器,它能夠源源不斷地克隆相同的東西,機器人A、機器人B..... ,都有相同的 屬性和方法 ;

判斷一個物件的建構函式:

就像上面例子一樣,我們怎麼找到,一個例項物件(robotA,robotB, robotC)的建構函式呢???

var afunc = function (name) {		// afunc就是建構函式!!!
  this.name = name
  
  this.get = function() {
    return this.name;
  };
}

var robotA = new afunc('html');

console.log(robotA.constructor);	// 函式 afunc
複製程式碼

辦法很簡單,通過 例項物件(robotA)的 constructor 屬性,就可以找到此例項物件的建構函式啦!!!

那大家有沒有考慮過一個問題,我們宣告的 afunc建構函式 又是誰幫我們生成的呢???用上述辦法試試:

var afunc = function (name) {		// afunc就是建構函式!!!
  this.name = name
  
  this.get = function() {
    return this.name;
  };
}

var robotA = new afunc('html');

console.log(afunc.constructor);		// function Function() {  }
複製程式碼

很顯然,我們宣告的建構函式 afun 是由另一個建構函式 Function 生成的!!!

讀到這裡,想必你肯定很疑惑,那麼 這個 Function 函式又是誰構造的呢 ???

我們先把這些疑惑先放放,我後面會解釋,在這之前,我們先看看,JS 內建的像 Function 一樣的函式有哪些?

巧解 JS 原型鏈

JS 內建的建構函式:

聰明的讀者,想必都已經想到了,我們宣告一個數字、字串等等內建型別都 可能 有建構函式!!!

// 7種內建型別依次試試,不知道哪7種的同學可以看看我另一篇文章 《JS靈巧判斷7種型別的方式》

/*-----------------顯然兩個貨沒有建構函式---------------------*/
console.log(null)				   // null

console.log(undefined)			   // undefined

/*-----------------七種型別中的其他 5 種型別--------------------*/

console.log(Number);			   // ƒ Number() {  }

console.log(String);                // ƒ String() {  }

console.log(Object);			   // ƒ Object() {  }

console.log(Boolean);               // ƒ Boolean() {  }

console.log(Symbol);                // ƒ Symbol() {  }

--------------------7種型別之外的內建建構函式---------------------*/

console.log(Date);				   // ƒ Date() {  }

console.log(RegExp);			   // ƒ RegExp() {  }

/*--------------------注意:Math不是建構函式而是一個物件------------*/

console.log(Math);				   // [object Math] { ... }
複製程式碼

大概就列出來這麼些 內建函式 吧,那我們要怎樣使用這些建構函式呢???

當我們宣告一個字串、數字時,這些函式就會開始工作,證明這個真理:

var n = 123;				       // 當你寫的 123 會被瀏覽器用 Number() 內建函式構造出來;

console.log(n.constructor);			// ƒ Number() { }

var s = 'javascript';

console.log(s.constructor);			// f String() { }

// 懶,其他我就省略啦 ......

// 其實你也可以用 new 關鍵字宣告一個 數字、字元......,不過用這種方法的人該多蠢!!!

var n = new Number(123);

var s = new String('javascript');

console.log(typeof n);			// "object"

console.log(typeof s);			// "obejct"

console.log(Number(n));			// w3c 上是通過強制轉換來獲取 123 這個值的,

複製程式碼

你可以很清晰地看到,其實,我們的這裡個 內建函式 跟我們宣告的 afunc 沒有區別,都是同樣的操作!!!

現在,我們再來看一個有趣的現象,我們這些內建函式的建構函式又是誰???

Number.constructor	 // ƒ Function() {  }  構造者是 Function 函式

String.constructor	 // ƒ Function() {  }  構造者是 Function 函式

Object.constructor	 // ƒ Function() {  }  構造者是 Function 函式

Boolean.constructor	 // ƒ Function() {  }  構造者是 Function 函式

Symbol.constructor	 // ƒ Function() {  }  構造者是 Function 函式

Date.constructor	 // ƒ Function() {  }  構造者是 Function 函式

RegExp.constructor	 // ƒ Function() {  }  構造者是 Function 函式


Math.constructor	 // ƒ Object() {  }  注意:Math已經不是一個函式了,構造者是一個Object函式!!!
複製程式碼

現在,我們清楚了:

  1. 我們每宣告一個函式,瀏覽器解析到這個函式是會交給內建函式 Function 來構造!!!
  2. 再看看 Math 物件,你也已經可能猜到,物件是由 f Object() 函式所構造的!!!

相類似,我們用 var關鍵字宣告一個字串、數字、布林值等等,全部都是由瀏覽器解析然後交給內建函式處理!

巧解 JS 原型鏈

函式物件和普通物件

估計看到標題你就傻了吧,函式物件是什麼鬼?其實,函式的本質也是一個物件,只不過函式的能力很強大罷了!

// 如何區分,函式物件和普通物件???

var o = {};    
    
var f = function(){ };

console.log( o.constructor );			// ƒ Object() { }

console.log( f.constructor );			// ƒ Function() { }

複製程式碼

結論:普通物件的建構函式是 Object() , 函式物件的建構函式是 Function();

巧解 JS 原型鏈

原型的本質:

本篇文章是講原型,前面是基礎知識,現在終於到正餐啦!!!

函式物件與普通物件的區別:

函式物件和普通物件是有區別的,那我們怎麼去分辨這種區別呢???

var o = {};  			       // object(普通物件) 

console.log( o.prototype );     // undefined

console.log( typeof o );		// "obejct"


/*----------------------我是你的分割線-------------------------*/


var f = function(){};           // function(函式物件)

console.log( f.prototype );     // {......}

console.log( typof f );		   // "function"

console.log( typof f.prototype );   // "object" 原型是一個普通物件
複製程式碼

結論:

1. 函式物件的型別是 function,普通物件的型別是 object!!!

2. 函式物件 有 原型 ( prototype ),普通物件 沒有 原型 prototype 的!!!

每宣告一個函式,就會有一個產生一個原型,這個原型的 引用 就儲存在 函式物件的 func.prototype 上面;

為什麼要用原型?

回答這個問題前,還需要一點前置知識 。。。。。。

理解什麼是 引用:

引用的概念出自於 C++ ,引用 的實質是 指標,不過用 引用 來思考 javascript 中的物件我想更為合適;

首先,我們所宣告的每一個變數都是會佔用計算機資源的,它們被儲存在計算機記憶體的某個位置;

引用也會佔據空間,小到一個小小的數字,大到一個巨大的物件:

巧解 JS 原型鏈

如圖,所示,引用,就是 “外號” ,一個人可以有很多種外號,無論叫你哪一個外號,都是指你本身!!!

這是如何體現在程式碼裡面呢?

小明,有兩個外號,狗蛋 和 嚶嚶嚶;

小紅,有兩個外號,二狗 和 哈哈哈;

那麼,小明是不是小紅呢?小明和小紅大家都很熟悉了,不是一個人;

var xiaoming = {name: '小明'};	// 我們用小明代指這個物件

var goudan = xiaoming;  // 小明 有一個外號叫做 狗蛋

var yingyingying = xiaoming;    // 小明 有一個外號叫做 嚶嚶嚶

console.log( goudan === xiaoming ); // true 狗蛋 是 小明

console.log( yingyingying === xiaoming );   // true 嚶嚶嚶 是 小明

/*------------我是分割線-----------------*/

var xiaohong = {name: '小紅'};	// 我們用小紅代指這個物件

var ergou = xiaohong;	  // 小紅 有一個外號叫做 二狗

var hahaha = xiaohong;     // 小紅 有一個外號叫做 哈哈哈

console.log( ergou === xiaohong );	 // true 二狗 是 小紅

console.log( hahaha === xiaohong );	 // true 哈哈哈 是 小紅

/*----------------我是分割線---------------------*/

console.log( xiaohong === xiaoming );	// false 小明 不是 小紅 <= 這個例子雖然無聊但是有用
複製程式碼

很顯然的,我們用 var 宣告的變數就叫做 引用(別名) 我們通過這個 引用 來找到這個物件,在計算機裡面的位置。

初次認識屬性 __proto__ :

在回答為 什麼要用原型 這個問題時,我們還需要明白一個屬性 __proto__

我這裡先不闡述 __proto__ 屬性具體的定義和概念,以免大家思維混亂 。。。。。。

var afunc = function (name) {		// afunc就是建構函式!!!
  this.name = name
  
  this.get = function() {
    return this.name;
  };
}

afunc.prototype = {			
  set: function(newName) {
    this.name = newName;
  }
}

var robotA = new afunc('html');

console.log( robotA.prototype );	 // undefined 前面文章已經解釋了 普通物件沒有 原型

console.log( robotA.__proto__ );	 // {set: function() {...}} 找到了原型。

robotA.set('css');

console.log(robotA.get());			// "css"
複製程式碼

現在你能夠理解,robotA.__proto__afunc.prototype 都僅僅是 afunc 的原型的 引用 了嗎???

如果,不理解,再仔細想想,思考一下,在之後的內容裡,我會展示 __proto__ 更為詳細的講解;

當我們呼叫 robotA.get() 方法時,JS 會先去 建構函式 裡面找是否有此方法,沒有的話就到 原型 裡面找!!!

回答為什麼要用原型:

我們明白了什麼是引用,就非常好解釋,為什麼需要用原型。

var afunc = function (name) {		// afunc就是建構函式!!!
  this.name = name
  
  this.get = function() {
    return this.name;
  };
}

afunc.prototype = {		   // 呼叫原型物件
  set: function(newName) {
    this.name = newName;
  }
}

var robotA = new afunc('html');

var robotB = new afunc('css');

var robotC = new afunc('javascript');

robotA === robotB			// false 這兩在計算機中的物件 是不一樣

robotA === robotC			// false 

robotB === robotC			// false 

robotA.__proto__ === robotB.__proto__		// true 這兩個引用所指的物件 是相同的

robotA.__proto__ === robotC.__proto__		// true

robotB.__proto__ === robotC.__proto__		// true

複製程式碼

仔細想想,robotArobotBrobotC ,這是分別是三個物件的 引用(別名)

計算機為這三個物件,分配了,三個儲存空間,我們通過引用,找到這個儲存空間的位置,然後執行!!!

robotA.__proto__robotB.__proto__robotC.__proto__ 也是三個引用(別名)

但是,這上述三個引用指向的內容,都是 afunc 函式的 原型 ,計算機裡只儲存唯一的一個 afunc 函式的 原型 ;

巧解 JS 原型鏈

這樣的原型有什麼好處???

如果,你需要 new 1000 個物件,這 1000 個物件就會分配 1000 個物件的儲存空間;

試想,每次 new 相同的 屬性和方法 ,這樣系統的開銷就會變得非常大!!!

我們把相同的屬性和方法放在一起,抽象為一個原型,這 1000 個物件都訪問原型上的 共有方法

這樣豈不是,美滋滋!!!

巧解 JS 原型鏈

談談原型鏈:

終於到這一步了,感覺手都寫酸了。。。。。

原型鏈,肯定跟原型有關啦,那這個關係到底是什麼?

理解 __proto__ 的本質:

每一個物件,不管是函式物件或者普通物件,都會有 __proto__ 屬性。

但是,這個屬性已經 不推薦使用 了,但是為了展示什麼是 原型鏈,我用了它。

如果想了解原因:請狠狠地戳這裡,瞭解ES6標準;

var o = { };

console.log( o.__proto__ );	// {......} 生成 o 物件的原型;

console.log( o.__proto__.constructor );	// 生成 o 物件的建構函式 f Object() { };

console.log( o.__proto__.__proto__ );	// null; 到頭啦,最初的起點 null !!!

複製程式碼

關於 {......} 代表的是 object 起點原型,它也是一個普通物件!!!;

仔細思考,這兩段程式碼,我不在這裡闡述過多的東西,其實這裡很繞的;

巧解 JS 原型鏈

現在,來 函式物件 啦,仔細觀察,此過程,比單純的 普通物件 要複雜!!!

var f = function() { };		

console.log( f.__proto__ );	// ƒ () {  } 生成 f 函式物件的原型

console.log( f.__proto__.constructor );	// 生成 f 函式物件的建構函式 f Function() { }

console.log( f.__proto__.__proto__ );	// {......} 生成 f 函式物件的原型的原型 

console.log( f.__proto__.__proto__.constructor );	// ƒ Object() { } 

console.log( f.__proto__.__proto__.__proto__ );	  // null 又到頭啦!!!

複製程式碼

如果,你理解了這段程式碼,也就理解了,為什麼說,函式物件也是物件這句話了。

巧解 JS 原型鏈

現在,我們來模擬一下 ,從 new 一個物件,開始的原型鏈過程 !!!

var f = function() { };		

f.prototype = {
  name: "javascript"
}

var obj = new f();

console.log(obj.__proto__);				// { name: "javascript" } 構造 obj 普通物件的 原型
 
console.log(obj.__proto__.__proto__);	  // { ...... } 構造 obj 普通物件原型的 原型

console.log(obj.__proto__.__proto__.__proto__);	  // null 到頭啦!!!

/*------------這是你需要區分的東西----------------*/

console.log(f.prototype);				// f 函式物件的原型

console.log(obj.prototype);				// undefined 普通物件沒有原型!!!

複製程式碼

巧解 JS 原型鏈

其餘的內容留給讀者自己去思考。試著想一想,能否畫出整個內建函式物件的原型鏈關係圖

重要參考和感謝:

感覺不錯,點個贊再走唄!!!

在此對上面連結或者書籍的作者表示感謝和支援,歡迎大家提出問題!!!

相關文章