【D1n910】學(xué)習(xí)《JavaScript 設(shè)計(jì)模式》(1[2/2]/6)
正常操作,正常分析,大家好,我是D1n910。
今天繼續(xù)來(lái)學(xué)習(xí) 《JavaScript 設(shè)計(jì)模式》的第一篇 第2章 寫(xiě)的都是看到的——面向?qū)ο缶幊獭?/p>
這是一個(gè)連續(xù)的讀書(shū)筆記,所以如果你之前的內(nèi)容沒(méi)有看的話,請(qǐng)務(wù)必一定要去看。
快速入口 ??

這里再次感謝 《Javascript 設(shè)計(jì)模式》及其作者 張榮銘,專(zhuān)欄內(nèi)容是在它的基礎(chǔ)上生成的。

第一篇 面向?qū)ο缶幊?/span>
第 2?章?寫(xiě)的都是看到的——面向?qū)ο缶幊?/p>
來(lái)公司工作,小新很想知道具體工作是怎么做的。項(xiàng)目經(jīng)理帶小新看項(xiàng)目,但是小新發(fā)現(xiàn)這些項(xiàng)目里的都是一個(gè)個(gè)對(duì)象。
2.1、兩種編程風(fēng)格 —— 面向過(guò)程與面向?qū)ο?/strong>
小新發(fā)現(xiàn)一個(gè)情況就是,大家都是創(chuàng)建對(duì)象來(lái)完成任務(wù)內(nèi)容,而不是直接寫(xiě)函數(shù),這是為什么呢?
在小新看來(lái)的需求開(kāi)發(fā)方式,以從深圳到上海為例子。
小新會(huì)這么做:
(1)一個(gè)容器能夠載人
(2)發(fā)動(dòng)機(jī)能夠推動(dòng)容器
(3)四個(gè)輪子能夠更好地推動(dòng)容器

對(duì)于小新B來(lái)說(shuō),他覺(jué)得兩個(gè)大輪子更好,所以他的函數(shù)方法又會(huì)變成這樣。

對(duì)于小新C來(lái)說(shuō),他認(rèn)為發(fā)動(dòng)機(jī)大會(huì)更好,所以他的函數(shù)方法會(huì)變成這樣。

實(shí)際上為了同一個(gè)類(lèi)似的目的,三個(gè)開(kāi)發(fā)人員寫(xiě)了自己的三套方法,而且都是要從頭寫(xiě),在代碼內(nèi)容也都不盡相同的。

我們是根據(jù)需求任務(wù)來(lái)設(shè)置全局變量、函數(shù)。這種就是面向過(guò)程開(kāi)發(fā)。
只是為了能夠?qū)崿F(xiàn)【從深圳->上?!窟^(guò)程而開(kāi)發(fā)。
也許同一個(gè)【從深圳->上?!啃枨螅煌?xiàng)目里,不同的人實(shí)現(xiàn)方式不一一樣;
也許同一個(gè)項(xiàng)目里,同一個(gè)人,不同的需求【五天從深圳->上?!俊ⅰ救鞆纳钲?>上?!?,最終的實(shí)現(xiàn)方式也不一一樣。
但是在現(xiàn)代企業(yè)項(xiàng)目里,會(huì)把這些內(nèi)容封裝起來(lái),變成一輛對(duì)象——車(chē)。

