前言
原型
、原型鏈
應該是被大多數前端er
說爛的詞,但是應該還有很多人不能完整的解釋這兩個內容,當然也包括我自己。
最早一篇原型鏈
文章寫於2019年07月
,那個時候也是費了老大勁才理解到了七八成,到現在基本上忘的差不多了。時隔兩年,興趣所向重新開始復盤一下原型
和原型鏈
的內容。
JavaScript中的對象
在JavaScript
中,對象被稱為是一系列屬性的集合。
創建對象的方式也有很多種,最常見的一種就是雙花括號
的形式:
var obj = {};
obj.name = '小土豆';
obj.age = 18;
這種方式實際上是下面這種方式的語法糖
:
var obj = new Object();
obj.name = '小土豆';
obj.age = 18;
除此之外,在JavaScript
中也可以通過構造函數
自定義對象。
function Cat(){}
var catMimi = new Cat(); // 自定義對象
如果一個函數使用
new
關鍵字調用,那么這個函數就可以稱為是構造函數
,否則就是普通函數
。
什么是原型
一句話簡單總結原型:原型是一個對象
。
在后面的總結中,
原型
可能會被描述為原型對象
,其等價於原型
原型從哪里來?原型這個對象存在於哪里,需要通過代碼去創建嗎?
我們說對象是一系列屬性的集合,那原型這個對象包含什么屬性呢?
如何操作和使用原型?
接下來我們一個一個問題去探究。
原型從哪里來
JavaScript
會為所有的函數
創建一個原型
。
function Cat(){}
上面的代碼中我們創建了一個Cat
函數,那這個Cat
函數就有一個原型
,用代碼表示就是:Cat.prototype
。
同樣我們創建一個函數Fn1
,函數Fn1
就有一個原型
,用代碼表示就是Fn1.prototype
。
函數名稱
的大寫
和小寫
本質上沒有任何區別
原型包含哪些屬性
前面我們說過以下這兩點:
- 原型是一個對象
- 對象是一系列屬性的集合
那原型
都包含哪些屬性呢?
前面我們已經知道原型
用代碼表示就是:functionName.prototype
,那我們在代碼中console.log
一下。
function Cat(){}
console.log("Cat.prototype:");
console.log(Cat.prototype);
function Dog(){}
console.log("Dog.prototype:");
console.log(Dog.prototype);
Firefox
瀏覽器中的輸出結果如下:
可以看到函數的原型
默認有兩個屬性:constructor
和<prototype>
。
其中,函數原型的constructor
屬性指向函數本身。
函數原型的<propotype>
屬性稱為隱式原型
,后面我們會分出一節單獨介紹隱式原型
。
如何操作和使用原型
正常我們操作一個普通對象
的方式是下面這樣的:
var obj = {}; // 創建對象
obj.name = '小土豆'; // 為對象添加屬性
obj.age = 18; // 為對象添加屬性
var name = obj.name; // 訪問對象屬性
原型
既然也是一個對象,所以操作原型
的方式和上述的方式相同。
function Cat(){}
Cat.prototype.type = 'cat';
Cat.prototype.color = 'White';
Cat.prototype.sayInfo = function(){
console.log(this.type + ' is ' + this.color);
}
此時再次打印Cat.prototype
就能看到我們添加到原型
上的屬性:
訪問原型對象
上的方法和屬性:
以上這些操作
原型
的方法,對於真正的項目開發並沒有什么參考價值,不過不用着急,后面我們會詳細講解
隱式原型
前面我們在總結函數的原型對象
時提到過隱式原型
。
那實際上,JavaScript
會為所有的對象
創建叫隱式原型
的屬性。我們一直說原型是一個對象,所以在上面的截圖中,原型也有一個隱式原型
屬性。
隱式原型的代碼表示
隱式原型
是對象的私有屬性
,在代碼中可以這樣訪問:obj.__proto__
。
obj.__proto__
這種寫法是非標准
的,一些低版本的瀏覽器並不支持這樣的寫法
我們在瀏覽器的控制台中實際訪問一下:
從打印的結果可以看到隱式原型
也是一個對象,那隱式原型
這個對象里面又包含什么屬性呢?下面我們一起來看看。
隱式原型存在的意義
首先我們寫一個簡單的示例:
function Cat(){}
var catMimi = new Cat();
var catJuju = new Cat();
在上面這段代碼中,我們創建了一個Cat
函數,並且通過new
關鍵字創建了以Cat
為構造函數
的兩個實例對象catMimi
和catJuju
。
接下來我們在瀏覽器的console
工具中看看這兩個實例對象的隱式原型
都包含了那些屬性。
可以看到,catMimi.__proto__
和catJuju._proto__
的結果貌似是一樣的,而且眼尖的同學應該也發現了這個打印結果似乎和前面一節【原型包含那些屬性】
中打印的Cat.prototype
是一樣的。
那話不多說,我們用==
運算符判斷一下即可:
可以看到所有的判斷結果均為true
。
由於對象catMimi
、catJuJu
都是由Cat
函數創建出來的實例,所以總結出來結論就是:對象的隱式原型__proto__指向創建該對象的函數的原型對象
。
原型鏈:原型和隱式原型存在的意義
前面我們總結了原型
、隱式原型
的概念以及如何使用代碼操作原型
和隱式原型
,總的看來原型
和隱式原型
好像也沒有特別厲害的地方,它們到底有什么用呢?
所有的實例對象共享原型上定義的屬性和方法
我們來看下面這樣一個示例:
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');
}
}
在這個示例中,我們創建了一個Cat
函數,同時Cat
函數有五個屬性:type
、eyes
、name
、age
、sayInfo
,其中type
和eyes
屬性已經有了初始值,而name
、age
通過參數傳遞並賦值;sayInfo
對應是一個函數,打印出type
、name
和age
的值。
接着我們創建Cat
的兩個實例對象catMimi
、catJuju
,並傳入不同的name
和age
參數。
var catMimi = new Cat('Mimi', 1);
var catJuju = new Cat('Juju', 2);
控制台查看一下我們創建的對象:
可以看到這兩個對象有着相同的屬性,由於type
、eyes
是在Cat
函數創建時已經有了固定的初始值
,所以這兩個屬性值是相同的;sayInfo
函數也都是相同的功能,打印出一些屬性的信息;只有name
、age
是通過參數傳遞的,各自的值不相同。除此之外呢,catMimi
和catJuju
是兩個不同的對象,兩者的屬性值
互相獨立,修改其中任意一個的屬性值並不會影響另外一個對象的屬性值。
假如之后我們有更多這樣的對象,JavaScript
還是會為每一個對象創建相同的屬性
,而這些所有的對象都擁有着相同的type
、eyes
屬性值和相同功能的sayInfo
函數。這無疑造成了內存浪費,那這個時候我們就可以將這些屬性定義到函數的原型對象
上:
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);
然后我們再來看看這兩個對象:
可以看到這兩個對象現在只包含了兩個屬性,就是Cat
構造函數內容內部定義的兩個屬性:name
、age
。
接着我們在去訪問對象上的type
、eyes
和sayInfo
:
我們的實例對象
還是可以正常訪問到屬性,方法也打印出來正確的信息。那到底是怎么訪問到的呢?
原型鏈
在上一個示例代碼中,我們將一些屬性
和方法
定義到函數的原型
上,最后使用該函數創建出來的實例對象
可以正常訪問原型
上定義的屬性
和方法
,這是怎么做到的呢?
前面我們說過:對象的隱式原型
指向創建該對象的函數的原型對象
,所以當實例對象
中沒有某個屬性時,JavaScript
就會沿着該實例對象
的隱式原型
去查找,這便是我們所說的原型鏈
。
那既然是鏈,我們想到的應該是一個連着一個的東西,所以應該不僅僅是當前實例對象的隱式原型
指向創建該對象的函數的原型對象
,所以我們在對catMimi
對象做點操作:
在上面的操作,我們調用了catMimi
的hasOwnProperty
方法,很明顯我們並沒有為這個對象定義該方法,那這個方法從哪里來呢?
答案依然是原型鏈
:
- 調用
catMimi.hasOwnProperty()
方法 - 在實例對象
catMimi
中查找屬性,發現沒有該屬性 - 去
catMimi.__proto__
中查找,因為catMimi.__proto__=Cat.prototype
(實例對象的隱式原型
指向創建該實例的函數的原型
),也就是在Cat.prototype
中查找hasOwnProperty
屬性,很明顯Cat.prototype
也沒有該屬性 - 於是繼續沿着
Cat.prototype.__proto__
查找,又因為Cat.prototype.__proto__ = Object.prototype
(我們一直在強調原型是一個對象,既然是對象,就是由Object
函數創建的,所以Cat.prototype
的隱式原型
指向Object
函數的原型)
我們打印一下Object.prototype
的是否包含hasOwnProperty
屬性:
可以看到,Object.prototype
中存在hasOwnProperty
屬性,所以catMimi.hasOwnPrototype
實際上調用的是Object.prototype.hasOwnProperty
。
總結
本篇文章到此基本就基本結束了,相信大家應該對原型
和原型鏈
有了一定的了解。最后呢,我們在對本篇文章做一個總結。
近期文章
骨架屏(page-skeleton-webpack-plugin)初探
Vue結合Django-Rest-Frameword實現登錄認證(二)
Vue結合Django-Rest-Frameword實現登錄認證(一)
寫在最后
如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者
文章公眾號
首發,關注 不知名寶藏程序媛
第一時間獲取最新的文章
筆芯❤️~