巧解 JS 原型鏈

前言:

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

構造函數:

什麼是構造函數?css

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..... ,都有相同的 屬性和方法 ;html

判斷一個對象的構造函數:

就像上面例子同樣,咱們怎麼找到,一個實例對象(robotA,robotB, robotC)的構造函數呢???java

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構造函數 又是誰幫咱們生成的呢???用上述辦法試試:bash

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 函數又是誰構造的呢 ???ui

咱們先把這些疑惑先放放,我後面會解釋,在這以前,咱們先看看,JS 內置的像 Function 同樣的函數有哪些?this

JS 內置的構造函數:

聰明的讀者,想必都已經想到了,咱們聲明一個數字、字符串等等內置類型都 可能 有構造函數!!!spa

// 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關鍵字聲明一個字符串、數字、布爾值等等,所有都是由瀏覽器解析而後交給內置函數處理!

函數對象和普通對象

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

// 如何區分,函數對象和普通對象???

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

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

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

複製代碼

結論:普通對象的構造函數是 Object() , 函數對象的構造函數是 Function();

原型的本質:

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

函數對象與普通對象的區別:

函數對象和普通對象是有區別的,那咱們怎麼去分辨這種區別呢???

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 中的對象我想更爲合適;

首先,咱們所聲明的每個變量都是會佔用計算機資源的,它們被保存在計算機內存的某個位置;

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

如圖,所示,引用,就是 「外號」 ,一我的能夠有不少種外號,不管叫你哪個外號,都是指你自己!!!

這是如何體如今代碼裏面呢?

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

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

那麼,小明是否是小紅呢?小明和小紅你們都很熟悉了,不是一我的;

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 函數的 原型 ;

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

若是,你須要 new 1000 個對象,這 1000 個對象就會分配 1000 個對象的存儲空間;

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

咱們把相同的屬性和方法放在一塊兒,抽象爲一個原型,這 1000 個對象都訪問原型上的 共有方法

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

談談原型鏈:

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

原型鏈,確定跟原型有關啦,那這個關係究竟是什麼?

理解 __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 起點原型,它也是一個普通對象!!!;

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

如今,來 函數對象 啦,仔細觀察,此過程,比單純的 普通對象 要複雜!!!

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 又到頭啦!!!

複製代碼

若是,你理解了這段代碼,也就理解了,爲何說,函數對象也是對象這句話了。

如今,咱們來模擬一下 ,從 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 普通對象沒有原型!!!

複製代碼

其他的內容留給讀者本身去思考。試着想想,可否畫出整個內置函數對象的原型鏈關係圖

重要參考和感謝:

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

在此對上面連接或者書籍的做者表示感謝和支持,歡迎你們提出問題!!!

相關文章
相關標籤/搜索