在我們這個(gè)例子里,為了讓這輛車(chē)能夠?qū)崿F(xiàn)從深圳 -> 上海。
可以設(shè)定這輛車(chē)的發(fā)動(dòng)機(jī)性能;
這輛車(chē)能夠載多少多少的人;
……
這輛車(chē)自帶智能內(nèi)容,能夠根據(jù)我們的需要調(diào)配自己內(nèi)部的內(nèi)容,而不需要我們每次從零開(kāi)發(fā)一個(gè)發(fā)動(dòng)機(jī)、容器、輪子。
有時(shí)候這輛車(chē)不能夠滿(mǎn)足現(xiàn)有的需求任務(wù),比如我們要求能夠裝載1000個(gè)客人,這輛車(chē)最多只能變成能夠裝500個(gè)客人的。
那需要我們是根據(jù)需求任務(wù),對(duì)對(duì)象車(chē)進(jìn)行改造,變成能夠支持轉(zhuǎn)載1000個(gè)客人的車(chē)。
這就是面向?qū)ο蠖_(kāi)發(fā)。
2.2、包裝明星——封裝(P12)
2.2.1 創(chuàng)建一個(gè)類(lèi)(P12)
在上面的例子中,我們舉了一個(gè)很形象的例子,就是車(chē)。
現(xiàn)實(shí)社會(huì)中,有工程師設(shè)計(jì)出車(chē)的圖紙,然后交付給工廠生產(chǎn)出能夠使用的一輛輛車(chē)。
那么在Javascript中,我們想要?jiǎng)?chuàng)建和使用對(duì)象,首先就要?jiǎng)?chuàng)建一個(gè)Javascript里的圖紙,也就是類(lèi),Class。
(現(xiàn)在最新的 Javascript 支持直接用 Class 關(guān)鍵字來(lái)聲明類(lèi),可以自行查閱)
Javascript 創(chuàng)建類(lèi)很簡(jiǎn)單。有幾個(gè)關(guān)鍵點(diǎn)。
(1)聲明一個(gè)函數(shù)保存在一個(gè)變量里;
(2)這個(gè)類(lèi)的變量名首字母一般是大寫(xiě)的,這是編程界的習(xí)俗,如果不這么做的話,會(huì)被綁到柱子上燒死(開(kāi)玩笑的);
(3)在(1)聲明的函數(shù)的內(nèi)部,對(duì)this(函數(shù)內(nèi)部自帶的變量,用于指向當(dāng)前這個(gè)對(duì)象)添加屬性或者方法來(lái)實(shí)現(xiàn)對(duì)類(lèi)添加屬性或者方法。
像下面這樣,我們就可以創(chuàng)建一個(gè)車(chē)類(lèi),Car。
var Car = function(id, carName, color) {
? ? this.id = id;
? ? this.carName = carName;
? ? this.color = color
}
創(chuàng)建時(shí)接收三個(gè)參數(shù),id、carName、color,然后復(fù)制給車(chē)對(duì)象上。
類(lèi)的屬性、方法還可以通過(guò)我們上一篇的 prototype 來(lái)實(shí)現(xiàn)添加。
這里有兩種方式,一種是給類(lèi)的 prototype 的屬性賦值,一種是直接講一個(gè)對(duì)象賦值給類(lèi)的 prototype。
這兩種要注意不能混用。
方法一、
Car.prototype.carWheelNum = 4
方法二、
Car.prototype =?{
????carWheelNum: 4
}
創(chuàng)建好我們的類(lèi)后,我們封裝好了屬性、方法在里面,并不是能夠直接拿來(lái)用的。
類(lèi)相當(dāng)于咱們的圖紙,想要使用就像按照?qǐng)D紙生成具體的車(chē)一樣,我們也要按照類(lèi)生成具體的對(duì)象才能夠用類(lèi)里封裝的屬性和方法。
在 Javascript 中,我們用 new 關(guān)鍵字來(lái)實(shí)例化(創(chuàng)建)新的對(duì)象,然后就可以用點(diǎn)語(yǔ)法來(lái)訪問(wèn)對(duì)象的屬性?xún)?nèi)容了。
var baoma = new Car(1, '寶馬', 'red')
baoma.id // 1

用 this 添加的屬性和方法和用 prototype 添加的有什么不一樣嗎?
用?this?添加的屬性和方法是創(chuàng)建出來(lái)的每個(gè)對(duì)象獨(dú)有的,new 一個(gè)新的對(duì)象的時(shí)候會(huì)按照類(lèi)的內(nèi)容跑一邊屬性的賦值,所以每次new一個(gè)新的對(duì)象,都會(huì)重新跑一邊,重新賦值;
用?prototype?添加的屬性和方法是每個(gè)對(duì)象共有的,每創(chuàng)建一個(gè)新的對(duì)象,不需要重新賦值屬性和方法。

