摘要:每個類有三部分構(gòu)成第一部分是構(gòu)造函數(shù)內(nèi),供實例對象化復(fù)制用。第二部分是構(gòu)造函數(shù)外,直接通過點語法添加,供類使用,實例化對象訪問不到。組合繼承還有一個要注意的地方在代碼處,將子類原型的屬性指向子類的構(gòu)造函數(shù)。
前言
前一陣面試,過程中發(fā)現(xiàn)問到一些很基礎(chǔ)的問題時候,自己并不能很流暢的回答出來?;蛘哂龅揭恍┗A(chǔ)知識的應(yīng)用,由于對這些點理解的不是很深入,拿著筆居然什么都寫不出來,于是有了回顧一下這些基礎(chǔ)知識的想法。
首先就是面試中經(jīng)常會問到的,JS是怎么實現(xiàn)繼承的,其實問到繼承,面試官想問的可能還是你對JS面向?qū)ο蟮睦斫獍伞?/p>
這一部分的主要參考資料:《JavaScript高級程序設(shè)計》、《JavaScript設(shè)計模式》
如果有什么錯誤的地方,也希望看到這篇文章的小伙伴給我指出來,謝謝 ^_^
Javascript是一種基于對象(object-based)的語言,你遇到的所有東西幾乎都是對象。
一個簡單的對象創(chuàng)建:
var People = { name : "eavan", age : 24, getName : function(){ alert(this.name); //eavan } }
使用的時候就可以用People.name,獲取People這個對象的name屬性,或者是People.getName()來得到People的name值。
另一種對象創(chuàng)建方式:
var People = new Object(); People.name = "eavan"; People.age = 24; People.getName = function(){ alert(this.name); }
這里用到了new,就順便提一下在使用new的時候發(fā)生了什么,其實在使用new的時候,大致可以認為做了這三件事,看下面的代碼:
var People = {}; //我們創(chuàng)建了一個空對象People People.__proto__ = Object.prototype; //我們將這個空對象的__proto__成員指向了Object函數(shù)對象prototype成員對象 Object.call(People); //我們將Object函數(shù)對象的this指針替換成People,然后再調(diào)用Object函數(shù)1.2封裝
簡單來說就是對一些屬性的隱藏域暴露,比如私有屬性、私有方法、共有屬性、共有方法、保護方法等等。而js也能實現(xiàn)私有屬性、私有方法、共有屬性、共有方法等等這些特性。
像java這樣的面向?qū)ο蟮木幊陶Z言一般會有一個類的概念,從而實現(xiàn)封裝。而javascript中沒有類的概念,JS中實現(xiàn)封裝主要還是靠函數(shù)。
首先聲明一個函數(shù)保存在一個變量里面。然后在這個函數(shù)(類)的內(nèi)部通過對this變量添加屬性或者方法來實現(xiàn)對類添加屬相或者方法。
var Person = function(){ var name = "eavan"; //私有屬性 function checkName(){}; //私有方法 this.myName = "gaof"; //對象共有屬性 this.myFriends = ["aa","bb","cc"]; this.copy = function(){} //對象共有方法 this.getName = function(){ //構(gòu)造器方法 return name; }; }
純構(gòu)造函數(shù)封裝數(shù)據(jù)的問題是:對像this.copy = function(){}這種方法的創(chuàng)建,其實在執(zhí)行的時候大可不必綁定到特定的對象上去,將其定義到全局變量上也是一樣的,而且其過程相當(dāng)于實例化了一個Function,也大可不必實例化這么多其實干同一件事的方法。而這個小問題的解決可以用原型模式來解決。
1.3理解原型在每創(chuàng)建一個函數(shù)的時候,都會生成一個prototype屬性,這個屬性指向函數(shù)的原型對象。而其是用來包含特定類型的所有實例共享的屬性和方法。所以,直接添加在原型中的實例和方法,就會被所有實例所共享。
同樣還是上面的Person的例子,我們可以為其原型添加新的屬性和方法。
Person.isChinese = true; //類的靜態(tài)共有屬性(對象不能訪問) Person.prototype.sex = "man" ; //類的共有屬性 Person.prototype.frends = ["gao","li","du"]; Person.prototype.isBoy = function(){}; //類的共有方法
原型封裝數(shù)據(jù)的問題:對綁定在prototype上的引用類型的變量,由于被所有對象所共有,其中某一個對象對該數(shù)據(jù)進行修改,當(dāng)別的對象訪問該數(shù)據(jù)的時候,所訪問到的值就是被修改后的。
比如如下代碼:
var person1 = new Person(); person1.frends.push("dd"); console.log(person1.frends); //["gao", "li", "du", "dd"] var person2 = new Person(); person2.frends.push("ee"); console.log(person2.frends); //["gao", "li", "du", "dd", "ee"]
原本希望對person1和person2的friends屬性分別添加新的內(nèi)容,結(jié)果二者的friends屬性居然是“公用”的!
綜上,最常見的方式應(yīng)該是組合使用構(gòu)造函數(shù)和原型模式,構(gòu)造函數(shù)用于定義實例屬性,原型模式用于定義方法和共享的屬性。
每個類有三部分構(gòu)成:第一部分是構(gòu)造函數(shù)內(nèi),供實例對象化復(fù)制用。第二部分是構(gòu)造函數(shù)外,直接通過點語法添加,供類使用,實例化對象訪問不到。第三部分是類的原型中,實例化對象可以通過其原型鏈間接訪問到,也是為所有實例化對象所共用。
在說到對象實例的屬性的時候,我們有一個問題,就是在訪問一個屬性的時候,這個屬性是屬于實例,還是屬于這個實例的原型的呢?
比如還是上面的例子,我們?yōu)閜erson2實例增加一個sex屬性,這時候訪問person2的sex屬性時,得到的是我們增加的值。說明為對象實例添加一個屬性的時候,這個屬性就會屏蔽原型對象中保存的同名屬性。
person2.sex = "woman"; console.log(person1.sex); //man console.log(person2.sex); //woman
這個時候我們可以使用hasOwnProperty()方法來檢測一個屬性是存在于實例中,還是存在于原型中。如果實例中有這個屬性,hasOwnProperty()會返回true,而hasOwnProperty()并不會感知到原型中的屬性。所以可以用這個方法檢測屬性到底是存在于實例中還是原型中。
console.log(person1.hasOwnProperty("sex")); //原型中的屬性,返回false console.log(person2.hasOwnProperty("sex")); //實例中的屬性,返回true二、繼承
ECMAScript中描述了原型鏈的概念,并將原型鏈作為實現(xiàn)繼承的主要方法。其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
2.1 原型鏈繼承如下代碼:
function Super(){ this.val = true; this.arr = ["a"]; } function Sub(){ //... } Sub.prototype = new Super(); var sub = new Sub(); console.log(sub.val) //true
以上代碼定義了Super和Sub兩個類型,繼承的核心就一句話:Sub.prototype = new Super() 將父類的一個實例賦給子類的原型。這樣子類就能夠使用父類實例所擁有的方法和父類原型中的方法。
這種情況要想給子類添加自己的方法或者是覆蓋父類中某個方法的時候,一定要在放在替換原型語句后面。否則寫在原型上的方法都會丟失。
而且在給子類添加新方法的時候,不能使用字面量的方式添加新方法,這樣會導(dǎo)致繼承無效。
如:
Sub.prototype = new Super(); Sub.prototype = { //錯誤的方式 getVal : function(){ //... } }
以上代碼剛剛把Super的實例賦給原型,緊接著又將原信替換成一個對象字面量,導(dǎo)致現(xiàn)在原型包含的是一個Object的實例,并非Super的實例,因此原型鏈被切斷了,Sub和Super已經(jīng)沒有關(guān)系了。
原型鏈的問題:
最主要的問題有兩個:一是由于引用類型的原型屬性會被所有實例所共享,所以通過原型鏈繼承時,原型變成了另一個類型的實例,原先的實例屬性也就變成了現(xiàn)在的原型屬性,如下代碼:
function Super(){ this.friends = ["peng","gao"]; } function Sub(){ //... } Sub.prototype = new Super(); var sub1 = new Sub(); var sub2 = new Sub(); sub1.friends.push("du"); console.log(sub2.friends); //["peng", "gao", "du"]
這個例子說明的就是上面的問題,子類的所有實例共享了父類中的引用類型屬性。
原型鏈繼承的另一個問題是在創(chuàng)建子類行的實例的時候,沒法向父類的構(gòu)造函數(shù)傳遞參數(shù)。
2.2 構(gòu)造函數(shù)繼承具體實現(xiàn):
function Super(){ this.val = true; this.arr = ["a"]; } function Sub(){ Super.call(this); } var sub = new Sub(); console.log(sub.val) //true
這種模式這是解決了原型鏈繼承中出現(xiàn)的兩個問題,它可以傳遞參數(shù),也沒有了子類共享父類引用屬性的問題。
但這種模式也有他的問題,那就是在父類原型中定義的方法,其實是對子類不可見的。
既然上述的兩種方式各有各自的局限性,將它倆整合到一起是不是會好一點呢,于是就有了組合繼承。
function Super(){ this.val = true; this.arr = ["a"]; } function Sub(){ Super.call(this); //{2} } Sub.prototype = new Super(); //{1} Sub.prototype.constructor = Sub; //{3} var sub = new Sub(); console.log(sub.val) //true
組合繼承還有一個要注意的地方:
在代碼{3}處,將子類原型的constructor屬性指向子類的構(gòu)造函數(shù)。因為如果不這么做,子類的原型是父類的一個實例,所以子類原型的constructor屬性就丟失了,他會順著原型鏈繼續(xù)往上找,于是就找到了父類的constructor所以它指向的其實是父類。
這種繼承方式是使用最多的一種方式。
這種繼承方式解決了上兩種方式的缺點,不會出現(xiàn)共享引用類型的問題,同時父類原型中的方法也被繼承了下來。
如果要說起有什么缺點我們發(fā)現(xiàn),在執(zhí)行代碼{1}時,Sub.prototype會得到父類型的val和arr兩個屬性。他們是Super的實例屬性,只不過現(xiàn)在在Sub的原型上。而代碼{2}處,在創(chuàng)建Sub實例的時候,調(diào)用Super的構(gòu)造函數(shù),又會在新的對象上創(chuàng)建屬性val和arr,于是,這兩個屬性就屏蔽了原型中兩個同名屬性。
2.4寄生組合式繼承對于上面的問題,我們也有解決辦法,不是在子類原型中多了一份父類的屬性和方法么,那我原型中就只要父類原型中的屬性和方法,這里我們引入了一個方法:
function inheritObject(obj){ var F = function(){}; F.prototype = obj; return new F(); }
這個方法創(chuàng)建了一個對象臨時性的構(gòu)造函數(shù),然后將傳入的對象作為這個構(gòu)造函數(shù)的原型,最后返回這個臨時類型的一個新實例。
我們可以設(shè)想,如果用這個方法拷貝一份父類的原型屬性給子類,是不是就避免了上面提到的子類原型中多了一份父類構(gòu)造函數(shù)內(nèi)的屬性??慈缦麓a:
function Super(){ this.val = 1; this.arr = [1]; } Super.prototype.fun1 = function(){}; Super.prototype.fun2 = function(){}; function Sub(){ Super.call(this); } var p = inheritObject(Super.prototype); //{1} p.constructor = Sub; //{2} Sub.prototype = p; //{3} var sub = new Sub();
基本思路就是:不必為了指定子類型的原型而調(diào)用父類的夠著函數(shù),我們需要的無非就是父類原型的一個副本而已。本質(zhì)上就是復(fù)制出父類的一個副本,然后再將結(jié)果指定給子類型的原型。
三、多態(tài)所謂多態(tài),就是同一個方法的多種調(diào)用方式,在javascript中,通過arguments對象對傳入的參數(shù)做判斷就可以實現(xiàn)多種調(diào)用方式。
例子:
function Add(){ function zero(){ return 10; } function one(num){ return 10 + num; } function two(num1, num2){ return num1 + num2; } this.add = function(){ var arg = arguments, len = arg.length; switch (len){ case 0: return zero(); case 1: return one(arg[0]); case 2: return two(arg[0], arg[1]); } } } var A = new Add(); console.log(A.add()); //10 console.log(A.add(5)); //15 console.log(A.add(6, 7)); //13
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88433.html
摘要:站在這個時間點上,我對自己之前三次失敗的面試經(jīng)歷做了一次深度回顧。關(guān)于我第三次面試失敗的經(jīng)歷,依然是與輪播圖有關(guān)。當(dāng)然,這次思特奇面試之旅,最后也是以失敗告終,這也是我離進大廠最近的一次。 showImg(https://segmentfault.com/img/bVYQuP?w=528&h=513); 前言 時間的齒輪已經(jīng)來到了2017年的11月份,距離2018年僅僅還剩下不到兩...
摘要:站在這個時間點上,我對自己之前三次失敗的面試經(jīng)歷做了一次深度回顧。關(guān)于我第三次面試失敗的經(jīng)歷,依然是與輪播圖有關(guān)。當(dāng)然,這次思特奇面試之旅,最后也是以失敗告終,這也是我離進大廠最近的一次。 showImg(https://segmentfault.com/img/bVYQuP?w=528&h=513); 前言 時間的齒輪已經(jīng)來到了2017年的11月份,距離2018年僅僅還剩下不到兩...
摘要:并嘗試用為什么你統(tǒng)計的方式是錯的掘金翻譯自工程師的文章。正如你期望的,文中的前端開發(fā)單一職責(zé)原則前端掘金單一職責(zé)原則又稱單一功能原則,面向?qū)ο笪鍌€基本原則之一。 單頁式應(yīng)用性能優(yōu)化 - 首屏數(shù)據(jù)漸進式預(yù)加載 - 前端 - 掘金前言 針對首頁和部分頁面打開速度慢的問題,我們開始對單頁式應(yīng)用性能進行優(yōu)化。本文介紹其中一個方案:基于 HTTP Chunk 的首屏數(shù)據(jù)漸進式預(yù)加載方案,該方案總...
摘要:前言很認真的說吧,在和騰訊面試官的面試的過程。騰訊二面自我介紹二面的面試官和一面不是同一個面試官,所以在這個時候,我的基本介紹還是和一面一樣,介紹自己的基本信息,以及怎么想到學(xué)習(xí)前端和怎么學(xué)習(xí)前端。 前言 很認真的說吧,在和騰訊面試官的面試的過程。有點感覺是在聊天一樣,他們是面試官,但是感覺更像是引路人,不管結(jié)果的好壞,在騰訊面試的過程,只要你認真去聽去問,就可以學(xué)到很多東西吧。 如果...
閱讀 2812·2023-04-25 18:06
閱讀 2609·2021-11-22 09:34
閱讀 1699·2021-11-08 13:16
閱讀 1325·2021-09-24 09:47
閱讀 3065·2019-08-30 15:44
閱讀 2787·2019-08-29 17:24
閱讀 2600·2019-08-23 18:37
閱讀 2452·2019-08-23 16:55