1. 程式人生 > >詳解JavaScript中的原型

詳解JavaScript中的原型

# 前言 `原型`、`原型鏈`應該是被大多數`前端er`說爛的詞,但是應該還有很多人不能完整的解釋這兩個內容,當然也包括我自己。 最早一篇`原型鏈`文章寫於`2019年07月`,那個時候也是費了老大勁才理解到了七八成,到現在基本上忘的差不多了。時隔兩年,興趣所向重新開始覆盤一下`原型`和`原型鏈`的內容。 # JavaScript中的物件 在`JavaScript`中,物件被稱為是一系列屬性的集合。 建立物件的方式也有很多種,最常見的一種就是`雙花括號`的形式: ```javascript var obj = {}; obj.name = '小土豆'; obj.age = 18; ``` 這種方式實際上是下面這種方式的`語法糖`: ```javascript var obj = new Object(); obj.name = '小土豆'; obj.age = 18; ``` 除此之外,在`JavaScript`中也可以通過`建構函式`自定義物件。 ```javascript function Cat(){} var catMimi = new Cat(); // 自定義物件 ``` > 如果一個函式使用`new`關鍵字呼叫,那麼這個函式就可以稱為是`建構函式`,否則就是`普通函式`。 # 什麼是原型 一句話簡單總結原型:`原型是一個物件`。 > 在後面的總結中,`原型`可能會被描述為`原型物件`,其等價於`原型` 原型從哪裡來?原型這個物件存在於哪裡,需要通過程式碼去建立嗎? 我們說物件是一系列屬性的集合,那原型這個物件包含什麼屬性呢? 如何操作和使用原型? 接下來我們一個一個問題去探究。 ## 原型從哪裡來 `JavaScript`會為所有的`函式`建立一個`原型`。 ```javascript function Cat(){} ``` 上面的程式碼中我們建立了一個`Cat`函式,那這個`Cat`函式就有一個`原型`,用程式碼表示就是:`Cat.prototype`。 同樣我們建立一個函式`Fn1`,函式`Fn1`就有一個`原型`,用程式碼表示就是`Fn1.prototype`。 > `函式名稱`的`大寫`和`小寫`本質上沒有任何區別 ## 原型包含哪些屬性 前面我們說過以下這兩點: * 原型是一個物件 * 物件是一系列屬性的集合 那`原型`都包含哪些屬性呢? 前面我們已經知道`原型`用程式碼表示就是:`functionName.prototype`,那我們在程式碼中`console.log`一下。 ```javascript function Cat(){} console.log("Cat.prototype:"); console.log(Cat.prototype); function Dog(){} console.log("Dog.prototype:"); console.log(Dog.prototype); ``` `Firefox`瀏覽器中的輸出結果如下: ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53803823c8cc4408bb2f90d70e887242~tplv-k3u1fbpfcp-watermark.image) 可以看到函式的`原型`預設有兩個屬性:`constructor`和``。 其中,函式原型的`constructor`屬性指向函式本身。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb2ca1cedb594eb391969bf3735596ed~tplv-k3u1fbpfcp-watermark.image) 函式原型的``屬性稱為`隱式原型`,後面我們會分出一節單獨介紹`隱式原型`。 ## 如何操作和使用原型 正常我們操作一個`普通物件`的方式是下面這樣的: ```javascript var obj = {}; // 建立物件 obj.name = '小土豆'; // 為物件新增屬性 obj.age = 18; // 為物件新增屬性 var name = obj.name; // 訪問物件屬性 ``` `原型`既然也是一個物件,所以操作`原型`的方式和上述的方式相同。 ```javascript function Cat(){} Cat.prototype.type = 'cat'; Cat.prototype.color = 'White'; Cat.prototype.sayInfo = function(){ console.log(this.type + ' is ' + this.color); } ``` 此時再次列印`Cat.prototype`就能看到我們新增到`原型`上的屬性: ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/802c1453b2de4870ac57ae6ad29d721f~tplv-k3u1fbpfcp-watermark.image) 訪問`原型物件`上的方法和屬性: ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5ea28cba2276459e838c246b489e74f8~tplv-k3u1fbpfcp-watermark.image) > 以上這些操作`原型`的方法,對於真正的專案開發並沒有什麼參考價值,不過不用著急,後面我們會詳細講解 # 隱式原型 前面我們在總結函式的`原型物件`時提到過`隱式原型`。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a6a4e2f2e9b243e79ce60f4156f9b161~tplv-k3u1fbpfcp-watermark.image) 那實際上,`JavaScript`會為所有的`物件`建立叫`隱式原型`的屬性。我們一直說原型是一個物件,所以在上面的截圖中,原型也有一個`隱式原型`屬性。 ## 隱式原型的程式碼表示 `隱式原型`是物件的`私有屬性`,在程式碼中可以這樣訪問:`obj.__proto__`。 > `obj.__proto__`這種寫法是`非標準`的,一些低版本的瀏覽器並不支援這樣的寫法 我們在瀏覽器的控制檯中實際訪問一下: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4caeaad2f68143d086df2f3b26e9ac79~tplv-k3u1fbpfcp-watermark.image) 從列印的結果可以看到`隱式原型`也是一個物件,那`隱式原型`這個物件裡面又包含什麼屬性呢?下面我們一起來看看。 ## 隱式原型存在的意義 首先我們寫一個簡單的示例: ```javascript function Cat(){} var catMimi = new Cat(); var catJuju = new Cat(); ``` 在上面這段程式碼中,我們建立了一個`Cat`函式,並且通過`new`關鍵字建立了以`Cat`為`建構函式`的兩個例項物件`catMimi`和`catJuju`。 接下來我們在瀏覽器的`console`工具中看看這兩個例項物件的`隱式原型`都包含了那些屬性。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/654ea25af9d049b58395645751c0c56d~tplv-k3u1fbpfcp-watermark.image) 可以看到,`catMimi.__proto__`和`catJuju._proto__`的結果貌似是一樣的,而且眼尖的同學應該也發現了這個列印結果似乎和前面一節`【原型包含那些屬性】`中列印的`Cat.prototype`是一樣的。 那話不多說,我們用`==`運算子判斷一下即可: ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4aeb7f5011de4702a3d650acaff1555b~tplv-k3u1fbpfcp-watermark.image) 可以看到所有的判斷結果均為`true`。 由於物件`catMimi`、`catJuJu`都是由`Cat`函式創建出來的例項,所以總結出來結論就是:`物件的隱式原型__proto__指向建立該物件的函式的原型物件`。 # 原型鏈:原型和隱式原型存在的意義 前面我們總結了`原型`、`隱式原型`的概念以及如何使用程式碼操作`原型`和`隱式原型`,總的看來`原型`和`隱式原型`好像也沒有特別厲害的地方,它們到底有什麼用呢? ## 所有的例項物件共享原型上定義的屬性和方法 我們來看下面這樣一個示例: ```javascript 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`引數。 ```javascript var catMimi = new Cat('Mimi', 1); var catJuju = new Cat('Juju', 2); ``` 控制檯檢視一下我們建立的物件: ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/24ce4d2edf494d2e802cf7d862764819~tplv-k3u1fbpfcp-watermark.image) 可以看到這兩個物件有著相同的屬性,由於`type`、`eyes`是在`Cat`函式建立時已經有了固定的`初始值`,所以這兩個屬性值是相同的;`sayInfo`函式也都是相同的功能,打印出一些屬性的資訊;只有`name`、`age`是通過引數傳遞的,各自的值不相同。除此之外呢,`catMimi`和`catJuju`是兩個不同的物件,兩者的`屬性值`互相獨立,修改其中任意一個的屬性值並不會影響另外一個物件的屬性值。 假如之後我們有更多這樣的物件,`JavaScript`還是會為每一個物件建立相同的`屬性`,而這些所有的物件都擁有著相同的`type`、`eyes`屬性值和相同功能的`sayInfo`函式。這無疑造成了記憶體浪費,那這個時候我們就可以將這些屬性定義到函式的`原型物件`上: ```javascript 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); ``` 然後我們再來看看這兩個物件: ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9d1603ae7869456ba07968e5e9722e18~tplv-k3u1fbpfcp-watermark.image) 可以看到這兩個物件現在只包含了兩個屬性,就是`Cat`建構函式內容內部定義的兩個屬性:`name`、`age`。 接著我們在去訪問物件上的`type`、`eyes`和`sayInfo`: ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4847cf0a0b5c4825b25d2eb88cf92b47~tplv-k3u1fbpfcp-watermark.image) 我們的`例項物件`還是可以正常訪問到屬性,方法也打印出來正確的資訊。那到底是怎麼訪問到的呢? ## 原型鏈 在上一個示例程式碼中,我們將一些`屬性`和`方法`定義到函式的`原型`上,最後使用該函式創建出來的`例項物件`可以正常訪問`原型`上定義的`屬性`和`方法`,這是怎麼做到的呢? 前面我們說過:物件的`隱式原型`指向建立該物件的函式的`原型物件`,所以當`例項物件`中沒有某個屬性時,`JavaScript`就會沿著該`例項物件`的`隱式原型`去查詢,這便是我們所說的`原型鏈`。 ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/087fe10b9f7448a284b547bc9211d91c~tplv-k3u1fbpfcp-watermark.image) 那既然是鏈,我們想到的應該是一個連著一個的東西,所以應該不僅僅是當前例項物件的`隱式原型`指向建立該物件的函式的`原型物件`,所以我們在對`catMimi`物件做點操作: ![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/02697d7e6bed48e1878a72bd6c233639~tplv-k3u1fbpfcp-watermark.image) 在上面的操作,我們呼叫了`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`屬性: ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fee5d51959c541cd92f177e6ef38d606~tplv-k3u1fbpfcp-watermark.image) 可以看到,`Object.prototype`中存在`hasOwnProperty`屬性,所以`catMimi.hasOwnPrototype`實際上呼叫的是`Object.prototype.hasOwnProperty`。 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/adede89e51c04a2b90beef8cb1cb848d~tplv-k3u1fbpfcp-watermark.image) # 總結 本篇文章到此基本就基本結束了,相信大家應該對`原型`和`原型鏈`有了一定的瞭解。最後呢,我們在對本篇文章做一個總結。 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ead8002adf6487ba7c080c81029e1e3~tplv-k3u1fbpfcp-watermark.image) # 近期文章 [詳解Vue中的computed和watch](https://juejin.cn/post/6917805693860839431) [記一次真實的Webpack優化經歷](https://juejin.cn/post/6908897055599509512) [JavaScript的執行上下文,真沒你想的那麼難](https://juejin.cn/post/6901107803696398349) [骨架屏(page-skeleton-webpack-plugin)初探](https://juejin.cn/post/6885535026184716295) [Vue結合Django-Rest-Frameword實現登入認證(二)](https://juejin.cn/post/6882566665524281357) [Vue結合Django-Rest-Frameword實現登入認證(一)](https://juejin.cn/post/6874872436962099208) # 寫在最後 如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者 文章`公眾號`首發,關注 [`不知名寶藏程式媛`](https://mmbiz.qpic.cn/mmbiz_gif/I4j8PCMjMMhl5J9MoqaIsAAeJVfMqYibiaJWpspxGicRiczx0xib35DLPlXvOd6amGPoLxLfnbERpC5TIPDgFBwc8gQ/640?wx_fmt=gif&tp=webp&wxfrom=5&wx_lazy=1) 第一時間獲取最新的文章 筆芯