向上面的車(chē)輪數(shù)就是共有的。
這里的原理:
JavaScript 在用點(diǎn)語(yǔ)法找一個(gè)對(duì)象上的屬性時(shí),如果當(dāng)前的對(duì)象找不到,就會(huì)按照原型鏈,往對(duì)象的 _proto_ 屬性指向的對(duì)象上找,直到?jīng)]有原型鏈找不到要的屬性了,就返回 undefined。
對(duì)象的?_proto_? 屬性指向的對(duì)象等于類(lèi)的 prototype 指向的對(duì)象。
所有根據(jù)同一個(gè)類(lèi)實(shí)例化的對(duì)象都是擁有同一個(gè) _proto_ 指向的屬性。
像我們剛剛的例子,

首先從視覺(jué)上的信息,我們就知道知道對(duì)象的?_proto_? 屬性指向的對(duì)象等于類(lèi)的 prototype 指向的對(duì)象,其次,也能通過(guò)等式判斷是否相等。

這些內(nèi)容可以在我之前的文章中看到更多的內(nèi)容
這也是上一篇見(jiàn)過(guò)的內(nèi)容,也是說(shuō)了要看的內(nèi)容,如果你沒(méi)去看的話,那就是你是大壞蛋!

2.2.2、這些都是我的——屬性與方法封裝(p12)
知道了怎么創(chuàng)建一個(gè)類(lèi)以后,我們要具體理清楚類(lèi)和對(duì)象中的屬性與方法。什么意思呢。
面向?qū)ο蟮母拍罾?,我們?huì)對(duì)屬性和方法進(jìn)行隱藏和暴露,就會(huì)牽扯出 私有屬性、私有方法等。
還是以車(chē)為例子,我們先看看面向?qū)ο髸r(shí)會(huì)考慮的東西。
我們可以直接看到車(chē)的品牌、車(chē)牌號(hào)、車(chē)身顏色,這都是車(chē)子直接暴露出來(lái)的,用戶(hù)可以直接了解到的。
車(chē)子的速度是車(chē)子的一個(gè)屬性,我們可以用車(chē)子暴露出來(lái)的公共方法——踩油門(mén),去改變車(chē)子的速度。
還有一些東西,我們是基本上不會(huì)知道的,車(chē)子也不會(huì)希望告訴我們的,比如車(chē)子的實(shí)際成本價(jià)格。
根據(jù)上面的內(nèi)容,羅列了 JavaScript 中類(lèi)中對(duì)象能夠用到的屬性和方法
對(duì)象公有屬性,對(duì)象公有方法
定義:能夠被外部直接訪問(wèn)到
來(lái)源:只用 this 創(chuàng)建的屬性和方法
私有屬性,私有方法
定義:只能在類(lèi)內(nèi)創(chuàng)建的特權(quán)方法、構(gòu)造時(shí)訪問(wèn)使用
來(lái)源:在類(lèi)中直接聲明的屬性、方法
特權(quán)方法(本身是對(duì)象公有方法)
定義:能夠被外部訪問(wèn),同時(shí)能夠訪問(wèn)私有屬性,私有方法
來(lái)源:只用 this 創(chuàng)建的方法
構(gòu)造器
定義:在實(shí)例化對(duì)象時(shí),會(huì)調(diào)用的特權(quán)方法
來(lái)源:同定義
下面查看例子
var?Book?=?function(id,?name,?price){
??//?私有屬性
??var?num?=?1;
??//?私有方法
??function?checkId?()?{
??};
??//?特權(quán)方法
??this.getName?=?function?()?{
????console.log(name)
??};
??this.addPrice?=?function?()?{
????price?++
??}
??this.getPrice?=?function?()?{
????console.log(price)
??};
??//?對(duì)象公有屬性
??this.id?=?id;
??//?對(duì)象公有方法
??this.copy?=?function?()?{};
??//?構(gòu)造器
??this.addPrice()
}
除了上面這些意外,還有下面的這幾種屬性
類(lèi)靜態(tài)公有屬性,類(lèi)靜態(tài)公有方法(對(duì)象不能訪問(wèn))
定義:在外部能夠直接用點(diǎn)語(yǔ)法訪問(wèn)類(lèi)的屬性
來(lái)源:在類(lèi)上用點(diǎn)語(yǔ)法定義的屬性和方法
公有屬性,公有方法
定義:所有的對(duì)象公用的內(nèi)容
來(lái)源:在類(lèi)上用 prototype 來(lái)進(jìn)行定義
示例:
//?類(lèi)靜態(tài)公有屬性
Book.isChinese?=?yes;
//?類(lèi)靜態(tài)公有方法
Book.resetTime?=?function()?{
??console.log('resetTime')
}
Book.prototype?=?{
??//?公有屬性
??isJSBook:?false,
??//?公有方法
??dispaly:?function()
}
公有屬性,公有方法
定義:能夠被所有的對(duì)象使用
來(lái)源:在類(lèi)上用 prorotype 定義的屬性和方法
這里要牢記的是,這里的方法作用其實(shí)就是看 new 對(duì)象時(shí),對(duì) this 的作用,就可以很方便地區(qū)分啦。
2.2.3、你們看不到我——閉包實(shí)現(xiàn)(P15)
上面我們說(shuō)了,可以利用類(lèi)的點(diǎn)語(yǔ)法來(lái)實(shí)現(xiàn)
類(lèi)靜態(tài)公有屬性,類(lèi)靜態(tài)公有方法(對(duì)象不能訪問(wèn))
現(xiàn)在我們想實(shí)現(xiàn)
類(lèi)靜態(tài)私有屬性,類(lèi)靜態(tài)私有方法
定義:不能夠被外部直接訪問(wèn),所有對(duì)象公用同一個(gè)內(nèi)容。
思考一下,我們就可以用閉包實(shí)現(xiàn)。
示例,實(shí)現(xiàn)記錄工程里實(shí)例化過(guò)多少本書(shū)
var Book = (function() {
????// 類(lèi)靜態(tài)私有屬性
? var bookNum = 0;
????//?類(lèi)靜態(tài)私有方法
? function getAllBookNum() {
? ? return bookNum;
? }
? return function(newName, newPrice) {
? ? this.id = bookNum++
? ? this.name = newName
? ? this.getAllBookNum = function() {
? ? ? console.log(getAllBookNum())
? ? }
? }
})()
var daxue = new Book('daxue')
var zairenjian = new Book('zairenjian')
daxue.getAllBookNum() // 2

