本文章帶有強烈的我的風格主義,我以本身的方式理解原型鏈,還請各位路過的大佬們,多多指點,有啥不懂直接提出來,你們多多交流。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
聰明的讀者,想必都已經想到了,咱們聲明一個數字、字符串等等內置類型都 可能 有構造函數!!!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函數!!!
複製代碼
如今,咱們清楚了:
Function
來構造!!!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
複製代碼
仔細想一想,robotA
、robotB
、robotC
,這是分別是三個對象的 引用(別名);
計算機爲這三個對象,分配了,三個存儲空間,咱們經過引用,找到這個存儲空間的位置,而後執行!!!
而 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 普通對象沒有原型!!!
複製代碼
其他的內容留給讀者本身去思考。試着想想,可否畫出整個內置函數對象的原型鏈關係圖
感受不錯,點個贊再走唄!!!
在此對上面連接或者書籍的做者表示感謝和支持,歡迎你們提出問題!!!