在前端這塊領域,原型與原型鏈是每個前端er必須掌握的概念。咱們屢次在面試或者一些技術博客裏面看見這個概念。因而可知,這個玩意對於前端來講有多重要。其實它自己理解起來不難,可是不少剛入行前端的同窗,看到prototype
、__proto__
理解起來仍是有點吃力,而後腦子裏面就亂成一鍋粥,就像我同樣。可是這是很正常的事情,沒什麼大不了的,就像咱們想要學會跑步,那麼咱們就必須先學會走路。任何事情都是有個過程的。因此如今就跟我一塊兒來攻克這個難點吧。經過這篇文章你將掌握如下知識點:javascript
__proto_
;prototype
;javascript
中對象
的概念;javascript
中類
的概念;new
的實現;instanceof
的實現;javascript
的繼承;javascript
這門語言的理解。這也是本篇文章的寫做思路。前端
對象
那麼咱們就從對象這一律念開始提及,其實對象這一律念相信你們並不陌生。有一種說法是「javasrcript中萬物皆是對象」,其實這個說法是錯誤的,一個很簡單的例子,javasript
中簡單基本類型(string、boolean、number、null、undefined、symbol)自己就不是對象。其實javasript
中對象主要分爲函數對象
和普通對象
。其中:java
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
這些都是函數對象,他們同時也被稱爲內置對象
。函數對象
自己其實就是一個純函數,javascript
用他們來模擬類
。普通對象就很簡單了,就是咱們常見的對象:面試
const obj = {
name: 'juefei',
desc: 'cool'
}
複製代碼
可能說到這,你仍是沒法理解到底啥是函數對象
,啥是普通對象
,那咱們就一塊兒來看看下面的代碼:函數
const obj1 = {};
const obj2 = new Object();
function func1() {
}
const obj3 = new func1();
const func2 = new function() {
}
const func3 = new Function()
複製代碼
接着咱們來分別打印一下他們:學習
console.log(obj1); // object
console.log(obj2); // object
console.log(obj3); // object
console.log(func1); // function
console.log(func2); // function
console.log(func3); // function
複製代碼
因此能夠看見,obj1
、obj2
、,obj3
是普通對象,他們都是Object
的實例,而func1
、func2
、func3
則都是Function
的實例,稱爲函數對象
。咱們再看看:ui
console.log(typeof Object); //f unction
console.log(typeof Function); // function
複製代碼
你是否是驚呆了,原來Object
和Function
都是 Function
的實例。 因此咱們得出一個結論就是:this
Function
的實例,那就是函數對象
,其他則爲普通對象
。一樣咱們也能夠看出,不只 Object
是函數對象
,就連 Function
自己也是函數對象,由於咱們經過 console.log(typeof Function);
得知 Function
是 Function
的實例。是否是又開始有點繞了?沒事,到這一步你就記住咱們剛剛的結論就算完成目標:spa
Function
的實例,那就是函數對象
,其他則爲普通對象
。那麼說到對象,咱們從上面能夠看出,一個對象是經過構造函數 new 出來的,這其實跟原型
和原型鏈
有很大的關係,那麼原型
和原型鏈
究竟是用來幹嗎的呢?prototype
原型
涉及到這兩個概念,咱們就必須先來介紹兩個東西: __proto__
和 prototype
,這兩個變量能夠說,在 javascript
這門語言裏面隨處可見,咱們無論他三七二十一,咱們先來看一張表:
對象類型 | __proto__ |
prototype |
---|---|---|
普通對象 | ✅ | ❌ |
函數對象 | ✅ | ✅ |
因此,請你先記住如下結論:
只有函數對象
有 prototype
屬性,普通對象
沒有這個屬性。
函數對象
和 普通對象
都有 __proto__
這個屬性。
prototype
和 __proto__
都是在建立一個函數或者對象會自動生成的屬性。
接着咱們來驗證一下:
function func (){ //func稱爲構造函數
}
console.log( typeof func.prototype); // object
console.log(typeof func.__proto__); // function
複製代碼
const obj = {}
console.log(typeof obj.__proto__) //object
console.log(typeof obj.prototype) //undefined (看見了吧,普通對象真的沒有 prototype 屬性)
複製代碼
因此就驗證了咱們剛剛的結論:
函數對象
有 prototype
屬性,普通對象
沒有這個屬性函數對象
和 普通對象
都有 __proto__
這個屬性。prototype
和 __proto__
都是在建立一個函數或者對象會自動生成的屬性。你看我又重複寫了一遍,我不是爲了湊字數,是爲了你加深記憶,這對於咱們接下來的篇幅很重要。 接着咱們來看看下面的代碼:
console.log(obj.__proto__ === Object.prototype); // true
console.log(func.__proto__ === Function.prototype); // true
複製代碼
因此咱們又得出以下結論:
__proto__
屬性主動指向構造的 prototype
;prototype
屬性被 __proto__
屬性 所指向。這就是prototype
屬性和 __proto__
屬性的區別與聯繫。 這可能又有點繞了,來多看幾遍這一節,多背一下咱們的結論。咱們繼續。
那麼問題來了,既然func
是一個函數對象
,函數對象是有 prototype
屬性的,那麼func.prototype.__proto__
等於啥呢?
爲了解決這個問題,咱們來思考一下:
首先,咱們看看func.prototype
是啥:
console.log(typeof func.prototype); //object
複製代碼
好,咱們知道了,func.prototype
是一個對象,那既然是對象,那 func.prototype
那不就是 Object
的實例嗎?那也就是說,func.prototype.__proto__
屬性確定是指向 Object.prototype
咯! 好,咱們來驗證一下:
console.log(func.prototype.__proto__ === Object.prototype); //true
複製代碼
看見沒有,就是這樣的。那看到這裏,咱們應該也知道當咱們這建立一個構造函數的時候,javascript是如何幫咱們自動生成__proto__
和prototype
屬性的。哈哈沒錯就是這樣:
//咱們手動建立func函數
function func() {}
//javascript悄悄咪咪執行如下代碼:
func._proto = Function.prototype; //實例的 __proto__ 屬性主動指向構造的 prototype
func.prototype = {
constructor: func,
__proto: Object.prototype //咱們剛剛纔在上面驗證的,你別又忘記了
}
複製代碼
我還專門爲你畫了個圖(夠貼心不老鐵):
因此prototype
又被稱爲顯式原型對象,而__proto__
又被稱爲隱式原型對象。
hi,看到這裏,你是否是有種腦子開了光的感受。哈哈,因此到如今你應該已經理解原型的概念了,若是你還不理解,那就把上述章節再看一遍。最好拿個紙筆出來跟着畫一畫,順便拿出電腦把示例代碼敲一敲。好,整理一下頭腦,接下來咱們來看看什麼又是原型鏈
。
原型鏈
再介紹這個概念以前,咱們先來看以下代碼:
function Person = function(name,desc){
this.name = name;
this.desc = desc;
} //***1****//
Person.prototype.getName = function(){
return this.name;
}//***2****//
Person.prototype.getDesc = function(){
return this.desc;
}//***3****//
const obj = new Person('juefei','cool');//***4****//
console.log(obj);//***5****//
console.log(obj.getName);//***6****//
複製代碼
接下來咱們來逐步解析一下:
Person
,此時,Person.portotype
自動建立,其中包含了 constructor
和 __proto__
兩個屬性;Person.prototype
新增了一個方法 getName
;Person.prototype
新增了一個方法 getDesc
;Person
新建一個實例: obj
(在建立實例的時候,構造函數會自動執行);obj
:{
name: 'juefei',
desc: 'cool'
}
複製代碼
根據上面一節的結論,咱們得出:
obj.__proto__ = Person.prototype;
複製代碼
obj
上面找不到 getName()
這個方法,因此它就會自動去經過自身的 __proto__
繼續向上查找,結果找到了 Person.prototype
,接着它發現,恰好 Person.prototype
上面有getName()
方法,因而找到了這個方法,它就中止了尋找。 怎麼樣,是否是有一種環環相扣的感受?他們造成一個鏈了,沒錯,這就是原型鏈
。咱們得出以下結論:
在訪問一個對象(假設這個對象叫obj)的屬性/方法時,若在當前的對象上面找不到,則會嘗試經過obj.__proto__
去尋找,而 obj.__proto__
又指向其構造函數(假設叫objCreated
)的 prototype
,因此它又自動去 objCreated.prototype
的屬性/方法上面去找,結果仍是沒找到,那麼就訪問 objCreated.prototype.__proto__
繼續往上面尋找,直到找到,則中止對原型鏈對尋找,若最終仍是沒能找到,則返回 undefined
。 一直沿着原型鏈尋找下去,直到找到 Object.prototype.__proto__
,指向 null
,因而返回 undefined
了。
是否是天然而然就理解了。我又給你畫了個圖(請對照着上面👆那個圖看):
接下來咱們再來增長一些概念:
內置函數對象
自己的 __proto__
屬性都指向 Function
的原型對象,即: Function.prototype
;Object.prototype.__proto__
指向 null
,全部的內置函數對象
的原型對象的 __proto__
屬性 ( 內置函數對象.prototype.__proto__
),都指向Object
。咱們得出以下終極原型鏈的圖:
針對這個圖,我最終給出咱們常常看見那個原型鏈的圖:
好好對比一下,拿出紙和筆畫一畫,根據上面章節的講解,相信你很容易就能明白。
javascript
中的類
剛剛咱們終於明白什麼是 原型
和 原型鏈
。下面咱們根據上面的概念來說解一下javascript
中的類
。 咱們知道,在面向對象的語言中,類能夠被實例化
屢次,這個實例化
是指咱們能夠根據構造函數去獨立複製
多個獨立的實例,這些實例之間是獨立的。可是實際上在 javascript
卻不是這樣的,由於它不是這種複製機制
。咱們不能建立一個類的多個實例,咱們只能建立這個類的多個對象,由於他們都是經過原型
和原型鏈
關聯到同一個對象。因此在 javascript
中 ,類
都是經過原型
和原型鏈
來實現的,它實際上是一種委託方式
。
new
的實現瞭解了上面javascript
中的類
的概念,那咱們應該很容易就理解new
的過程,其核心無非就是執行原型鏈的連接:
function myNew(Cons,...args){
let obj = {};
obj.__proto__ = Cons.prototype; //執行原型連接
let res = Cons.call(obj,args);
return typeof res === 'object' ? res : obj;
}
複製代碼
instanceof
的實現那麼學習了原型
和原型鏈
,instanceof
的實現確定也很簡單了,它也是經過原型
和原型鏈
來實現的:
function myInstanceof(left,right){
let rightProto = right.prototype;
let leftValue = left.__proto__;
while(true){
if(leftValue === null){
return false;
}
if(leftValue === rightProto){
return true;
}
leftValue = leftValue.__proto__;
}
}
複製代碼
我就不講解過程了,由於我知道你確定能看懂,哈哈。
javascript
的繼承咱們都知道繼承也是經過原型
和原型鏈
來實現的,那我在這裏介紹兩種常見的繼承方式:
//組合式繼承
//經過call繼承Parent的屬性,並傳入參數
//將Child的原型對象指向Parent的實例,從而繼承Parent的函數
function Parent(value){
this.val = value;
}
Parent.prototype.getValue = function(){
console.log(this.val);
}
function Child(value){
Parent.call(this,value);//繼承Parentd的屬性
}
Child.prototype = new Parent();
複製代碼
//寄生組合式繼承
//經過call繼承Parent的屬性,並傳入參數
//經過Object.create()繼承Parent的函數
function Parent(value){
this.val = value;
}
Parent.prototype.getValue = function(){
console.log(this.val);
}
function Child(value){
//繼承Parentd的屬性
Parent.call(this,value);
}
Child.prototype = Object.create(Parent.prototype,{
constructor:{
value:Child,
writable:true,
configurable:true,
enumerable:false
}
})
複製代碼
B.__proto__ = A.prototype
;B.a
,若在B中找不到a,則會在B.__proto__
中,也就是A.prototype
中查找,若A.prototype
中仍然沒有,則會繼續向上查找,最終,必定會找到Object.prototype
,假若還找不到,由於Object.prototype.__proto__
指向null
,所以會返回undefined
;Object.prototype.__proto__
——> null。因而可知,原型
和原型鏈
是如此的強大,但願看完這篇文章,你再也不會對他們感到恐懼。 寫完這篇已經近凌晨兩點,若是你以爲這篇文章對你有些許收穫,請點贊支持!!
參考資料: << 你不知道的javascript 上卷 >>