上面做了閉包的示例。
現(xiàn)在討論一下什么是閉包。
作者給出的閉包的定義是:
閉包是有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中變量的函數(shù)。
即在一個(gè)函數(shù)內(nèi)部創(chuàng)建另外一個(gè)函數(shù)。
我覺(jué)得這個(gè)定義是優(yōu)雅恰當(dāng)?shù)摹?/p>
這里的閉包可以作為可實(shí)例化對(duì)象使用。
當(dāng)然了,直接返回構(gòu)造函數(shù)的,不太好定義類(lèi)共有屬性、公共屬性等。
所以可以改成下面這種返回類(lèi)的形式。
var Book = (function() {
? var bookNum = 0;
? function getAllBookNum() {
? ? return bookNum;
? }
? // 創(chuàng)建一個(gè)類(lèi)
? function _book (newName, newPrice) {
? ? this.id = bookNum++
? ? this.name = newName
? ? this.getAllBookNum = function() {
? ? ? console.log(getAllBookNum())
? ? }
? }
? ? _book.dd = 1
? return _book
})()

2.2.4、找位檢察長(zhǎng)——?jiǎng)?chuàng)建對(duì)象的安全模式(p17)
我們寫(xiě)函數(shù),要進(jìn)行防錯(cuò)處理,下面作者針對(duì)一般人剛接觸面向?qū)ο髮?xiě)法,忘記寫(xiě) new,寫(xiě)了一種防止報(bào)錯(cuò)的辦法。
首先看下面的舉例,直接這么看的話,ook1 因?yàn)闆](méi)有使用 new 關(guān)鍵字,是實(shí)例化失敗的

