摘要:不然原型鏈會斷開。喵喵喵這樣會使上一條語句失效,從而使原型鏈斷開。這是在原型鏈里面無法做到的一個功能。屬性使用借用構造函數模式,而方法則使用原型鏈。
一、對象的繼承 1.了解原型鏈
在上一篇我們講過關于原型對象的概念,當然如果不了解的建議去翻看第一篇文章,文末附有連接。我們知道每個對象都有各自的原型對象,那么當我們把一個對象的實例當做另外一個對象的原型對象。。這樣這個對象就擁有了另外一個引用類型的所有方法與屬性,當我們再把該對象的實例賦予另一個原型對象時,這樣又把這些方法繼承下去。如此層層遞進,對象與原型間存在鏈接關系,這樣就構成了原型鏈。
function Animal(){ this.type = "Animal"; } Animal.prototype.say = function(){ console.log(this.type); } function Cat(){ this.vioce = "喵喵喵"; } Cat.prototype = new Animal(); Cat.prototype.shout = function(){ console.log(this.vioce); } let cat1 = new Cat(); cat1.say(); //"Animal" //當然,我們還可以繼續繼承下去 function Tom(){ this.name = "Tom"; } Tom.prototype = new Cat(); Tom.prototype.sayName = function(){ console.log(this.name); } let cat2 = new Tom(); cat2.say(); //"Animal" cat2.shout(); //"喵喵喵" cat2.sayName(); //"Tom" cat1.sayName(); //err 報錯表示沒有該函數
很神奇的,原型鏈就實現了對象的繼承。使用原型鏈就可以使一個新對象擁有之前對象的所有方法和屬性。至于cat1.sayName()會報錯,是因為該方法是在它的子原型對象中定義,所以無法找到該函數。但是我相信很多人看到這里還是會一頭霧水,到底鏈在哪里了?誰和誰鏈在一起了?我用一張圖來讓大家更好的理解這個。
咋眼一看,這張圖信息量不少,但是理解起來卻一點都不難。我們先從Animal看起,Animal中存在一個prototype指向其原型對象,這一部分應該沒什么問題。但是Animal原型對象中卻存在[[prototype]]指向了Object,實際上是指向了Object.prototype。這是因為所有函數都是從Object繼承而來的,所有函數都是Object的實例。這也正是所有的函數都可以擁有Object方法的原因,如toString()。所以這也是原型鏈的一部分,我們從創建自定義類型開始就已經踏入了原型鏈中。
但是這部分我們暫且不管它,我們繼續往下面看。我們把Animal的實例當做Cat的原型對象
Cat.prototype = new Animal();
這樣Cat實例就擁有了其父類型的所有方法與屬性。因為代碼中尋找一個方法會不斷往上找,先在實例中尋找,如果沒有就在原型對象中去尋找,假如原型對象中沒有,就會往原型對象的原型對象中去找,如此遞進,最終如果找到則返回,找不到則報錯。當我們構成原型鏈時,會有一個對象原型當做其父類型的實例,這樣便形成一條原型鏈。當然,如果現在有不明白 [[prototype]] (__proto__)與prototype的區別可以去翻看我們第一篇文章,在這就不重復了。
這樣一來我們便明白了為何cat1中沒有sayName函數并了解原型鏈如何實現繼承了。但是我又提出了一個問題,假如我們把給子類型原型對象定義方法的位置調換一下,那么會發生什么事呢?
function Animal(){ this.type = "Animal"; } Animal.prototype.say = function(){ console.log(this.type); } function Cat(){ this.vioce = "喵喵喵"; } Cat.prototype.shout = function(){ console.log(this.vioce); } Cat.prototype = new Animal(); let cat1 = new Cat(); cat1.say(); //"Animal" cat1.shuot(); //err,報錯無此函數
控制臺中會毫不留情的告訴你,沒有該方法Uncaught TypeError: cat1.shuot is not a function。這是因為當你把父類的實例賦給子類原型對象時,會將其替換。那么你之前所定義的方法就會失效。所以在這里要注意的一點就是:給原型添加方法時一定要在替換原型語句之后,而且還有一點要注意就是,在用原型鏈實現繼承的時候,千萬不可以用字面量形式定義原型方法。不然原型鏈會斷開。
function Animal(){ this.type = "Animal"; } Animal.prototype.say = function(){ console.log(this.type); } function Cat(){ this.vioce = "喵喵喵"; } Cat.prototype = new Animal(); Cat.prototype = { //這樣會使上一條語句失效,從而使原型鏈斷開。 shout:function(){ console.log(this.vioce); } }2.原型鏈的問題
接下來我們談談原型鏈的問題。說起原型鏈的問題我們大概可以聯想到原型對象的問題:其屬性與方法會被所有實例共享,那么在原型鏈中亦是如此。
function Animal(){ this.type = "Animal"; this.color = ["white","black","yellow"]; } Animal.prototype.say = function(){ console.log(this.type); } function Cat(){ this.vioce = "喵喵喵"; } Cat.prototype = new Animal(); Cat.prototype.shout = function(){ console.log(this.vioce); } let cat1 = new Cat(); let cat2 = new Cat(); cat1.say(); //"Animal" cat1.say(); //"Animal" cat1.color.push("pink"); console.log(cat1.color); //["white", "black", "yellow", "pink"] console.log(cat2.color); //["white", "black", "yellow", "pink"]
當然,這也好理解不是。倘若孫子教會了爺爺某件事,那么爺爺會把他的本領傳個他的每個兒子孫子,沒毛病對吧。但是我們想要的是,孫子自己學會某件事,但不想讓其他人學會。這樣意思就是每個實例擁有各自的屬性,不與其他實例共享。那么我們就引入了借用構造函數的概念了。
3.借用構造函數借用構造函數,簡單來說就是在子類構造函數里面調用父類的構造函數。要怎么調用?可以使用到apply()和call()這些方法來實現這個功能。
function Animal(type = "Animal"){ //設置一個參數,如果子類不傳入參數則默認為"Animal" this.type = type; this.color = ["white","black","yellow"]; } function Cat(type){ Animal.call(this,type); //繼承Animal同時傳入type,也可以不傳參 } let cat1 = new Cat(); //沒有傳參,type默認為"Animal" let cat2 = new Cat("Cat"); //傳入"Cat",type則為"Cat" cat1.color.push("pink"); console.log(cat1.color); //["white", "black", "yellow", "pink"] console.log(cat2.color); //["white", "black", "yellow"] console.log(cat1.type); //"Animal" console.log(cat2.type); //"Cat"
這樣就實現了實例屬性不共享的功能,而且我們在這個里面還可以傳入一個參數,讓其向父類傳參。這是在原型鏈里面無法做到的一個功能。至于call()與apply()方法,在這暫且不展開,日后另作文章闡明。暫且只需要知道這是改變函數作用域的就行。
那么,借用構造函數的問題也就是構造函數的問題,方法都定義在構造函數里面了,復用性就基本涼涼。所以,我們要組合起來使用。屬性使用借用構造函數模式,而方法則使用原型鏈。
4.組合繼承function Animal(){ this.type = "Animal"; this.color = ["white","black","yellow"]; } Animal.prototype.say = function(){ console.log(this.type); } function Cat(){ Animal.call(this); //繼承屬性 this.vioce = "喵喵喵"; } Cat.prototype = new Animal(); //繼承方法 Cat.prototype.shout = function(){ console.log(this.vioce); } let cat1 = new Cat(); let cat2 = new Cat(); cat1.say(); //"Animal" cat1.say(); //"Animal" cat1.color.push("pink"); console.log(cat1.color); //["white", "black", "yellow", "pink"] console.log(cat2.color); //["white", "black", "yellow"]
這一套方法也變成了最常用的繼承方法了。但是其中也是有個缺陷,就是每次都會調用兩次父類的構造函數。從而使得實例中與原型對象中創造相同的屬性,不過原型對象中的值卻毫無意義。那有沒有更完美的方法?有,就是寄生組合式繼承。在這里我就放代碼給大家。
function obj(o){ function F(){} F.prototype = o; return new F(); } function inheritPrototype(sub,super){ let prototype = obj(super.prototype); //相當于拷貝了一個父類對象 prototype.constructor = sub; 增強對象 sub.prototype = prototype; 指定對象 } function Animal(){ this.type = "Animal"; this.color = ["white","black","yellow"]; } Animal.prototype.say = function(){ console.log(this.type); } function Cat(){ Animal.call(this); //繼承屬性 this.vioce = "喵喵喵"; } inheritPrototype(Cat,Animal); Cat.prototype.shout = function(){ console.log(this.vioce); } let cat1 = new Cat(); let cat2 = new Cat(); cat1.say(); //"Animal" cat1.say(); //"Animal" cat1.color.push("pink"); console.log(cat1.color); //["white", "black", "yellow", "pink"] console.log(cat2.color); //["white", "black", "yellow"]
這樣通過一個巧妙的方法就可以少調用一次父類的構造函數,而且不會賦予原型對象中無意義的屬性。這是被認為最理想的繼承方法。但是最多人用的還是上面那個組合式繼承方法。
總結到這原型鏈的基本概念與用法都已經一一講述,我們需要注意的地方就是prototype與__proto__的關系,重點是分清其中的區別,了解父類型跟其子類型的關系,他們之間的聯系在哪。大概要弄懂的地方,就是要把那兩文章的兩張圖吃透,那么我們就已經把原型鏈吃透大半了。
最后倘若大家還有什么不懂的地方,或者博主有什么遺漏的地方,歡迎大家指出交流。如有興趣可以持續關注本博主。
原創文章,轉載請注明出處
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94649.html
摘要:但是該方法有個缺點就是看不出該對象的類型,于是乎構造函數模式應運而生。當然,如果細心的朋友應該會發現函數名首字母大寫了,這是約定在構造函數時將首字母大寫。這時候,聰明的人應該都可以想到,將構造函數模式和原型模式組合起來就可以了。 一.什么是js對象 1.簡單理解js對象 在了解原型鏈之前,我們先要弄清楚什么是JavaScript的對象,JavaScript對象又由哪些組成。有人說一個程...
摘要:函數式編程,一看這個詞,簡直就是學院派的典范。所以這期周刊,我們就重點引入的函數式編程,淺入淺出,一窺函數式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數式編程就是關于如使用通用的可復用函數進行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數式編程(Functional Programming),一...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復雜性。寫一個符合規范并可配合使用的寫一個符合規范并可配合使用的理解的工作原理采用回調函數來處理異步編程。 JavaScript怎么使用循環代替(異步)遞歸 問題描述 在開發過程中,遇到一個需求:在系統初始化時通過http獲取一個第三方服務器端的列表,第三方服務器提供了一個接口,可通過...
摘要:下圖給出一個簡單的列表圖什么是哈希和哈希值為理解挖礦的代碼機制,首先解決幾個概念。第一個就是哈希。哈希值為十六進制表示的數,且長度固定。也正是哈希值的這些特點,賦予了其加密信息時更高的安全性。 第四期 挖礦的相關算法(2) 卡酷少Wechat:13260325501 看過(1)篇,相信你一定對挖礦的機制有了一點了解。那么本篇,我們來一起看一下挖礦中涉及的算法。 在本篇文章中,如果在...
摘要:導語網上有很多自稱能實現移除注釋的正則表達式,實際上存在種種缺陷。為了避免正則的記憶功能,都使用了正則字面量進行測試。下面是去除單行注釋的最終代碼。修改方式將刪除注釋的正則改為。無法正確的移除引號塊。 導語 網上有很多自稱能實現移除JS注釋的正則表達式,實際上存在種種缺陷。這使人多少有些愕然,也不禁疑惑到:真的可以用正則實現嗎?而本篇文章以使用正則移除JS注釋為目標,通過實踐,由淺及深...
閱讀 2804·2021-11-24 09:39
閱讀 2777·2021-09-23 11:45
閱讀 3404·2019-08-30 12:49
閱讀 3352·2019-08-30 11:18
閱讀 1908·2019-08-29 16:42
閱讀 3344·2019-08-29 16:35
閱讀 1321·2019-08-29 11:21
閱讀 1912·2019-08-26 13:49