Javascript?原型與原型鏈深入詳解
前言
在前端這塊領(lǐng)域,原型與原型鏈?zhǔn)敲恳粋€(gè)前端 er 必須掌握的概念。多次在面試或者一些技術(shù)博客里面看見這個(gè)概念。由此可見,這個(gè)玩意對于前端來說有多重要。其實(shí)它本身理解起來不難,但是很多剛?cè)胄星岸说耐瑢W(xué),看到prototype
、__proto__
理解起來還是有點(diǎn)吃力,然后腦子里面就亂成一鍋粥,就像我一樣。 但是這是很正常的事情,沒什么大不了的,就像我們想要學(xué)會(huì)跑步,那么我們就必須先學(xué)會(huì)走路。任何事情都是有個(gè)過程的。所以現(xiàn)在就跟我一起來攻克這個(gè)難點(diǎn)吧。
通過這篇文章你將掌握以下知識點(diǎn):
- 理解
__proto_
; - 理解
prototype
; - 理解
javascript
中對象
的概念; - 理解原型和原型鏈;
- 理解
javascript
中類
的概念; - 理解
new
的實(shí)現(xiàn); - 理解
instanceof
的實(shí)現(xiàn); - 理解
javascript
的繼承; - 加深對
javascript
這門語言的理解。
這也是本篇文章的寫作思路。
對象
那么我們就從對象這一概念開始說起,其實(shí)對象這一概念相信大家并不陌生。有一種說法是“javasrcript 中萬物皆是對象”,其實(shí)這個(gè)說法是錯(cuò)誤的,一個(gè)很簡單的例子,javasript
中簡單基本類型(string、boolean、number、null、undefined、symbol)本身就不是對象。其實(shí)javasript
中對象主要分為函數(shù)對象
和普通對象
。
其中:
String
Number
Boolean
Object
Function
Array
Date
RegExp
Error
這些都是函數(shù)對象,他們同時(shí)也被稱為內(nèi)置對象
。函數(shù)對象
本身其實(shí)就是一個(gè)純函數(shù),javascript
用他們來模擬類
。
普通對象就很簡單了,就是我們常見的對象:
const obj = { name: "juefei", desc: "cool", };
可能說到這,你還是無法理解到底啥是函數(shù)對象
,啥是普通對象
,那我們就一起來看看下面的代碼:
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
的實(shí)例,而func1
、func2
、func3
則都是Function
的實(shí)例,稱為函數(shù)對象
。
我們再看看:
console.log(typeof Object); //f unction console.log(typeof Function); // function
你是不是驚呆了,原來Object
和Function
都是 Function
的實(shí)例。
所以我們得出一個(gè)結(jié)論就是:
- 只要是
Function
的實(shí)例,那就是函數(shù)對象
,其余則為普通對象
。
同樣我們也可以看出,不僅 Object
是函數(shù)對象
,就連 Function
本身也是函數(shù)對象,因?yàn)槲覀兺ㄟ^ console.log(typeof Function);
得知 Function
是 Function
的實(shí)例。是不是又開始有點(diǎn)繞了?沒事,到這一步你就記住我們剛剛的結(jié)論就算完成目標(biāo):
- 只要是
Function
的實(shí)例,那就是函數(shù)對象
,其余則為普通對象
。
那么說到對象,我們從上面可以看出,一個(gè)對象是通過構(gòu)造函數(shù) new 出來的,這其實(shí)跟原型
和原型鏈
有很大的關(guān)系,那么原型
和原型鏈
到底是用來干嘛的呢?
原型
涉及到這兩個(gè)概念,我們就必須先來介紹兩個(gè)東西: __proto__
和prototype
,這兩個(gè)變量可以說,在 javascript
這門語言里面隨處可見,我們不管他三七二十一,我們先來看一張表:
對象類型 | __proto__ | prototype |
---|---|---|
普通對象 | ? | ? |
函數(shù)對象 | ? | ? |
所以,請你先記住以下結(jié)論:
- 只有
函數(shù)對象
有prototype
屬性,普通對象
沒有這個(gè)屬性。 函數(shù)對象
和普通對象
都有__proto__
這個(gè)屬性。prototype
和__proto__
都是在創(chuàng)建一個(gè)函數(shù)或者對象會(huì)自動(dòng)生成的屬性。
接著我們來驗(yàn)證一下:
function func() { //func稱為構(gòu)造函數(shù) } 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 屬性)
所以就驗(yàn)證了我們剛剛的結(jié)論:
- 只有
函數(shù)對象
有prototype
屬性,普通對象
沒有這個(gè)屬性 函數(shù)對象
和普通對象
都有__proto__
這個(gè)屬性。prototype
和__proto__
都是在創(chuàng)建一個(gè)函數(shù)或者對象會(huì)自動(dòng)生成的屬性。
你看我又重復(fù)寫了一遍,我不是為了湊字?jǐn)?shù),是為了你加深記憶,這對于我們接下來的篇幅很重要。
接著我們來看看下面的代碼:
console.log(obj.__proto__ === Object.prototype); // true console.log(func.__proto__ === Function.prototype); // true
所以我們又得出如下結(jié)論:
- 實(shí)例的
__proto__
屬性主動(dòng)指向構(gòu)造的prototype
; prototype
屬性被__proto__
屬性 所指向。
這就是prototype
屬性和__proto__
屬性的區(qū)別與聯(lián)系。 這可能又有點(diǎn)繞了,來多看幾遍這一節(jié),多背一下我們的結(jié)論。我們繼續(xù)。
那么問題來了,既然func
是一個(gè)函數(shù)對象
,函數(shù)對象是有 prototype
屬性的,那么func.prototype.__proto__
等于啥呢?
為了解決這個(gè)問題,我們來思考一下:
首先,我們看看func.prototype
是啥:
console.log(typeof func.prototype); //object
好,我們知道了,func.prototype
是一個(gè)對象,那既然是對象,那 func.prototype
那不就是 Object
的實(shí)例嗎?那也就是說,func.prototype.__proto__
屬性肯定是指向Object.prototype
咯! 好,我們來驗(yàn)證一下:
console.log(func.prototype.__proto__ === Object.prototype); //true
看見沒有,就是這樣的。那看到這里,我們應(yīng)該也知道當(dāng)我們這創(chuàng)建一個(gè)構(gòu)造函數(shù)的時(shí)候,javascript 是如何幫我們自動(dòng)生成__proto__
和prototype
屬性的。哈哈沒錯(cuò)就是這樣:
//我們手動(dòng)創(chuàng)建func函數(shù) function func() {} //javascript悄悄咪咪執(zhí)行以下代碼: func._proto = Function.prototype; //實(shí)例的 __proto__ 屬性主動(dòng)指向構(gòu)造的 prototype func.prototype = { constructor: func, __proto: Object.prototype, //我們剛剛才在上面驗(yàn)證的,你別又忘記了 };
我還專門為你畫了個(gè)圖(夠貼心不老鐵):
所以prototype
又被稱為顯式原型對象,而__proto__
又被稱為隱式原型對象。
hi,看到這里,你是不是有種腦子開了光的感覺。哈哈,所以到現(xiàn)在你應(yīng)該已經(jīng)理解原型的概念了,如果你還不理解,那就把上述章節(jié)再看一遍。最好拿個(gè)紙筆出來跟著畫一畫,順便拿出電腦把示例代碼敲一敲。好,整理一下頭腦,接下來我們來看看什么又是原型鏈
。
原型鏈
再介紹這個(gè)概念之前,我們先來看如下代碼:
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****//
接下來我們來逐步解析一下:
- 創(chuàng)建了一個(gè)構(gòu)造函數(shù)
Person
,此時(shí),Person.portotype
自動(dòng)創(chuàng)建,其中包含了constructor
和__proto__
兩個(gè)屬性; - 給對象
Person.prototype
新增了一個(gè)方法getName
; - 給對象
Person.prototype
新增了一個(gè)方法getDesc
; - 根據(jù)構(gòu)造函數(shù)
Person
新建一個(gè)實(shí)例:obj
(在創(chuàng)建實(shí)例的時(shí)候,構(gòu)造函數(shù)會(huì)自動(dòng)執(zhí)行); - 打印實(shí)例
obj
:
{ name: 'juefei', desc: 'cool' }
根據(jù)上面一節(jié)的結(jié)論,我們得出:
obj.__proto__ = Person.prototype;
- 執(zhí)行到第 6 步時(shí),由于在實(shí)例
obj
上面找不到getName()
這個(gè)方法,所以它就會(huì)自動(dòng)去通過自身的__proto__
繼續(xù)向上查找,結(jié)果找到了Person.prototype
,接著它發(fā)現(xiàn),剛好Person.prototype
上面有getName()
方法,于是找到了這個(gè)方法,它就停止了尋找。 怎么樣,是不是有一種環(huán)環(huán)相扣的感覺?他們形成一個(gè)鏈了,沒錯(cuò),這就是原型鏈
。
我們得出如下結(jié)論:
在訪問一個(gè)對象(假設(shè)這個(gè)對象叫 obj)的屬性/方法時(shí),若在當(dāng)前的對象上面找不到,則會(huì)嘗試通過obj.__proto__
去尋找,而 obj.__proto__
又指向其構(gòu)造函數(shù)(假設(shè)叫objCreated
)的 prototype
,所以它又自動(dòng)去 objCreated.prototype
的屬性/方法上面去找,結(jié)果還是沒找到,那么就訪問 objCreated.prototype.__proto__
繼續(xù)往上面尋找,直到找到,則停止對原型鏈對尋找,若最終還是沒能找到,則返回 undefined
。 一直沿著原型鏈尋找下去,直到找到 Object.prototype.__proto__
,指向 null
,于是返回 undefined
了。
是不是自然而然就理解了。我又給你畫了個(gè)圖:
接下來我們再來增加一些概念:
- 任何
內(nèi)置函數(shù)對象
本身的__proto__
屬性都指向Function
的原型對象,即:Function.prototype
; - 除了
Object.prototype.__proto__
指向null
,所有的內(nèi)置函數(shù)對象
的原型對象的__proto__
屬性 (內(nèi)置函數(shù)對象.prototype.__proto__
),都指向Object
。
我們得出如下終極原型鏈的圖:
針對這個(gè)圖,我最終給出我們經(jīng)常看見那個(gè)原型鏈的圖:
好好對比一下,拿出紙和筆畫一畫,根據(jù)上面章節(jié)的講解,相信你很容易就能明白。
javascript中的類
剛剛我們終于明白什么是 原型
和 原型鏈
。下面我們根據(jù)上面的概念來講解一下javascript
中的類
。 我們知道,在面向?qū)ο蟮恼Z言中,類可以被實(shí)例化
多次,這個(gè)實(shí)例化
是指我們可以根據(jù)構(gòu)造函數(shù)去獨(dú)立復(fù)制
多個(gè)獨(dú)立的實(shí)例,這些實(shí)例之間是獨(dú)立的。但是實(shí)際上在 javascript
卻不是這樣的,因?yàn)樗皇沁@種復(fù)制機(jī)制
。我們不能創(chuàng)建一個(gè)類的多個(gè)實(shí)例,我們只能創(chuàng)建這個(gè)類的多個(gè)對象,因?yàn)樗麄兌际峭ㄟ^原型
和原型鏈
關(guān)聯(lián)到同一個(gè)對象。所以在 javascript
中 ,類
都是通過原型
和原型鏈
來實(shí)現(xiàn)的,它其實(shí)是一種委托方式
。
new的實(shí)現(xiàn)
了解了上面javascript
中的類
的概念,那我們應(yīng)該很容易就理解new
的過程,其核心無非就是執(zhí)行原型鏈的鏈接:
function myNew(Cons, ...args) { let obj = {}; obj.__proto__ = Cons.prototype; //執(zhí)行原型鏈接 let res = Cons.call(obj, args); return typeof res === "object" ? res : obj; }
instanceof的實(shí)現(xiàn)
那么學(xué)習(xí)了原型
和原型鏈
,instanceof
的實(shí)現(xiàn)肯定也很簡單了,它也是通過原型
和原型鏈
來實(shí)現(xiàn)的:
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__; } }
我就不講解過程了,因?yàn)槲抑滥憧隙芸炊?/p>
javascript的繼承
我們都知道繼承也是通過原型
和原型鏈
來實(shí)現(xiàn)的,那我在這里介紹兩種常見的繼承方式:
組合繼承:
//組合式繼承 //通過call繼承Parent的屬性,并傳入?yún)?shù) //將Child的原型對象指向Parent的實(shí)例,從而繼承Parent的函數(shù) 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的屬性,并傳入?yún)?shù) //通過Object.create()繼承Parent的函數(shù) 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, }, });
總結(jié)
- 若 A 通過 new 創(chuàng)建了 B,則
B.__proto__ = A.prototype
; - 執(zhí)行
B.a
,若在 B 中找不到 a,則會(huì)在B.__proto__
中,也就是A.prototype
中查找,若A.prototype
中仍然沒有,則會(huì)繼續(xù)向上查找,最終,一定會(huì)找到Object.prototype
,倘若還找不到,因?yàn)?code>Object.prototype.__proto__指向null
,因此會(huì)返回undefined
; - 原型鏈的頂端,一定有
Object.prototype.__proto__
——> null。
由此可見,原型
和原型鏈
是如此的強(qiáng)大
到此這篇關(guān)于Javascript 原型與原型鏈深入詳解的文章就介紹到這了,更多相關(guān)Javascript 原型鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
js實(shí)現(xiàn)不提示直接關(guān)閉網(wǎng)頁窗口
本文主要介紹了js實(shí)現(xiàn)不提示直接關(guān)閉網(wǎng)頁窗口的方法。具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-03-03JavaScript中校驗(yàn)銀行卡號的實(shí)現(xiàn)代碼
本文通過案例給大家介紹了js中校驗(yàn)銀行卡號的代碼,代碼小編測試過,可行。代碼簡單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2016-12-12javascript 面向?qū)ο骹unction詳解及實(shí)例代碼
這篇文章主要介紹了javascript 面向?qū)ο骹unction詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02JS與Ajax Get和Post在使用上的區(qū)別實(shí)例詳解
這篇文章主要介紹了JS與Ajax Get和Post在使用上的區(qū)別實(shí)例詳解的相關(guān)資料,非常不錯(cuò)具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06JavaScript實(shí)現(xiàn)簡單的時(shí)鐘實(shí)例代碼
這篇文章主要介紹了JavaScript實(shí)現(xiàn)簡單的時(shí)鐘實(shí)例代碼,有需要的朋友可以參考一下2013-11-11javascript之水平橫向滾動(dòng)歌詞同步的應(yīng)用
javascript之水平橫向滾動(dòng)歌詞同步的應(yīng)用...2007-05-05webpack配置proxyTable時(shí)pathRewrite無效的解決方法
這篇文章主要介紹了webpack配置proxyTable時(shí)pathRewrite無效的解決方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12