摘要:因?yàn)槲覀冇眠@個(gè)函數(shù)來構(gòu)造對(duì)象,所以我們也把稱作構(gòu)造函數(shù)。所以通過定義構(gòu)造函數(shù),就相當(dāng)于定義了一個(gè)類,通過關(guān)鍵字,即可生成一個(gè)實(shí)例化的對(duì)象。
一、序言
??和其他面向?qū)ο蟮恼Z(yǔ)言(如Java)不同,Javascript語(yǔ)言對(duì)類的實(shí)現(xiàn)和繼承的實(shí)現(xiàn)沒有標(biāo)準(zhǔn)的定義,而是將這些交給了程序員,讓程序員更加靈活地(當(dāng)然剛開始也更加頭疼)去定義類,實(shí)現(xiàn)繼承。(以下不討論ES6中利用class、extends關(guān)鍵字來實(shí)現(xiàn)類和繼承;實(shí)質(zhì)上,ES6中的class、extends關(guān)鍵字是利用語(yǔ)法糖實(shí)現(xiàn)的)
Javascript靈活到甚至可以實(shí)現(xiàn)接口的封裝(類似Java中的Interface和implements)。二、類的實(shí)現(xiàn)
1.我對(duì)類的理解
首先,我先說說我對(duì)類的理解:類是包含了一系列【屬性/方法】的集合,可以通過類的構(gòu)造函數(shù)創(chuàng)建一個(gè)實(shí)例對(duì)象(例如人類是一個(gè)類,而每一個(gè)人就是一個(gè)實(shí)例對(duì)象),而這個(gè)實(shí)例對(duì)象中會(huì)包含兩方面內(nèi)容:
a.類的所有非靜態(tài)【屬性/方法】
非靜態(tài)【屬性/方法】就是每一個(gè)實(shí)例所特有的,屬于個(gè)性。(例如每個(gè)人的名字都不相同,而名字這個(gè)屬性就是一個(gè)非靜態(tài)屬性)
b.類的所有靜態(tài)【屬性/方法】
靜態(tài)【屬性/方法】就是每一個(gè)實(shí)例所共享的,屬于共性。(例如每個(gè)人都要吃飯,而吃飯這個(gè)方法就是一個(gè)非靜態(tài)方法)
2.Javascript對(duì)類的實(shí)現(xiàn)
a.利用函數(shù)創(chuàng)建類,利用new關(guān)鍵字生成實(shí)例對(duì)象
(話不多說,先上代碼,以下沒有特別說明的話,我都會(huì)先上代碼,然后進(jìn)行解釋說明)
// 代碼2.2.a function Human() { console.log("create human here") } var fakeperson = Human() // undefined var person = new Human() // {}
這里Human既是一個(gè)普通函數(shù),也是一個(gè)類的構(gòu)造函數(shù),當(dāng)調(diào)用Human()的時(shí)候,它作為一個(gè)普通函數(shù)會(huì)被執(zhí)行,會(huì)輸出create human here,但是沒有返回值(即返回undefined);而當(dāng)調(diào)用new Human()時(shí),也會(huì)輸出create human here并且返回一個(gè)對(duì)象。因?yàn)槲覀冇肏uman這個(gè)函數(shù)來構(gòu)造對(duì)象,所以我們也把Human稱作構(gòu)造函數(shù)。所以通過定義構(gòu)造函數(shù),就相當(dāng)于定義了一個(gè)類,通過new關(guān)鍵字,即可生成一個(gè)實(shí)例化的對(duì)象。
b.利用構(gòu)造函數(shù)實(shí)現(xiàn)非靜態(tài)【屬性/方法】
// 代碼2.2.b function Human(name) { this.name = name } var person_1 = new Human("Jack") var person_2 = new Human("Rose") console.log(person_1.name) // Jack console.log(person_2.name) // Rose
這里的Human構(gòu)造函數(shù)中多了一個(gè)參數(shù)并且函數(shù)體中多了一句this.name = name,這句話的中的this指針指向new關(guān)鍵字返回的實(shí)例化對(duì)象,所以根據(jù)構(gòu)造函數(shù)參數(shù)的不同,其生成的對(duì)象中的具有的屬性name的值也會(huì)不同。而這里的name就是這個(gè)類的 非靜態(tài)【屬性/方法】
c.利用prototype實(shí)現(xiàn)靜態(tài)【屬性/方法】
這里因?yàn)橐玫皆玩湹闹R(shí),所以放到原型鏈后面說。
1.類的prototype是什么?
在Javascript中,每當(dāng)我們定義一個(gè)構(gòu)造函數(shù),Javascript引擎就會(huì)自動(dòng)為這個(gè)類中添加一個(gè)prototype(也被稱作原型)
2.對(duì)象的__proto__是什么?
在Javascript中,每當(dāng)我們使用new創(chuàng)建一個(gè)對(duì)象時(shí),Javascript引擎就會(huì)自動(dòng)為這個(gè)對(duì)象中添加一個(gè)__proto__屬性,并讓其指向其類的prototype
// 代碼3.2 function Human(name) { this.name = name } console.log(Human.prototype) var person_test1 = new Human("Test1") var person_test2 = new Human("Test2") console.log(person_test1.__proto__) console.log(person_test2.__proto__) console.log(Human.prototype === person_test1.__proto__) // true console.log(Human.prototype === person_test2.__proto__) // true
我們會(huì)發(fā)現(xiàn)Human.prototype是一個(gè)對(duì)象,Human類的實(shí)例化對(duì)象person_test1、person_test2下都有一個(gè)屬性__proto__也是對(duì)象,并且它們都等于Human.prototype,我們知道在Javascript中引用類型的相等意味著他們所指向的是同一個(gè)對(duì)象。所以我們可以得到結(jié)論,任何一個(gè)實(shí)例化對(duì)象的__proto__屬性都指向其類的prototype。
3.對(duì)象的__proto__有什么作用?
// 代碼3.3 var Pproto = { name:"jack" } var person = { __proto__:Pproto } console.log(person.name) // jack person.name = "joker" console.log(person.name) // joker
我們發(fā)現(xiàn)最開始我們并沒有給person定義name屬性,為什么console出來jack呢?這就是Javascript著名的原型鏈的結(jié)果啦。話不多說,先上圖:
當(dāng)我們?cè)L問person.name時(shí),發(fā)生了什么呢?
首先它會(huì)訪問person對(duì)象本身的屬性,如果本身沒有定義name屬性的話,它會(huì)去尋找它的__proto__屬性對(duì)象,在這個(gè)例子中person的__proto__屬性對(duì)應(yīng)的是Pproto對(duì)象,所以person的__proto__指向了Pproto,然后我們發(fā)現(xiàn)Pproto對(duì)象是具有name屬性的,那么person.name就到此為止,返回了jack,但是如果我們又給person加上了一個(gè)自身的屬性name呢?這時(shí),再次person.name就不會(huì)再尋找__proto__了,因?yàn)閜erson本身已經(jīng)具有了name屬性,而且其值為joker,所以這里會(huì)返回joker.
我們注意到上圖中Pproto的__proto__指向了Object,這是因?yàn)槊恳粋€(gè)通過字面量的方式創(chuàng)建出來的對(duì)象它們都默認(rèn)是Object類的對(duì)象,所以它們的__proto__自然指向Object.prototype。
4.利用prototype實(shí)現(xiàn)靜態(tài)【屬性/方法】
// 代碼3.4 function Human(name) { this.name = name } Human.prototype.eat = function () { console.log("I eat!") } var person_1 = new Human("Jack") var person_2 = new Human("Rose") person_1.eat() // I eat! person_2.eat() // I eat! console.log(person_1.eat === person_2.eat) // true
這里我們?cè)跇?gòu)造函數(shù)外多寫了一句:Human.prototype.eat = function() {...} 這樣以后每個(gè)通過Human實(shí)例化的對(duì)象的__proto__都會(huì)指向Human.prototype,并且根據(jù)上述原型鏈知識(shí),我們可以知道只要構(gòu)造函數(shù)中沒有定義同名的非靜態(tài)【屬性/方法】,那么每個(gè)對(duì)象訪問say方法時(shí),訪問的其實(shí)都是Human.prototype.say方法,這樣我們就利用prototype實(shí)現(xiàn)了類的靜態(tài)【屬性/方法】,所有的對(duì)象實(shí)現(xiàn)了共有的特性,那就是eat
四、繼承的實(shí)現(xiàn)1.我對(duì)繼承的理解
假如有n(n>=2)個(gè)類,他們的一些【屬性/方法】不一樣,但是也有一些【屬性/方法】是相同的,所以我們每次定義它們的時(shí)候都要重復(fù)的去定義這些相同的【屬性/方法】,那樣豈不是很煩?所以一些牛逼的程序員想到,能不能像兒子繼承父親的基因一樣,讓這些類也像“兒子們”一樣去“繼承”他們的“父親”(而這里的父親就是包含他們所具有的相同的【屬性/方法】)。這樣我們就可以多定義一個(gè)類,把它叫做父類,在它的里面包含所有的這些子類所具有的相同的【屬性/方法】,然后通過繼承的方式,讓所有的子類都可以訪問這些【屬性/方法】,而不用每次都在子類的定義中去定義這些【屬性/方法】了。
2.原型鏈實(shí)現(xiàn)繼承(讓子類繼承了父類的靜態(tài)【屬性/方法】)
// 代碼4.1 function Father() { } Father.prototype.say = function() { console.log("I am talking...") } function Son() { } var sonObj_1 = new Son() console.log(sonObj_1.say) // undefined // 原型鏈實(shí)現(xiàn)繼承的關(guān)鍵代碼 Son.prototype = new Father() var sonObj_2 = new Son() console.log(sonObj_2.say) // function() {...}
看到這句Son.prototype = new Father()你可能有點(diǎn)蒙圈,沒關(guān)系,我先上個(gè)原型鏈的圖,你分分鐘就能明白了
對(duì)著圖我們想一想,首先,一開始Son、Father兩個(gè)類沒有什么關(guān)系,所以在訪問say的時(shí)候肯定是undefined,但是當(dāng)我們使用了Son.prototype = new Father()后,我們知道通過new Son()生成的對(duì)象都會(huì)有__proto__屬性,而這個(gè)屬性指向Son.prototype,而這里我們又讓它等于了一個(gè)Father的對(duì)象,而Father類又定義了靜態(tài)方法say,所以這里我們的sonObj_2通過沿著原型鏈尋找,尋找到了say方法,于是就可以訪問到Father類的靜態(tài)方法say了。這樣就實(shí)現(xiàn)了子類繼承了父類的靜態(tài)【屬性/方法】,那么如何讓子類繼承父類的非靜態(tài)【屬性/方法】呢?
3.構(gòu)造函數(shù)實(shí)現(xiàn)繼承(讓子類繼承了父類的非靜態(tài)【屬性/方法】)
// 代碼4.3 function Father(name) { this.name = name } function Son() { Father.apply(this, arguments) this.sing = function() { console.log(this.name + " is singing...") } } var sonObj_1 = new Son("jack") var sonObj_2 = new Son("rose") sonObj_1.sing() // jack is singing... sonObj_2.sing() // rose is singing...
在這個(gè)例子中,通過在Son的構(gòu)造函數(shù)中利用apply函數(shù),執(zhí)行了Father的構(gòu)造函數(shù),所以每一個(gè)Son對(duì)象實(shí)例化的過程中都會(huì)執(zhí)行Father的構(gòu)造函數(shù),從而得到name屬性,這樣,每一個(gè)Son實(shí)例化的Son對(duì)象都會(huì)有不同的name屬性值,于是就實(shí)現(xiàn)了子類繼承了父類的非靜態(tài)【屬性/方法】
4.組合方式實(shí)現(xiàn)繼承(組合 原型鏈繼承 + 構(gòu)造函數(shù)繼承)
顧名思義,就是結(jié)合上述兩種方法,然后同時(shí)實(shí)現(xiàn)對(duì)父類的靜態(tài)及非靜態(tài)【屬性/方法】的繼承,代碼如下:
// 代碼4.4 function Father(name) { this.name = name } Father.prototype.sayName = function() { console.log("My name is " + this.name) } function Son() { Father.apply(this, arguments) } Son.prototype = new Father("father") var sonObj_1 = new Son("jack") var sonObj_2 = new Son("rose") sonObj_1.sayName() // My name is jack sonObj_2.sayName() // My name is rose
這里子類Son沒有一個(gè)自己的方法,它的sayName方法繼承自父類的靜態(tài)方法sayName,構(gòu)造函數(shù)中繼承了父類的構(gòu)造函數(shù)方法,所以得到了非靜態(tài)的name屬性,因此它的實(shí)例對(duì)象都可以調(diào)用靜態(tài)方法sayName,但是因?yàn)樗鼈兏髯缘膎ame不同,所以打印出來的name的值也不同??吹竭@里,大家可能認(rèn)為這已經(jīng)是一種完美無缺的Javascript的繼承方式了,但是還差一丟丟,因?yàn)樵玩溊^承不是一種純粹的繼承原型的方式,它有副作用,為什么呢?因?yàn)樵谖覀冋{(diào)用Son.prototype = new Father()的時(shí)候,不僅僅使Son的原型指向了一個(gè)Father的實(shí)例對(duì)象,而且還讓Father的構(gòu)造函數(shù)執(zhí)行了一遍,這樣就會(huì)執(zhí)行this.name = name;所以這個(gè)Father對(duì)象就不純粹了,它具有了name屬性,并且值為father,那為什么之后我們?cè)L問的時(shí)候訪問不到這個(gè)值呢?這又是因?yàn)樵玩湹脑蚶?,話不多說先上圖:
所以這里父類的構(gòu)造函數(shù)在進(jìn)行原型鏈繼承的時(shí)候也執(zhí)行了一次,并且在原型鏈上生成了一個(gè)我們永遠(yuǎn)也不需要訪問的name屬性,而這肯定是占內(nèi)存的(想象一下name不是一個(gè)字符串,而是一個(gè)對(duì)象),那么我們?cè)趺茨茏屧玩溊^承更純粹一點(diǎn)呢?讓它只繼承原型(靜態(tài)【屬性/方法】)呢?
5.寄生組合方式實(shí)現(xiàn)繼承
為了讓原型鏈繼承的更純粹,這里我們引入一個(gè)Super函數(shù),讓Father的原型寄生在Super的原型上,然后讓Son去繼承Super,最后我們把這個(gè)過程放到一個(gè)閉包內(nèi),這樣Super就不會(huì)污染全局變量啦,話不多說上代碼:
// 代碼4.4 function Father(name) { this.name = name } Father.prototype.sayName = function() { console.log("My name is " + this.name) } function Son() { Father.apply(this, arguments) } (function () { function Super(){} Super.prototype = Father.prototype Son.prototype = new Super() }()) var sonObj_1 = new Son("jack")
這個(gè)時(shí)候再去打印sonObj1就會(huì)發(fā)現(xiàn),它的原型中已經(jīng)沒有name屬性啦,如下所示:
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95328.html
摘要:是完全的面向?qū)ο笳Z(yǔ)言,它們通過類的形式組織函數(shù)和變量,使之不能脫離對(duì)象存在。而在基于原型的面向?qū)ο蠓绞街校瑢?duì)象則是依靠構(gòu)造器利用原型構(gòu)造出來的。 JavaScript 函數(shù)式腳本語(yǔ)言特性以及其看似隨意的編寫風(fēng)格,導(dǎo)致長(zhǎng)期以來人們對(duì)這一門語(yǔ)言的誤解,即認(rèn)為 JavaScript 不是一門面向?qū)ο蟮恼Z(yǔ)言,或者只是部分具備一些面向?qū)ο蟮奶卣鳌1疚膶⒒貧w面向?qū)ο蟊疽?,從?duì)語(yǔ)言感悟的角度闡述為什...
摘要:相當(dāng)于在用原型繼承編寫復(fù)雜代碼前理解原型繼承模型十分重要。同時(shí),還要清楚代碼中原型鏈的長(zhǎng)度,并在必要時(shí)結(jié)束原型鏈,以避免可能存在的性能問題。 js是一門動(dòng)態(tài)語(yǔ)言,js沒有類的概念,ES6 新增了class 關(guān)鍵字,但只是語(yǔ)法糖,JavaScript 仍舊是基于原型。 至于繼承,js的繼承與java這種傳統(tǒng)的繼承不一樣.js是基于原型鏈的繼承. 在javascript里面,每個(gè)對(duì)象都有一...
摘要:除了以上介紹的幾種對(duì)象創(chuàng)建方式,此外還有寄生構(gòu)造函數(shù)模式穩(wěn)妥構(gòu)造函數(shù)模式。 showImg(https://segmentfault.com/img/remote/1460000018196128); 面向?qū)ο?是以 對(duì)象 為中心的編程思想,它的思維方式是構(gòu)造。 面向?qū)ο?編程的三大特點(diǎn):封裝、繼承、多態(tài): 封裝:屬性方法的抽象 繼承:一個(gè)類繼承(復(fù)制)另一個(gè)類的屬性/方法 多態(tài):方...
摘要:首先,需要來理清一些基礎(chǔ)的計(jì)算機(jī)編程概念編程哲學(xué)與設(shè)計(jì)模式計(jì)算機(jī)編程理念源自于對(duì)現(xiàn)實(shí)抽象的哲學(xué)思考,面向?qū)ο缶幊淌瞧湟环N思維方式,與它并駕齊驅(qū)的是另外兩種思路過程式和函數(shù)式編程。 JavaScript 中的原型機(jī)制一直以來都被眾多開發(fā)者(包括本人)低估甚至忽視了,這是因?yàn)榻^大多數(shù)人沒有想要深刻理解這個(gè)機(jī)制的內(nèi)涵,以及越來越多的開發(fā)者缺乏計(jì)算機(jī)編程相關(guān)的基礎(chǔ)知識(shí)。對(duì)于這樣的開發(fā)者來說 J...
摘要:原型鏈和構(gòu)造函數(shù)是一種面向?qū)ο蟮恼Z(yǔ)言,并且可以進(jìn)行原型繼承。來了極大的支持了工程化,它的標(biāo)準(zhǔn)讓瀏覽器內(nèi)部實(shí)現(xiàn)類和類的繼承構(gòu)造函數(shù)構(gòu)造函數(shù)調(diào)用父類構(gòu)造函數(shù)現(xiàn)在瀏覽器對(duì)其支持程度還不高。 原型鏈 原型鏈比作用域鏈要好理解的多。 JavaScript中的每個(gè)對(duì)象,都有一個(gè)內(nèi)置的_proto_屬性。這個(gè)屬性是編程不可見的(雖然ES6標(biāo)準(zhǔn)中開放了這個(gè)屬性,然而瀏覽器對(duì)這個(gè)屬性的可見性的支持不同)...
閱讀 1012·2022-06-21 15:13
閱讀 1879·2021-10-20 13:48
閱讀 1063·2021-09-22 15:47
閱讀 1394·2019-08-30 15:55
閱讀 3148·2019-08-30 15:53
閱讀 542·2019-08-29 12:33
閱讀 744·2019-08-28 18:15
閱讀 3489·2019-08-26 13:58