為什么 book1 是 undefined,然后我們的 this.id = id 執(zhí)行后會(huì)發(fā)生什么。
首先我們要知道 new 的這個(gè)關(guān)鍵字做了什么
new 的作用是
1.創(chuàng)建一個(gè)空對(duì)象,作為將要返回的對(duì)象實(shí)例
2.將這個(gè)空對(duì)象的原型指向構(gòu)造函數(shù)的prototype屬性
3.將構(gòu)建函數(shù)的this指針替換為在1創(chuàng)建的對(duì)象
那么我們自己實(shí)現(xiàn)一個(gè)new可以這么做


那么了解完 new 這個(gè)關(guān)鍵字的作用以后,我們回頭過(guò)來(lái)看看剛剛的問(wèn)題。
book1 是 undefined 的原因是,因?yàn)闆](méi)有帶 new 關(guān)鍵字,所以就是直接執(zhí)行函數(shù),原 Book 函數(shù)本身沒(méi)有 return 任何東西,所以得到的當(dāng)然是默認(rèn) return undefined。
this.id = id 會(huì)發(fā)生什么?

會(huì)發(fā)現(xiàn)全局變量? window? 被污染了,多了一個(gè) id 屬性,這里是因?yàn)闆](méi)有帶 new 關(guān)鍵字,所以就是直接執(zhí)行函數(shù),那么相當(dāng)于在 window 這個(gè)對(duì)象下執(zhí)行,this會(huì)一級(jí)一級(jí)找對(duì)象,找到上一級(jí)對(duì)象就不會(huì)再繼續(xù)找,這里的對(duì)象直接就是 window,所以要添加的屬性也會(huì)被放到 window 上。
作者由此延申除了要有一個(gè)類(lèi)似檢察長(zhǎng)的東西,防止有出現(xiàn)忘記用 new 從而導(dǎo)致全局變量被污染的情況,可以這么處理。
回憶一下我們 new 做的事情的1、2、3,那么也就是說(shuō) new 執(zhí)行了的話,this?應(yīng)該是從屬于 當(dāng)前類(lèi)的。
那么可以這么判斷:

這樣就可以避免出現(xiàn)發(fā)生錯(cuò)誤的問(wèn)題了。
回憶一下 new 的過(guò)程,上面的判斷方式,我們還可以改成下面這樣的:

但是我還是建議直接把那些忘記寫(xiě) new 的人拖下去燒死。(殘忍)
2.3、傳宗接代——繼承(P19)
學(xué)到現(xiàn)在,我們都是學(xué)的如何從一個(gè)藍(lán)圖里創(chuàng)建一個(gè)實(shí)例,這個(gè)實(shí)例有的屬性和方法,都是繼承這個(gè)藍(lán)圖的。
還沒(méi)有學(xué)到如果多級(jí)繼承。
比如A創(chuàng)建太極拳四十六式,B從A處繼承了太極拳太極拳四十六式,然后往里面加入了八式,變成了太極拳五十四式。
然后C又從B處繼承到了這套太極拳五十四式。
現(xiàn)在討論如何在?Javascript 中實(shí)現(xiàn)現(xiàn)這種繼承辦法。
2.3.1、子類(lèi)的原型對(duì)象——類(lèi)式繼承(P19)
比較常見(jiàn)的是類(lèi)式繼承,方法如下
// 聲明父類(lèi)
function Taiji46() {
? ? this.Taiji46 = 46
}
// 聲明父類(lèi)共有方法
Taiji46.prototype.outPut46 = function() {
? ? return this.Taiji46
}
// 聲明子類(lèi)
function Taiji54() {
? ? this.Taiji54 = 54
}
// 子類(lèi)繼承父類(lèi)
Taiji54.prototype = new Taiji46()
// 聲明子類(lèi)共有方法
Taiji54.prototype.outPut54 = function() {
? ? return this.Taiji54
}
// 實(shí)例化太極54式親傳弟子
var taiji54dizi1 = new Taiji54()
taiji54dizi1.outPut54() // 54
taiji54dizi1.outPut46() // 46
現(xiàn)在這種類(lèi)式繼承后的對(duì)象,可以打繼承自構(gòu)建函數(shù)的拳,也可以打父類(lèi)的拳。
這里子類(lèi)實(shí)例化后的對(duì)象能夠使用父類(lèi)的屬性和方法的本質(zhì)還是和我們上面學(xué)的原型鏈有關(guān)。
通過(guò)關(guān)鍵字 instanceof 我們可以發(fā)現(xiàn),太極54式親傳弟子確實(shí)與 Taiji46、54有關(guān)

