JavaScript 繼承詳解及示例代碼
有些知識(shí)當(dāng)時(shí)實(shí)在看不懂的話,可以先暫且放下,留在以后再看也許就能看懂了。
幾個(gè)月前,抱著《JavaScript 高級(jí)程序設(shè)計(jì)(第三版)》,啃完創(chuàng)建對(duì)象,就開(kāi)始啃起了 繼承 ,然而啃完 原型鏈 就實(shí)在是看不下去了,腦子越來(lái)越亂,然后就把它扔一邊了,繼續(xù)看后面的。現(xiàn)在利用這個(gè)暑假搞懂了這個(gè)繼承,就把筆記整理一下啦。
原型鏈(Prototype Chaining)
先看一篇文章,文章作者講的非常不錯(cuò),并且還配高清套圖哦。lol…
鏈接: [學(xué)習(xí)筆記](méi) 小角度看JS原型鏈
從原文中小摘幾句
- 構(gòu)造函數(shù)通過(guò) prototype 屬性訪問(wèn)原型對(duì)象
- 實(shí)例對(duì)象通過(guò) [[prototype]] 內(nèi)部屬性訪問(wèn)原型對(duì)象,瀏覽器實(shí)現(xiàn)了 proto 屬性用于實(shí)例對(duì)象訪問(wèn)原型對(duì)象
- 一切對(duì)象都是Object的實(shí)例,一切函數(shù)都是Function的實(shí)例
- Object 是構(gòu)造函數(shù),既然是函數(shù),那么就是Function的實(shí)例對(duì)象;Function是構(gòu)造函數(shù),但Function.prototype是對(duì)象,既然是對(duì)象,那么就是Object的實(shí)例對(duì)象
確定原型與實(shí)例的關(guān)系
有兩種方法來(lái)檢測(cè)原型與實(shí)例的關(guān)系:
instanceof :判斷該對(duì)象是否為另一個(gè)對(duì)象的實(shí)例
instanceof 內(nèi)部運(yùn)算機(jī)理如下:
functioninstance_of(L, R){//L 表示左表達(dá)式,R 表示右表達(dá)式 varO = R.prototype;// 取 R 的顯示原型 L = L.__proto__; // 取 L 的隱式原型 while(true) { if(L ===null) returnfalse; if(O === L)// 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true returntrue; L = L.__proto__; } }
上面代碼摘自: JavaScript instanceof 運(yùn)算符深入剖析
isPrototypeOf() :測(cè)試一個(gè)對(duì)象是否存在于另一個(gè)對(duì)象的原型鏈上
這兩個(gè)方法的不同點(diǎn)請(qǐng)參看: JavaScript isPrototypeOf vs instanceof usage
只利用原型鏈實(shí)現(xiàn)繼承
缺點(diǎn):1. 引用類(lèi)型值的原型屬性會(huì)被實(shí)例共享; 2. 在創(chuàng)建子類(lèi)型的實(shí)例時(shí),不能向超類(lèi)型的構(gòu)造函數(shù)中傳遞參數(shù)
functionFather(){ this.name ="father"; this.friends = ['aaa','bbb']; } functionSon(){ } Son.prototype = newFather(); Son.prototype.constructor = Son; vars1 =newSon(); vars2 =newSon(); console.log(s1.name);// father console.log(s2.name);// father s1.name = "son"; console.log(s1.name);// son console.log(s2.name);// father console.log(s1.friends);// ["aaa", "bbb"] console.log(s2.friends);// ["aaa", "bbb"] s1.friends.push('ccc','ddd'); console.log(s1.friends);// ["aaa", "bbb", "ccc", "ddd"] console.log(s2.friends);// ["aaa", "bbb", "ccc", "ddd"]
只利用構(gòu)造函數(shù)實(shí)現(xiàn)繼承
實(shí)現(xiàn)方法:在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型構(gòu)造函數(shù)(使用 apply() 和 call() 方法)
優(yōu)點(diǎn):解決了原型中引用類(lèi)型屬性的問(wèn)題,并且子類(lèi)可以向超類(lèi)中傳參
缺點(diǎn):子類(lèi)實(shí)例無(wú)法訪問(wèn)父類(lèi)(超類(lèi))原型中定義的方法,所以函數(shù)復(fù)用就無(wú)從談起了。
functionFather(name,friends){ this.name = name; this.friends = friends; } Father.prototype.getName = function(){ returnthis.name; }; functionSon(name){ // 注意: 為了確保 Father 構(gòu)造函數(shù)不會(huì)重寫(xiě) Son 構(gòu)造函數(shù)的屬性,請(qǐng)將調(diào)用 Father 構(gòu)造函數(shù)的代碼放在 Son 中定義的屬性的前面。 Father.call(this,name,['aaa','bbb']); this.age =22; } vars1 =newSon('son1'); vars2 =newSon('son2'); console.log(s1.name);// son1 console.log(s2.name);// son2 s1.friends.push('ccc','ddd'); console.log(s1.friends);// ["aaa", "bbb", "ccc", "ddd"] console.log(s2.friends);// ["aaa", "bbb"] // 子類(lèi)實(shí)例無(wú)法訪問(wèn)父類(lèi)原型中的方法 s1.getName(); // TypeError: s1.getName is not a function s2.getName(); // TypeError: s2.getName is not a function
組合繼承(Combination Inheritance)
實(shí)現(xiàn)方法:使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。
functionFather(name,friends){ this.name = name; this.friends = friends; } Father.prototype.money = "100k $"; Father.prototype.getName = function(){ console.log(this.name); }; functionSon(name,age){ // 繼承父類(lèi)的屬性 Father.call(this,name,['aaa','bbb']); this.age = age; } // 繼承父類(lèi)原型中的屬性和方法 Son.prototype = newFather(); Son.prototype.constructor = Son; Son.prototype.getAge = function(){ console.log(this.age); }; vars1 =newSon('son1',12); s1.friends.push('ccc'); console.log(s1.friends);// ["aaa", "bbb", "ccc"] console.log(s1.money);// 100k $ s1.getName(); // son1 s1.getAge(); // 12 vars2 =newSon('son2',24); console.log(s2.friends);// ["aaa", "bbb"] console.log(s2.money);// 100k $ s2.getName(); // son2 s2.getAge(); // 24
組合繼承避免了單方面使用原型鏈或構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)繼承的缺陷,融合了它們的優(yōu)點(diǎn),成為 JavaScript 中最常用的繼承模式,但是它也是有缺陷的,組合繼承的缺陷會(huì)在后面專(zhuān)門(mén)提到。
原型式繼承(Prototypal Inheritance)
實(shí)現(xiàn)思路:借助原型基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)不必因此而創(chuàng)建自定義類(lèi)型。
為了達(dá)到這個(gè)目的,引入了下面的函數(shù)(obj)
functionobj(o){ functionF(){} F.prototype = o; returnnewF(); } varperson1 = { name: "percy", friends: ['aaa','bbb'] }; varperson2 = obj(person1); person2.name = "zyj"; person2.friends.push('ccc'); console.log(person1.name);// percy console.log(person2.name);// zyj console.log(person1.friends);// ["aaa", "bbb", "ccc"] console.log(person2.friends);// ["aaa", "bbb", "ccc"] ECMAScript 5 通過(guò)新增 Object.create() 方法規(guī)范化了原型式繼承。在傳入一個(gè)參數(shù)的情況下, Object.create() 和 obj() 方法的行為相同。 varperson1 = { name: "percy", friends: ['aaa','bbb'] }; varperson2 =Object.create(person1); person2.name = "zyj"; person2.friends.push('ccc'); console.log(person1.name);// percy console.log(person2.name);// zyj console.log(person1.friends);// ["aaa", "bbb", "ccc"] console.log(person2.friends);// ["aaa", "bbb", "ccc"]
在沒(méi)有必要興師動(dòng)眾地創(chuàng)建構(gòu)造函數(shù),而只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類(lèi)似的情況下,可以選擇使用這種繼承。
寄生式繼承(Parasitic Inheritance)
寄生式繼承是與原型式繼承緊密相關(guān)的一種思路。
實(shí)現(xiàn)思路:創(chuàng)建一個(gè)僅僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部以某種方式來(lái)增強(qiáng)對(duì)象,最后再返回對(duì)象。
functionobj(o){ functionF(){} F.prototype = o; returnnewF(); } functioncreatePerson(original){// 封裝繼承過(guò)程 varclone = obj(original);// 創(chuàng)建對(duì)象 clone.showSomething = function(){// 增強(qiáng)對(duì)象 console.log("Hello world!"); }; returnclone;// 返回對(duì)象 } varperson = { name: "percy" }; varperson1 = createPerson(person); console.log(person1.name);// percy person1.showSomething(); // Hello world!
寄生組合式繼承(Parasitic Combination Inheritance)
先來(lái)說(shuō)說(shuō)我們前面的組合繼承的缺陷。 組合繼承最大的問(wèn)題就是無(wú)論什么情況下,都會(huì)調(diào)用兩次父類(lèi)的構(gòu)造函數(shù):一次是創(chuàng)建子類(lèi)的原型的時(shí)候,另一次是在調(diào)用子類(lèi)構(gòu)造函數(shù)的時(shí)候,在子類(lèi)構(gòu)造函數(shù)內(nèi)部又調(diào)用了父類(lèi)的構(gòu)造函數(shù)。
functionFather(name,friends){ this.name = name; this.friends = friends; } Father.prototype.money = "100k $"; Father.prototype.getName = function(){ console.log(this.name); }; functionSon(name,age){ // 繼承父類(lèi)的屬性 Father.call(this,name,['aaa','bbb']);// 第二次調(diào)用 Father() , 實(shí)際是在 new Son() 時(shí)才會(huì)調(diào)用 this.age = age; } // 繼承父類(lèi)原型中的屬性和方法 Son.prototype = newFather();// 第一次調(diào)用 Father() Son.prototype.constructor = Son;
第一次調(diào)用使的子類(lèi)的原型成了父類(lèi)的一個(gè)實(shí)例,從而子類(lèi)的原型得到了父類(lèi)的實(shí)例屬性;第二次調(diào)用會(huì)使得子類(lèi)的實(shí)例也得到了父類(lèi)的實(shí)例屬性;而子類(lèi)的實(shí)例屬性默認(rèn)會(huì)屏蔽掉子類(lèi)原型中與其重名的屬性。所以,經(jīng)過(guò)這兩次調(diào)用, 子類(lèi)原型中出現(xiàn)了多余的的屬性 ,從而引進(jìn)了寄生組合式繼承來(lái)解決這個(gè)問(wèn)題。
寄生組合式繼承的背后思路是: 不必為了指定子類(lèi)的原型而調(diào)用父類(lèi)的構(gòu)造函數(shù),我們所需要的無(wú)非就是父類(lèi)原型的一個(gè)副本而已 。
本質(zhì)上,就是使用寄生式繼承來(lái)繼承父類(lèi)的原型,然后將結(jié)果返回給子類(lèi)的原型。
functionobj(o){ functionF(){} F.prototype = o; returnnewF(); } functioninheritPrototype(son,father){ varprototype = obj(father.prototype);// 創(chuàng)建對(duì)象 prototype.constructor = son; // 增強(qiáng)對(duì)象 son.prototype = prototype; // 返回對(duì)象 } functionFather(name,friends){ this.name = name; this.friends = friends; } Father.prototype.money = "100k $"; Father.prototype.getName = function(){ console.log(this.name); }; functionSon(name,age){ // 繼承父類(lèi)的屬性 Father.call(this,name,['aaa','bbb']); this.age = age; } // 使用寄生式繼承繼承父類(lèi)原型中的屬性和方法 inheritPrototype(Son,Father); Son.prototype.getAge = function(){ console.log(this.age); }; vars1 =newSon('son1',12); s1.friends.push('ccc'); console.log(s1.friends);// ["aaa", "bbb", "ccc"] console.log(s1.money);// 100k $ s1.getName(); // son1 s1.getAge(); // 12 vars2 =newSon('son2',24); console.log(s2.friends);// ["aaa", "bbb"] console.log(s2.money);// 100k $ s2.getName(); // son2 s2.getAge(); // 24
優(yōu)點(diǎn):使子類(lèi)原型避免了繼承父類(lèi)中不必要的實(shí)例屬性。
開(kāi)發(fā)人員普遍認(rèn)為寄生組合式繼承是實(shí)現(xiàn)基于類(lèi)型繼承的最理想的繼承方式。
最后
最后,強(qiáng)烈推薦兩篇很硬的文章
Javascript – How Prototypal Inheritance really works
JavaScript's Pseudo Classical Inheritance diagram (需要翻墻)
摘第二篇文章的一張硬圖過(guò)來(lái):
看完之后,秒懂原型鏈,有木有?
以上就是對(duì)JavaScript 繼承的資料整理,后續(xù)繼續(xù)補(bǔ)充相關(guān)資料謝謝大家對(duì)本站的支持!
- 詳解Java中用于國(guó)際化的locale類(lèi)
- Java的Struts框架中配置國(guó)際化的資源存儲(chǔ)的要點(diǎn)解析
- Java的Struts框架中的主題模板和國(guó)際化設(shè)置
- java按字節(jié)截取帶有漢字的字符串的解法(推薦)
- java指紋識(shí)別以及谷歌圖片識(shí)別技術(shù)源碼
- 快速學(xué)習(xí)JavaWeb中監(jiān)聽(tīng)器(Listener)的使用方法
- 深入學(xué)習(xí)JavaWeb中監(jiān)聽(tīng)器(Listener)的使用方法
- 基于JavaScript實(shí)現(xiàn)購(gòu)物網(wǎng)站商品放大鏡效果
- JavaScript中push(),join() 函數(shù) 實(shí)例詳解
- Java 中的字符串常量池詳解
- Java Socket編程詳解及示例代碼
- javaweb 國(guó)際化:DateFormat,NumberFormat,MessageFormat,ResourceBundle的使用
相關(guān)文章
JavaScript實(shí)現(xiàn)點(diǎn)擊文字切換登錄窗口的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)點(diǎn)擊文字切換登錄窗口的方法,涉及javascript操作div層及相關(guān)樣式的技巧,需要的朋友可以參考下2015-05-05關(guān)于微信小程序map組件z-index的層級(jí)問(wèn)題分析
這篇文章主要給大家介紹了關(guān)于微信小程序map組件z-index的層級(jí)問(wèn)題的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用微信小程序具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07setTimeout()與setInterval()方法區(qū)別介紹
計(jì)時(shí)器setTimeout()和setInterval()兩個(gè)都是js的計(jì)時(shí)功能的函數(shù)兩個(gè)有些區(qū)別,下面為大家簡(jiǎn)單介紹下,希望對(duì)大家有所幫助2013-12-12JS、jquery實(shí)現(xiàn)幾分鐘前、幾小時(shí)前、幾天前等時(shí)間差顯示效果的代碼實(shí)例分享
在新浪微博首頁(yè)看到每條微博后邊顯示的時(shí)間并不是標(biāo)準(zhǔn)的年-月-日格式,而是經(jīng)過(guò)換算的時(shí)間差,如:發(fā)表于5分鐘前、發(fā)表于“2小時(shí)前”,比起標(biāo)準(zhǔn)的時(shí)間顯示格式,貌似更加直觀和人性化2014-04-04