前言
原型是javascript繼承的基礎,理解javascript原型有助於咱們學習好這門語言。關於javascript的原型知識的學習,建議你們仍是好好看看javascript的紅寶書《javascript高級程序設計》,這是javascript界有名的大神Nicholas C.Zakas寫的,正如React核心成員及Redux的創造者Dan Abramov所說的,Nicholas 對javascript的瞭解程度不多有人可以企及,好吧,反正經典的東西是不會過期的,每次讀都能收益很多的。javascript
本文旨在經過instanceof這個運算符的實現原原理來簡單梳理一下javascript的原型知識。
java
咱們知道,javascript有兩種數據類型:基本類型和對象(Object)。基本類型有六種:string,boolean,null,undefined,number,symbol(ES6新增)
數組
咱們要檢測基本類型,用typeof運算符:
瀏覽器
var str = "abc",
num = 123,
flag = true,
symblo = Symbol(),
a = null,
b = undefined; //測試說明用,實際不建議這樣定義
typeof str // "string"
typeof num // "number"
typeof flag // "boolean"
typeof symblo // "symbol"
typeof a // "object"
typeof b // "undefined"複製代碼
咱們看到,除了null外其他的都能正確返回其類型,對於null,typeof 運算符返回了object類型,雖然null是屬於基本類型,這是一個由來已久的bug了。另外對於Object類型,除了函數,typeof都會返回 「object」,函數返回 「function」。bash
var foo = function(){};
var obj = {};
var arr = [];
typeof foo; //"function"
typeof obj; // "object"
typeof arr; // "object"複製代碼
咱們看到,對於咱們定義的arr數組,typeof也返回了 「object」,這是由於數組Array在javascript中也是對象。那麼,咱們如何檢測出咱們想要的數組類型呢?函數
答案就是instanceof運算符了。(固然還有其它方法,本文只討論instanceof)
學習
arr instanceof Array //true複製代碼
instanceof 返回一個布爾類型的值,它的語法結構爲
測試
object instanceof constructor複製代碼
它能夠正確的判斷出對象的包裝類型,上面咱們看到,用instanceof操做arr,返回了true,符合咱們的預期。
ui
咱們來看一下MDN上對instanceof運算符的解釋:
this
instanceof運算符用於測試構造函數的prototype屬性是否出如今對象的原型鏈中的任何位置
因此instanceof其實是經過判斷一個對象的原型鏈中是否能找到該對象的構造函數的prototype來實現的,知道了內部基本實現原理,咱們就能夠本身模擬實現一個instanceof功能的函數了。
function _instanceof(left, right) {//left表示object,right表示constructor
var prototype= right.prototype, //獲取構造函數的原型prototype proto = left.__proto__; // 獲取對象的原型__proto__
// 判斷對象的類型是否等於構造類型的原型 while (true) {
if (proto === null){//找到了原型鏈的頂層沒有找到,返回false return false
}
if (proto === prototype){ //找到了就返回true
return true
}
proto = proto.__proto__ // 繼續返回上一層查找 } }
}複製代碼
這樣咱們就基本實現了一個instanceof了, 來檢測一下:
_instanceof(arr,Array) // true
_instanceof(str,Array) // false複製代碼
上面咱們定義的arr判斷是不是一個數組,返回了true,字符串str返回了false,符合預期。
從上面instanceof運算符的實現原理中咱們看到,若是一個對象的__proto__等於其構造器的prototype,那麼這個對象就是這個構造器的實例對象。因此上面定義的arr,實際上就是數組Array構造器new出來的一個實例對象。
var arr = new Array()
arr.__proto__ === Array.prototype //true複製代碼
在這裏說一下__proto__和 prototype的關係。
__proto__和prototype都指向一個對象的原型,__proto__是javascript對象都會有的屬性,而prototype是函數纔有的屬性,可是別忘了,在javascript中函數是一等公民,函數也是對象。函數既然是對象那麼它必然也有__proto__屬性,那麼函數的__proto__指向誰呢?
var foo = function(){}
console.log(foo.__proto__) //ƒ () { [native code] }複製代碼
在瀏覽器控制檯打印出來的是一個匿名函數,這個匿名函數是什麼呢?
咱們知道,在javascript中沒有類的概念(ES6中新增了class關鍵字,用來模擬類的繼承語法,但其本質上是ES5傳統繼承模型的一種語法糖),要實現類繼承須要經過構造函數和原型組合,換一種說法,就是用函數來模擬類
function Person(name){
this.name = name
}
var p = new Person("Paul")
p.name //Paul
p.__proto__ === Person.prototype //true
複製代碼
Person函數相較於foo函數,多了一個new 調用,它能夠當作是一個「類」
因此到這裏你應該帶着出一個疑問:Person.__proto__等於什麼呢?
console.log(Person.__proto__) //ƒ () { [native code] }複製代碼
看到了吧,foo.__proto__和Person.__proto__都指向一個特殊的匿名函數,那麼這個匿名函數究竟是什麼?
還記得嗎,javascript中建立函數的方式除了函數聲明和函數表達式,還有一個 new Function()的定義函數方式:
var fun = new Function()
typeof fun //"function"
fun.__proto__ === Function.prototype //true複製代碼
到這裏你應該明白上面的匿名函數是什麼了吧?其實foo函數和Person函數都是Function類的一個實例,這裏也則面說明了函數也是對象的概念,由於函數都是由Function這個類new出來的啊~
foo.__proto__ === Function.prototype //true
Person.__proto__ === Function.prototype //true複製代碼
到這裏你應該會好奇,Function.__proto__又等於什麼呢?答案是:
等於它本身的prototype
Function.__proto__ === Function.prototype //true
複製代碼
這是比較特殊的地方,但若是你從javascript一切皆對象的角度出發,彷佛也不難理解:Function也是對象啊~只不過Function是比較特殊的一個存在,它是由javascript引擎初始化的。
Function和Object的關係
咱們知道,javascript一切對象皆繼
承自Object這個類,因此有:
var obj = new Object()
obj.__proto__ === Object.prototype //true
Function.prototype.__proto__ === Object.prototype //true複製代碼
到這裏你可能又疑惑了,Object.__proto__又等於什麼呢?
console.log(Object.__proto__) // ƒ () { [native code] }
複製代碼
在控制檯中輸出咱們看到,Object.__proto__與上面的foo和Person函數同樣,等於一個特殊的匿名函數,因此:
Object.__proto__ === Function.prototype //true
複製代碼
到這裏你可能會蒙圈了,好吧,其實Object也是一個構造器,其實它和Function是同樣的,都是由javascript引擎初始化的,而後經過__proto__鏈接起來了。那麼Function和Object是誰從屬於誰呢?這個實際上是先有雞仍是先有蛋的問題,網上有專門介紹這二者關係的文章,有興趣的能夠去看看。這裏來總結一下Function和Objcet的關係:
1. 全部構造器(包括Object, Array,String,Number,Boolean,Symbol)
的__proto__都指向Function這個類的prototype;
2. 全部對象的__proto__最終都會指向Object的prototype.
你也許還有疑惑,原型鏈的終點在哪?是null
Object.prototype.__proto__ === null //true複製代碼
null的本質是空的對象引用,別忘了,原型自己也是對象,原型也有它本身的原型,這就構成了原型鏈的概念,因此把null做爲原型鏈的終點也是合理的,由於原型再往上查找已經沒有引用了,到null這裏就是終點了。