但是為什么 Taiji54 instanceof Taiji46 是 false ?
原因是?instanceof? 的作用是某個(gè)對(duì)象是否是某個(gè)類(lèi)的實(shí)例,或者說(shuō)某個(gè)對(duì)象是否繼承了某個(gè)類(lèi)。
它是按照 prototype 原型鏈判斷的。所以剛剛的如果是下面的內(nèi)容,就正確了。

再次拓展,我們可以發(fā)現(xiàn),原來(lái)所有的對(duì)象都是 Object 的實(shí)例。

Object 是所有對(duì)象的祖先。
2.3.2、創(chuàng)建即繼承——構(gòu)造函數(shù)繼承(P21)
以繼承父類(lèi)的引用類(lèi)型的屬性為例子。
比如這里有兩個(gè)親傳弟子,從父類(lèi)都知道出拳步驟是1、2、3,但是親傳弟子二號(hào)自己練的時(shí)候,非要在出拳步驟里加多個(gè)步驟2,結(jié)果發(fā)現(xiàn)這個(gè)改動(dòng)把弟子一的出拳章法也改動(dòng)了。

這就很容易埋藏陷阱。
而且按照我們剛剛的類(lèi)式繼承,我們是靠原型 prototype? 對(duì)父類(lèi)實(shí)例化來(lái)實(shí)現(xiàn)的,因此創(chuàng)建父類(lèi)的時(shí)候,是無(wú)法向父類(lèi)傳遞參數(shù)的。所以如果實(shí)例化父類(lèi)的時(shí)候,也沒(méi)有辦法對(duì)父類(lèi)的構(gòu)建函數(shù)進(jìn)行初始化。
為了解決上面兩個(gè)問(wèn)題,我們可以用構(gòu)造函數(shù)繼承。?
function Taiji46(name) {
? ? this.name = name
? ? this.Taiji46 = 46
? ? this.cqsx = [1, 2, 3]
}
// 聲明父類(lèi)共有方法
Taiji46.prototype.outPut46 = function() {
? ? return this.Taiji46
}
// 聲明子類(lèi)
function Taiji54(name) {
?????// 繼承父類(lèi)
? ? Taiji46.call(this, name)
? ? this.Taiji54 = 54
}
// 聲明子類(lèi)共有方法
Taiji54.prototype.outPut54 = function() {
? ? return this.Taiji54
}
// 實(shí)例化太極54式親傳弟子_1
var taiji54dizi_1 = new Taiji54('taiji54dizi_1')
// 實(shí)例化太極54式親傳弟子_2
var taiji54dizi_2 = new Taiji54('taiji54dizi_2')
taiji54dizi_1.name // taiji54dizi_1
taiji54dizi_2.name //?taiji54dizi_2

這里的關(guān)鍵點(diǎn)在于 cell 函數(shù)把this放到繼承的父類(lèi)里走了一遍,也就是走了構(gòu)造函數(shù),構(gòu)造了一遍。
不過(guò)這里有一個(gè)問(wèn)題在于,直接單純用構(gòu)造函數(shù)繼承的話,咱們就只能繼承到父類(lèi)的this的內(nèi)容了,父類(lèi)的 prototype 就無(wú)法使用了。

