看完這篇,再也不會(huì)害怕別人問(wèn)我什么是原型了
本文轉(zhuǎn)載自微信公眾號(hào)「不知名寶藏程序媛」,作者小土豆 。轉(zhuǎn)載本文請(qǐng)聯(lián)系不知名寶藏程序媛公眾號(hào)。
前言
原型、原型鏈應(yīng)該是被大多數(shù)前端er說(shuō)爛的詞,但是應(yīng)該還有很多人不能完整的解釋這兩個(gè)內(nèi)容,當(dāng)然也包括我自己。
最早一篇原型鏈文章寫于2019年07月,那個(gè)時(shí)候也是費(fèi)了老大勁才理解到了七八成,到現(xiàn)在基本上忘的差不多了。時(shí)隔兩年,興趣所向重新開始復(fù)盤一下原型和原型鏈的內(nèi)容。
JavaScript中的對(duì)象
在JavaScript中,對(duì)象被稱為是一系列屬性的集合。
創(chuàng)建對(duì)象的方式也有很多種,最常見的一種就是雙花括號(hào)的形式:
- var obj = {};
- obj.name = '小土豆';
- obj.age = 18;
這種方式實(shí)際上是下面這種方式的語(yǔ)法糖:
- var obj = new Object();
- obj.name = '小土豆';
- obj.age = 18;
除此之外,在JavaScript中也可以通過(guò)構(gòu)造函數(shù)自定義對(duì)象。
- function Cat(){}
- var catMimi = new Cat(); // 自定義對(duì)象
如果一個(gè)函數(shù)使用new關(guān)鍵字調(diào)用,那么這個(gè)函數(shù)就可以稱為是構(gòu)造函數(shù),否則就是普通函數(shù)。
什么是原型
一句話簡(jiǎn)單總結(jié)原型:原型是一個(gè)對(duì)象。
在后面的總結(jié)中,原型可能會(huì)被描述為原型對(duì)象,其等價(jià)于原型
原型從哪里來(lái)?原型這個(gè)對(duì)象存在于哪里,需要通過(guò)代碼去創(chuàng)建嗎?
我們說(shuō)對(duì)象是一系列屬性的集合,那原型這個(gè)對(duì)象包含什么屬性呢?
如何操作和使用原型?
接下來(lái)我們一個(gè)一個(gè)問(wèn)題去探究。
▣ 原型從哪里來(lái)
JavaScript會(huì)為所有的函數(shù)創(chuàng)建一個(gè)原型。
- function Cat(){}
上面的代碼中我們創(chuàng)建了一個(gè)Cat函數(shù),那這個(gè)Cat函數(shù)就有一個(gè)原型,用代碼表示就是:Cat.prototype。
同樣我們創(chuàng)建一個(gè)函數(shù)Fn1,函數(shù)Fn1就有一個(gè)原型,用代碼表示就是Fn1.prototype。
函數(shù)名稱的大寫和小寫本質(zhì)上沒有任何區(qū)別
▣ 原型包含哪些屬性
前面我們說(shuō)過(guò)以下這兩點(diǎn):
原型是一個(gè)對(duì)象
對(duì)象是一系列屬性的集合
那原型都包含哪些屬性呢?
前面我們已經(jīng)知道原型用代碼表示就是:functionName.prototype,那我們?cè)诖a中console.log一下。
- function Cat(){}
- console.log("Cat.prototype:");
- console.log(Cat.prototype);
- function Dog(){}
- console.log("Dog.prototype:");
- console.log(Dog.prototype);
Firefox瀏覽器中的輸出結(jié)果如下:
可以看到函數(shù)的原型默認(rèn)有兩個(gè)屬性:constructor和
其中,函數(shù)原型的constructor屬性指向函數(shù)本身。
函數(shù)原型的
▣ 如何操作和使用原型
正常我們操作一個(gè)普通對(duì)象的方式是下面這樣的:
- var obj = {}; // 創(chuàng)建對(duì)象
- obj.name = '小土豆'; // 為對(duì)象添加屬性
- obj.age = 18; // 為對(duì)象添加屬性
- var name = obj.name; // 訪問(wèn)對(duì)象屬性
原型既然也是一個(gè)對(duì)象,所以操作原型的方式和上述的方式相同。
- function Cat(){}
- Cat.prototype.type = 'cat';
- Cat.prototype.color = 'White';
- Cat.prototype.sayInfo = function(){
- console.log(this.type + ' is ' + this.color);
- }
此時(shí)再次打印Cat.prototype就能看到我們添加到原型上的屬性:
訪問(wèn)原型對(duì)象上的方法和屬性:
以上這些操作原型的方法,對(duì)于真正的項(xiàng)目開發(fā)并沒有什么參考價(jià)值,不過(guò)不用著急,后面我們會(huì)詳細(xì)講解
隱式原型
前面我們?cè)诳偨Y(jié)函數(shù)的原型對(duì)象時(shí)提到過(guò)隱式原型。
那實(shí)際上,JavaScript會(huì)為所有的對(duì)象創(chuàng)建叫隱式原型的屬性。我們一直說(shuō)原型是一個(gè)對(duì)象,所以在上面的截圖中,原型也有一個(gè)隱式原型屬性。
▣ 隱式原型的代碼表示
隱式原型
是對(duì)象的私有屬性,在代碼中可以這樣訪問(wèn):obj.__proto__。
obj.__proto__這種寫法是非標(biāo)準(zhǔn)的,一些低版本的瀏覽器并不支持這樣的寫法
我們?cè)跒g覽器的控制臺(tái)中實(shí)際訪問(wèn)一下:
從打印的結(jié)果可以看到隱式原型也是一個(gè)對(duì)象,那隱式原型這個(gè)對(duì)象里面又包含什么屬性呢?下面我們一起來(lái)看看。
▣ 隱式原型存在的意義
首先我們寫一個(gè)簡(jiǎn)單的示例:
- function Cat(){}
- var catMimi = new Cat();
- var catJuju = new Cat();
在上面這段代碼中,我們創(chuàng)建了一個(gè)Cat函數(shù),并且通過(guò)new關(guān)鍵字創(chuàng)建了以Cat為構(gòu)造函數(shù)的兩個(gè)實(shí)例對(duì)象catMimi和catJuju。
接下來(lái)我們?cè)跒g覽器的console工具中看看這兩個(gè)實(shí)例對(duì)象的隱式原型都包含了那些屬性。
可以看到,catMimi.__proto__和catJuju._proto__的結(jié)果貌似是一樣的,而且眼尖的同學(xué)應(yīng)該也發(fā)現(xiàn)了這個(gè)打印結(jié)果似乎和前面一節(jié)【原型包含那些屬性】中打印的Cat.prototype是一樣的。
那話不多說(shuō),我們用==運(yùn)算符判斷一下即可:
可以看到所有的判斷結(jié)果均為true。
由于對(duì)象catMimi、catJuJu都是由Cat函數(shù)創(chuàng)建出來(lái)的實(shí)例,所以總結(jié)出來(lái)結(jié)論就是:對(duì)象的隱式原型__proto__指向創(chuàng)建該對(duì)象的函數(shù)的原型對(duì)象。
原型鏈:原型和隱式原型存在的意義
前面我們總結(jié)了原型、隱式原型的概念以及如何使用代碼操作原型和隱式原型,總的看來(lái)原型和隱式原型好像也沒有特別厲害的地方,它們到底有什么用呢?
▣ 所有的實(shí)例對(duì)象共享原型上定義的屬性和方法
我們來(lái)看下面這樣一個(gè)示例:
- function Cat(name, age){
- this.type = 'RagdollCat'; //布偶貓
- this.eyes = 2;
- this.name = name;
- this.age = age;
- this.sayInfo = function(){
- console.log(this.type + ' ' + this.name + ' is ' + this.age + ' years old');
- }
- }
在這個(gè)示例中,我們創(chuàng)建了一個(gè)Cat函數(shù),同時(shí)Cat函數(shù)有五個(gè)屬性:type、eyes、name、age、sayInfo,其中type和eyes屬性已經(jīng)有了初始值,而name、age通過(guò)參數(shù)傳遞并賦值;sayInfo對(duì)應(yīng)是一個(gè)函數(shù),打印出type、name和age的值。
接著我們創(chuàng)建Cat的兩個(gè)實(shí)例對(duì)象catMimi、catJuju,并傳入不同的name和age參數(shù)。
- var catMimi = new Cat('Mimi', 1);
- var catJuju = new Cat('Juju', 2);
控制臺(tái)查看一下我們創(chuàng)建的對(duì)象:
可以看到這兩個(gè)對(duì)象有著相同的屬性,由于type、eyes是在Cat函數(shù)創(chuàng)建時(shí)已經(jīng)有了固定的初始值,所以這兩個(gè)屬性值是相同的;sayInfo函數(shù)也都是相同的功能,打印出一些屬性的信息;只有name、age是通過(guò)參數(shù)傳遞的,各自的值不相同。除此之外呢,catMimi和catJuju是兩個(gè)不同的對(duì)象,兩者的屬性值互相獨(dú)立,修改其中任意一個(gè)的屬性值并不會(huì)影響另外一個(gè)對(duì)象的屬性值。
假如之后我們有更多這樣的對(duì)象,JavaScript還是會(huì)為每一個(gè)對(duì)象創(chuàng)建相同的屬性,而這些所有的對(duì)象都擁有著相同的type、eyes屬性值和相同功能的sayInfo函數(shù)。這無(wú)疑造成了內(nèi)存浪費(fèi),那這個(gè)時(shí)候我們就可以將這些屬性定義到函數(shù)的原型對(duì)象上:
- function Cat(name, age){
- this.name = name;
- this.age = age;
- }
- Cat.prototype.type = 'RagdollCat'; //布偶貓
- Cat.prototype.eyes = 2;
- Cat.prototype.sayInfo = function(){
- console.log(this.type + ' ' + this.name + ' is ' + this.age + ' years old');
- }
- var catMimi = new Cat('Mimi', 1);
- var catJuju = new Cat('Juju', 2);
然后我們?cè)賮?lái)看看這兩個(gè)對(duì)象:
可以看到這兩個(gè)對(duì)象現(xiàn)在只包含了兩個(gè)屬性,就是Cat構(gòu)造函數(shù)內(nèi)容內(nèi)部定義的兩個(gè)屬性:name、age。
接著我們?cè)谌ピL問(wèn)對(duì)象上的type、eyes和sayInfo:
我們的實(shí)例對(duì)象還是可以正常訪問(wèn)到屬性,方法也打印出來(lái)正確的信息。那到底是怎么訪問(wèn)到的呢?
▣ 原型鏈
在上一個(gè)示例代碼中,我們將一些屬性和方法定義到函數(shù)的原型上,最后使用該函數(shù)創(chuàng)建出來(lái)的實(shí)例對(duì)象可以正常訪問(wèn)原型上定義的屬性和方法,這是怎么做到的呢?
前面我們說(shuō)過(guò):對(duì)象的隱式原型指向創(chuàng)建該對(duì)象的函數(shù)的原型對(duì)象,所以當(dāng)實(shí)例對(duì)象中沒有某個(gè)屬性時(shí),JavaScript就會(huì)沿著該實(shí)例對(duì)象的隱式原型去查找,這便是我們所說(shuō)的原型鏈。
那既然是鏈,我們想到的應(yīng)該是一個(gè)連著一個(gè)的東西,所以應(yīng)該不僅僅是當(dāng)前實(shí)例對(duì)象的隱式原型指向創(chuàng)建該對(duì)象的函數(shù)的原型對(duì)象,所以我們?cè)趯?duì)catMimi對(duì)象做點(diǎn)操作:
在上面的操作,我們調(diào)用了catMimi的hasOwnProperty方法,很明顯我們并沒有為這個(gè)對(duì)象定義該方法,那這個(gè)方法從哪里來(lái)呢?
答案依然是原型鏈:
- 調(diào)用catMimi.hasOwnProperty()方法
- 在實(shí)例對(duì)象catMimi中查找屬性,發(fā)現(xiàn)沒有該屬性
- 去catMimi.__proto__中查找,因?yàn)閏atMimi.__proto__=Cat.prototype(實(shí)例對(duì)象的隱式原型指向創(chuàng)建該實(shí)例的函數(shù)的原型),也就是在Cat.prototype中查找hasOwnProperty屬性,很明顯Cat.prototype也沒有該屬性
- 于是繼續(xù)沿著Cat.prototype.__proto__查找,又因?yàn)镃at.prototype.__proto__ = Object.prototype(我們一直在強(qiáng)調(diào)原型是一個(gè)對(duì)象,既然是對(duì)象,就是由Object函數(shù)創(chuàng)建的,所以Cat.prototype的隱式原型指向Object函數(shù)的原型)
我們打印一下Object.prototype的是否包含hasOwnProperty屬性:
可以看到,Object.prototype中存在hasOwnProperty屬性,所以catMimi.hasOwnPrototype實(shí)際上調(diào)用的是Object.prototype.hasOwnProperty。
總結(jié)
本篇文章到此基本就基本結(jié)束了,相信大家應(yīng)該對(duì)原型和原型鏈有了一定的了解。最后呢,我們?cè)趯?duì)本篇文章做一個(gè)總結(jié)。

原文鏈接:https://mp.weixin.qq.com/s/59p32Xe03YCGhP2uTBjTUg