為了綜合類(lèi)式繼承和構(gòu)造函數(shù)繼承,就有了組合繼承。
2.3.3、將優(yōu)點(diǎn)為我所用——組合繼承(P22)
類(lèi)式繼承通過(guò)子類(lèi)的原型 prototype 實(shí)例化實(shí)現(xiàn)的;構(gòu)造函數(shù)繼承式通過(guò)在子類(lèi)構(gòu)造函數(shù)作用環(huán)境中執(zhí)行一次父類(lèi)的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)的。所以組合繼承就是同時(shí)實(shí)現(xiàn)了這兩點(diǎn)。
function Taiji46(name) {
? ? this.name = name
? ? this.Taiji46 = 46
? ? this.cqsx = [1, 2, 3]
}
// 聲明父類(lèi)共有方法
Taiji46.prototype.outPut46 = function() {
? ? return this.Taiji46
}
// 聲明子類(lèi)
function Taiji54(name) {
? ? Taiji46.call(this, name)
? ? this.Taiji54 = 54
}
Taiji54.prototype = new Taiji46()
// 聲明子類(lèi)共有方法
Taiji54.prototype.outPut54 = function() {
? ? return this.Taiji54
}

但是這不是最完美的版本,因?yàn)槭褂脴?gòu)造函數(shù)繼承時(shí)執(zhí)行了一遍父類(lèi)的構(gòu)造函數(shù),而在實(shí)現(xiàn)子類(lèi)原型的類(lèi)式繼承時(shí)又調(diào)用了一遍父類(lèi)構(gòu)造函數(shù)。
因此父類(lèi)構(gòu)造函數(shù)調(diào)用了兩遍,所以這還不是最完美的方式。
(
可惡……難道這里不可以直接Taiji54.prototype = Taiji46.prototype 嗎?
我試了一下,完全可以好吧!
)
2.3.4、潔凈的繼承者——原型式繼承(P22)
在學(xué)習(xí)上面說(shuō)的更好的方式之前,作者想讓我們學(xué)習(xí)一個(gè)簡(jiǎn)單而很常用的方式。
先用了道格拉斯·克洛克福德2006年發(fā)表的《Javascript 中原型式繼承》的觀點(diǎn)
“借助原型 prototype 可以根據(jù)已有的對(duì)象創(chuàng)建一個(gè)新的對(duì)象,同時(shí)也不必創(chuàng)建新的自定義對(duì)象類(lèi)型”
直接看一下大師實(shí)現(xiàn)的代碼
// 原型式繼承
function inheritObject (o) {
????// 聲明一個(gè)過(guò)度函數(shù)對(duì)象
????function F() {}
????// 過(guò)渡對(duì)象的原型繼承父對(duì)象
???? F.prototype = o;
????// 返回過(guò)渡對(duì)象的一個(gè)實(shí)例,該實(shí)例的原型繼承了父對(duì)象
????return new F();
}
這種方式和類(lèi)式繼承很像,是它封裝。所以類(lèi)式繼承有的問(wèn)題,它也有。
但是這畢竟是 2006 年的思想,隨著這個(gè)思想的深入,后面就出現(xiàn)了 Object.create() 的方法可以直接實(shí)現(xiàn)上面的內(nèi)容。
(小聲嘀咕:現(xiàn)在還有“...”的拓展方法呢)
2.3.5、如虎添翼 ——寄生式繼承(P24)
作者還說(shuō)了道格拉斯·克洛克福德更進(jìn)一步的繼承,寄生式繼承。
其實(shí)我能理解,在道格拉斯·克洛克福德那個(gè)時(shí)候,對(duì)象的復(fù)制是一個(gè)比較棘手的問(wèn)題,很容易有淺拷貝問(wèn)題。
這里的寄生式繼承,指的是在直接拷貝基礎(chǔ)對(duì)象的基礎(chǔ)上,還能添加自己的方法到拷貝出來(lái)的對(duì)象上。
//基礎(chǔ)對(duì)象
var book = {
????name: 'js book',
????alikeBook: ['css book', 'html book']
}
function createBook(obj) {
????// 通過(guò)原型式繼承創(chuàng)建新對(duì)象
????var o = new??inheritObject (book);
????// 拓展新的對(duì)象
????o.getName = function() {
????????console.log(name);
????}
????// 返回拓展后的新對(duì)象
????return o;
}
這個(gè)是對(duì)原型繼承的第二次封裝,進(jìn)行對(duì)象的拓展。
2.3.6、終極繼承者——寄生組合式繼承(P25)
之前組合式繼承留下了一個(gè)問(wèn)題在于,子類(lèi)不是父類(lèi)的實(shí)例,子類(lèi)的原型式父類(lèi)的實(shí)例,所以才有了寄生組合式繼承。
首先看一下寄生式繼承 繼承原型
function inheritPrototype(subType, superType){
? ? var p = Object.create(superType.prototype);? ? // 復(fù)制一份父類(lèi)的原型副本保存在變量中
? ? p.constructor = subType;? ? ? ? ? ? ? ? ? ? // 修正因?yàn)橹貙?xiě)子類(lèi)原型導(dǎo)致子類(lèi)的 constructor 屬性被修改。如果你不知道這一行的作用,可以屏蔽之,然后跑一邊,然后查看一下子類(lèi)的 constructor
? ? subType.prototype = p;? ? ? ? ? ? ? ? ? ? ? ? //設(shè)置子類(lèi)的原型
}
那么我們只需要把之前組合式繼承里的子類(lèi)繼承父類(lèi) prototype 由原來(lái)的類(lèi)式繼承,改成上面的寄生式繼承即可。
function Taiji46(name) {
? ? this.name = name
? ? this.Taiji46 = 46
? ? this.cqsx = [1, 2, 3]
}
// 聲明父類(lèi)共有方法
Taiji46.prototype.outPut46 = function() {
? ? return this.Taiji46
}
// 聲明子類(lèi)
function Taiji54(name) {
? ? Taiji46.call(this, name)
? ? this.Taiji54 = 54
}
// Taiji54.prototype = new Taiji46()
inheritPrototype('Taiji54', 'Taiji46')
// 聲明子類(lèi)共有方法
Taiji54.prototype.outPut54 = function() {
? ? return this.Taiji54
}
2.4、老師不止一位——多繼承(P27)
面向?qū)ο笳Z(yǔ)言會(huì)有多繼承,在Javascript中也可以實(shí)現(xiàn)。
先講解一下當(dāng)前(2015)很流行的一個(gè)用來(lái)繼承單對(duì)象屬性的 extend 方法
// 單繼承 屬性賦值
var extend = function(target, source) {
????for (var property in source) {
????????target[property] = source[property];
????}
????// 返回目標(biāo)對(duì)象
????return target
}
這種就是之前的淺拷貝。
這種可以可以用 Object.assign() 完成了,后面多個(gè)混合的實(shí)現(xiàn)多態(tài)的辦法,也是可以用Object.assign() 填入多個(gè)參數(shù)完成。
下面可以看看原生寫(xiě)法
var mix = function() {
var i = 1,
len = arguments.length,
target = arguments[0],
arg;
for(;i<len;i++) {
arg = arguments[i];
// 遍歷被繼承對(duì)象中的屬性
for (var property in arg) {
target[property] = arg[property];
}
}
// 返回目標(biāo)對(duì)象
return target
}
2.5、多種調(diào)用方式——多態(tài)(P29)
面向?qū)ο缶幊讨械亩鄳B(tài)在 Javascript 中的實(shí)現(xiàn)。
多態(tài),就是同一個(gè)方法多種調(diào)用方式。
JavaScript 本身就是很靈活,下面通過(guò) arguments 來(lái)按照傳入的參數(shù)來(lái)判斷要調(diào)用的方式。
function Add() {
// 無(wú)參數(shù)算法
function zero() {
????return 0
}
// 一個(gè)參數(shù)算法
function one(num) {
????return ?num
}
// 兩個(gè)參數(shù)算法
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();
A.add(); // 0
A.add(1); // 1
A.add(6,2); // 8

本章讀書(shū)筆記結(jié)束,也不留課后作業(yè)了,光是看完,就辛苦了!
寫(xiě)了快兩天,才寫(xiě)完。
這一波是徹底理解 JavaScript?中面向?qū)ο蟮某R?jiàn)繼承寫(xiě)法以及其發(fā)展史,希望對(duì)大家有所幫助!
別忘了??梢灾苯尤タ纯醋钚碌?Class 類(lèi)方法繼承!
End
D1n910?
2012.02.13 02:32 寫(xiě)于福永