成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

和少婦白潔一起學(xué)JavaScript

DevTTL / 2869人閱讀

摘要:我們已經(jīng)回答了的構(gòu)造函數(shù)和原型都是誰(shuí)的問題,現(xiàn)在牽扯出來(lái)一個(gè),我們繼續(xù)檢查的構(gòu)造函數(shù)是全局對(duì)象上屬性叫的對(duì)象的原型是個(gè)匿名函數(shù),按照關(guān)于構(gòu)造函數(shù)的約定,它應(yīng)該是構(gòu)造函數(shù)的屬性我們給這個(gè)對(duì)象起個(gè)名字,叫。

我不確定JavaScript語(yǔ)言是否應(yīng)該被稱為Object-Oriented,因?yàn)镺bject Oriented是一組語(yǔ)言特性、編程模式、和設(shè)計(jì)與工程方法的籠統(tǒng)稱謂,沒有一個(gè)詳盡和大家都認(rèn)可的checklist去比較,就很難在主觀意見上互相認(rèn)同。

但JavaScript百分之一百是一門Object語(yǔ)言。

這句話有兩個(gè)直接含義:

除了原始類型(primitive type)值之外,一切皆對(duì)象,包括函數(shù);

一切對(duì)象都是構(gòu)造出來(lái)的,有一個(gè)函數(shù)作為它的構(gòu)造函數(shù)(constructor);

JavaScript的另一個(gè)標(biāo)志性特性是原型重用(prototype-based reuse),我在這里故意避免使用繼承(inheritance)這個(gè)詞語(yǔ),是不想讓讀者立刻聯(lián)想C++/Java語(yǔ)言的繼承,請(qǐng)忘記它們;

JavaScript里的對(duì)象并非是Class的實(shí)例化,它沒有靜態(tài)結(jié)構(gòu)的概念;當(dāng)然這不意味這對(duì)象沒有結(jié)構(gòu),但對(duì)象的結(jié)構(gòu)只能由構(gòu)造函數(shù)在運(yùn)行時(shí)構(gòu)造出來(lái),因此構(gòu)造函數(shù)在JavaScript里的地位是很高的,它是唯一負(fù)責(zé)結(jié)構(gòu)的地方。

每個(gè)對(duì)象都有一個(gè)原型,對(duì)象可以使用和重載原型對(duì)象上的數(shù)據(jù)成員或方法,這是對(duì)象的唯一重用機(jī)制;

介紹原型概念的文章和書很多,假定你理解原型的基本概念;這里需要指出的問題是,對(duì)象之間的屬性重用,和面向?qū)ο罄锩嬲f的重用是兩回事;

你可以從重用的如此簡(jiǎn)單的定義看出,它唯一的設(shè)計(jì)目的是想減少對(duì)象的數(shù)量,它提供的機(jī)制就是讓多個(gè)對(duì)象共享原型對(duì)象上的屬性,同時(shí)又可以有重載能力;

但不要對(duì)此浮想連篇,它和Java語(yǔ)言里通過繼承重用靜態(tài)結(jié)構(gòu)和行為是完全兩回事,即使說“JavaScript的原型化重用僅僅是行為重用,而Java的重用是結(jié)構(gòu)和行為的雙重重用”,這樣的表述也沒有意義,因?yàn)榍罢咴谶\(yùn)行時(shí)對(duì)象之間發(fā)生后者在靜態(tài)編譯時(shí)發(fā)生,一個(gè)在說我們發(fā)明了活字印刷術(shù)讓印刷變得更容易,另一個(gè)在說我們發(fā)明了電腦上的字體,你需要顯示哪個(gè)字就來(lái)到我這里拿;雖然結(jié)果有時(shí)看起來(lái)很像,但是機(jī)制上完全風(fēng)馬牛不相及,不要上了阮一峰老師的當(dāng)。

前面寫的這三條,可以作為構(gòu)造JavaScript對(duì)象系統(tǒng)的三個(gè)基礎(chǔ)假設(shè);

在JavaScript里最最底層的概念,并非你在如何使用JavaScript語(yǔ)言的那些教材中看到的種種編程概念,而是兩個(gè)詞語(yǔ):構(gòu)造原型(或者說結(jié)構(gòu)與重用)。

每個(gè)對(duì)象必有構(gòu)造函數(shù)和原型,整個(gè)JavaScript系統(tǒng)里你看到的所有東西,都可以在概念或模型上這樣去理解,雖然實(shí)現(xiàn)上是另一回事。

JavaScript對(duì)運(yùn)行環(huán)境(runtime)的假設(shè)只有一個(gè),就是單線程事件模型,其他關(guān)于虛擬機(jī)該怎樣實(shí)現(xiàn)并無(wú)定義,也沒有bytecode的定義;ECMA262采用了一種類似偽碼的方式定義了對(duì)對(duì)象、屬性、函數(shù)的基本操作邏輯,所有實(shí)現(xiàn),解釋器也好,JIT也好,無(wú)論如何執(zhí)行JavaScript腳本,只要保證語(yǔ)義一致即可;其實(shí)這種偽碼定義方式本身,就暗示了某種特性,但我們暫且不表。

單線程的事件模型不是萬(wàn)能的,但絕大多數(shù)情況下讓編程變得簡(jiǎn)單;缺乏runtime定義使得這門語(yǔ)言并不實(shí)用,開發(fā)者總是需要完整的東西,但好在JavaScript自誕生起就有了第一個(gè)runtime:網(wǎng)絡(luò)瀏覽器,這讓它有了立足之地,之后又出現(xiàn)Node.js,它又找到一個(gè)可以生存的地方。

扯遠(yuǎn)了,我們說回構(gòu)造和原型的問題。

創(chuàng)世紀(jì)

假如今天我們冒充上帝,開始構(gòu)造JavaScript的對(duì)象世界,在這個(gè)世界里沒有什么不是對(duì)象,也遵循前述原則;

我們開始犯愁的第一個(gè)問題,似乎我們掉進(jìn)了雞生蛋蛋生雞的邏輯怪圈。

對(duì)吧,第一個(gè)對(duì)象造不出來(lái),因?yàn)閷?duì)象需要構(gòu)造函數(shù)構(gòu)造,而函數(shù)也是對(duì)象,所以我們前面說的那個(gè)對(duì)象必然不是第一個(gè)對(duì)象。

當(dāng)然邏輯是邏輯,我們可以先捏幾個(gè)最原始的對(duì)象出來(lái),然后把constructor__proto__引用裝載上去,讓它們成為系統(tǒng)最初的亞當(dāng)和夏娃。反正上帝本來(lái)也回答不了亞當(dāng)?shù)膵屖钦l(shuí)的問題,我們也這么做。

最初在ECMA262里并沒有約定JavaScript實(shí)現(xiàn)必須提供能訪問每個(gè)對(duì)象的原型對(duì)象的方法,它只是一個(gè)概念;但是node/v8和js shell都提供了__proto__這個(gè)名字的屬性,可以給出任何對(duì)象的原型;另一個(gè)方法是使用Object.getPrototypeOf方法。

注意__proto__和function對(duì)象的prototype屬性是兩回事,prototype是function對(duì)象的特有屬性(就像Array對(duì)象有l(wèi)ength這個(gè)特有屬性),__proto__才是對(duì)象的原型;下面的描述和代碼里都使用__proto__這個(gè)很別扭的名字指對(duì)象的原型,它沒歧義,和代碼一致,再發(fā)明一個(gè)名字只會(huì)制造更多的混亂。

現(xiàn)在打開node shell。

> let m = {}
undefined
> m.__proto__
{}
> m.__proto__ === m
false

我們創(chuàng)建了一個(gè)空對(duì)象,叫做m,它的原型也是一個(gè)空對(duì)象,雖然同為空對(duì)象但是它們并非一個(gè)對(duì)象,所以并不相等;

> m.__proto__.__proto__
null
> let op = m.__proto__
undefined

再沿著原型鏈往上爬,看看原型的原型是誰(shuí)?沒了。這很好,我們知道m(xù)的原型沒有原型了,我們先把m的原型叫做op。

誰(shuí)構(gòu)造的op呢?

> op.constructor
[Function: Object]
> op.constructor === Object
true

op的構(gòu)造函數(shù)是全局那個(gè)叫Object的對(duì)象,它本身是一個(gè)函數(shù);不要把Object理解成namespace,或者把Object對(duì)象上的方法理解為“靜態(tài)方法”,Object就是一個(gè)對(duì)象,它被賦值給了全局對(duì)象的Object屬性,雖然它有特別的功能,但是要把它理解成我們正在構(gòu)造的對(duì)象世界中的一員,它只是在對(duì)象世界開天辟地時(shí)被構(gòu)造好了而已,而我們?cè)谟懻摰木褪沁@個(gè)構(gòu)造的過程。

我們已經(jīng)回答了op的構(gòu)造函數(shù)和原型都是誰(shuí)的問題,現(xiàn)在牽扯出來(lái)一個(gè)Object,我們繼續(xù)檢查;

> Object.constructor
[Function: Function]
> Object.constructor === Function
true
> Object.__proto__
[Function]

Object的構(gòu)造函數(shù)是全局對(duì)象上屬性叫Function的對(duì)象;Object的原型是個(gè)匿名函數(shù),按照J(rèn)avaScript關(guān)于構(gòu)造函數(shù)的約定,它應(yīng)該是構(gòu)造函數(shù)的prototype屬性:

> Object.__proto__ === Function.prototype
true
> let fp = Function.prototype
undefined

我們給這個(gè)對(duì)象起個(gè)名字,叫fp。

> fp
[Function]
> fp.constructor
[Function: Function]
> fp.constructor === Function
true
> fp.__proto__
{}
> fp.__proto__.__proto__
null
> fp.__proto__ === op
true

這個(gè)fp也不是很麻煩,我們發(fā)現(xiàn)它是一個(gè)匿名函數(shù),它的構(gòu)造函數(shù)是Function,而它的原型是op

最后來(lái)看Function

> Function.constructor
[Function: Function]
> Function.__proto__
[Function]
> Function.__proto__ === fp
true

Function自己耍了一個(gè)賴皮,自己是自己的構(gòu)造函數(shù)所以解決了雞和蛋的問題。Function的原型和prototype屬性指向了同一個(gè)對(duì)象fp。

所以到此為止呢,我們扒開了JavaScript世界里最原始的幾個(gè)對(duì)象,他們的原型關(guān)系是:

Function and Object -> fp -> op -> null

至于構(gòu)造函數(shù)呢,因?yàn)?b>Object是function,它的prototype是op,按照J(rèn)avaScript的約定:function對(duì)象的prototype屬性指向的對(duì)象應(yīng)該把constructor屬性設(shè)置成該function對(duì)象,即:

functionObject.prototype.constructor = functionObject

同樣的道理,Function的prototype是fp,fp的constructor也要設(shè)置成Function

這是JavaScript里最基礎(chǔ)的四個(gè)對(duì)象;其他的一切對(duì)象,在模型和概念中都可以構(gòu)造出來(lái);

如果你在寫一個(gè)解釋器,你在最初就要把這些東西創(chuàng)造出來(lái),然后創(chuàng)造一個(gè)global對(duì)象(或者叫context),在這個(gè)對(duì)象上裝上ObjectFunction,讓他們成為全局對(duì)象,至于opfp,就讓他們藏在里面好了;編程中沒有需要用到他們的地方,如果要找到他們,可以用Object.prototype或者Function.prototype來(lái)找到。

所以到此為止,我們啟動(dòng)了JavaScript的對(duì)象世界,有了Function我們就可以構(gòu)造函數(shù)對(duì)象,有了函數(shù)我們就可以構(gòu)造更多的對(duì)象,如果語(yǔ)言上允許(即不需要通過native code實(shí)現(xiàn)特殊功能),我們可以繼續(xù)創(chuàng)建Object.prototypeFunction.prototype上的那些函數(shù)對(duì)象并把他們裝載上去,在概念模型上,內(nèi)置對(duì)象沒有什么了不起,他們?nèi)匀豢梢员焕斫獬杀粯?gòu)造出來(lái)的對(duì)象;

事實(shí)上所有的函數(shù)作用域和函數(shù)內(nèi)的變量也可以被理解成對(duì)象和它的屬性,在本文的結(jié)尾我們會(huì)談這個(gè)問題,當(dāng)然它只是模型上的;

我們闡述了一切皆對(duì)象的含義;這個(gè)對(duì)象模型夠簡(jiǎn)單嗎?我認(rèn)為是的;它只有對(duì)象,函數(shù),原型三個(gè)概念。

一些人說JavaScript是Lisp穿了馬甲,從對(duì)象模型上是可以成立的;因?yàn)長(zhǎng)isp里的數(shù)據(jù)結(jié)構(gòu)是List,它是一個(gè)鏈表,每個(gè)節(jié)點(diǎn)有兩個(gè)slot,一個(gè)用于裝載值,另一個(gè)裝載next;而JavaScript對(duì)象其實(shí)也是鏈表,只不過它給每個(gè)節(jié)點(diǎn)增加了一個(gè)字符串標(biāo)簽,即所謂的property name;但如果你用for ... in語(yǔ)法遍歷對(duì)象內(nèi)部的時(shí)候,你仍然能看到內(nèi)部結(jié)構(gòu)的順序是穩(wěn)定的,仍然是鏈表;

給每個(gè)節(jié)點(diǎn)加上label是JavaScript設(shè)計(jì)上非常聰明的地方,因?yàn)樗屛目粕部梢詤⑴c如火如荼的編程活動(dòng)。

但是這個(gè)對(duì)象模型說完了好像什么也沒有說?怎么JavaScript書上講的那么多概念都沒有提到呢?

這是問題的本質(zhì),也是很多Java過來(lái)的程序員很費(fèi)勁的地方;JavaScript利用上述的這個(gè)非常簡(jiǎn)單的對(duì)象模型,去模擬,或者說實(shí)現(xiàn),其他所有的編程概念。

JavaScript最初的設(shè)計(jì)目的只是用于非常簡(jiǎn)單的一些小功能,需要可編程;不管Brenden Eich是天才、拙劣、還是巧合的模仿了Lisp,以及Smalltalk和Self,他把兩個(gè)非常簡(jiǎn)單且獨(dú)一無(wú)二的事情結(jié)合在了一起:

Lisp是λ Calculus在編程語(yǔ)言上的直接實(shí)現(xiàn);原型重用的意思則是:

JavaScript:讓我們消滅必須用靜態(tài)定義約定動(dòng)態(tài)對(duì)象結(jié)構(gòu)的做法吧,編程君!任何靜態(tài)能定義出來(lái)的結(jié)構(gòu),我們?cè)谶\(yùn)行時(shí)也可以通過不斷的復(fù)制獲得啊,只是會(huì)慢一點(diǎn)點(diǎn)而已。
編程君:內(nèi)存不夠怎么辦?
JavaScript:我們有原型??!
編程君:好吧,但你要請(qǐng)我吃冰激凌。

不談工程實(shí)現(xiàn),僅僅在概念和模型上紙上談兵的話,JavaScript語(yǔ)言模型之簡(jiǎn)單,是很多老牌語(yǔ)言和新興腳本語(yǔ)言都難以企及的,它非常純粹。

函數(shù)對(duì)象與構(gòu)造函數(shù)

在談構(gòu)造函數(shù)之前我們先看一段代碼:

// 構(gòu)造對(duì)象的方式1
const factory = (a, b) => {

  return {
    a: a,
    b: b,
    sum: function() {
      return this.a + this.b
    }
  }
}

return語(yǔ)句后面返回的對(duì)象,被稱為ex nihilo對(duì)象,拉丁語(yǔ),out of nothing的意思,即這個(gè)對(duì)象沒有用一個(gè)專門的構(gòu)造函數(shù)去構(gòu)造,而是用那個(gè)全局的Object去構(gòu)造了。

如果你僅僅是想創(chuàng)建具有同樣結(jié)構(gòu)的對(duì)象實(shí)現(xiàn)功能,這樣的工廠方法足夠了。但是這樣寫,一方面,重用不方便;另一方面,如果我只構(gòu)造幾十個(gè)這樣對(duì)象,可能不是什么大問題,但是如果要構(gòu)造一百萬(wàn)個(gè)呢?構(gòu)造一百萬(wàn)個(gè)會(huì)引發(fā)什么問題?

讓我們來(lái)重新強(qiáng)調(diào)對(duì)象的另一個(gè)含義:對(duì)象是有生命周期的;因?yàn)楹瘮?shù)也是對(duì)象,所以函數(shù)對(duì)象也不例外;這一點(diǎn)是JavaScript和Java的巨大差異,后者的函數(shù),本質(zhì)上是靜態(tài)存在的,或者說和程序的生命周期一致。但JavaScript里的函數(shù)對(duì)象并非如此。

前面的sum屬性對(duì)應(yīng)的匿名函數(shù)對(duì)象,它是什么時(shí)候創(chuàng)建呢?在return語(yǔ)句觸發(fā)Object構(gòu)造的時(shí)候。如果要?jiǎng)?chuàng)建一百萬(wàn)個(gè)對(duì)象呢?這個(gè)函數(shù)對(duì)象也會(huì)被創(chuàng)建一百萬(wàn)次,產(chǎn)生一百萬(wàn)個(gè)函數(shù)對(duì)象實(shí)例!

換句話說,這個(gè)工廠方法創(chuàng)建的一百萬(wàn)個(gè)對(duì)象不僅狀態(tài)各有一份,方法也各有一份,前者是我們的意圖,但后者是巨大的負(fù)擔(dān),雖然運(yùn)行環(huán)境不會(huì)真的蠢到去把代碼復(fù)制一百萬(wàn)份,但函數(shù)對(duì)象確實(shí)存在那么多,對(duì)象再小也有基礎(chǔ)的內(nèi)存消耗,數(shù)量多時(shí)內(nèi)存消耗不管怎樣都會(huì)可觀的,如果對(duì)象具有不只一個(gè)函數(shù),那浪費(fèi)就更可觀了。

這是JavaScript的一切皆對(duì)象,包括函數(shù)也是對(duì)象的代價(jià)。

遇到這樣的問題一般有兩種辦法,一種是修改機(jī)制,即前面說的模型,引入新的概念;另一種是加入策略,即在語(yǔ)言實(shí)現(xiàn)層面增加約定,但是利用現(xiàn)有機(jī)制,不增加概念;

JavaScript的設(shè)計(jì)者選擇了后者,這也是JavaScript的看似古怪的構(gòu)造函數(shù)的由來(lái)。

設(shè)計(jì)者說可以這樣來(lái)解決問題:如果一個(gè)函數(shù)對(duì)象的目的是構(gòu)造其他對(duì)象(即構(gòu)造函數(shù)),它需要一個(gè)對(duì)象作為它的合作者,裝載所有被構(gòu)造的對(duì)象的公用函數(shù),兩者之間的聯(lián)系這樣建立:

構(gòu)造函數(shù)對(duì)象需要具有一個(gè)名稱為prototype的屬性,指向公用函數(shù)容器對(duì)象;

公用函數(shù)容器對(duì)象需要具有一個(gè)名稱為constructor的屬性,指向構(gòu)造函數(shù)對(duì)象;

這個(gè)公用函數(shù)容器對(duì)象在創(chuàng)建function對(duì)象的時(shí)候,如果不是arrow function,它自動(dòng)就有prototype屬性,指向一個(gè)空對(duì)象;如果是arrow函數(shù),沒有這個(gè)屬性,arrow函數(shù)也不可以和new一起使用;

> function x() {}
undefined
> x.prototype
x {}
> const y = () => {}
undefined
> y.prototype
undefined
>

當(dāng)調(diào)用構(gòu)造函數(shù)時(shí),通過使用new關(guān)鍵字明確表示要構(gòu)造對(duì)象,這時(shí)函數(shù)的工作方式變了:

先創(chuàng)建一個(gè)空對(duì)象N,把它的原型__proto__設(shè)置成該構(gòu)造函數(shù)對(duì)象的prototype屬性;

把N的constructor屬性設(shè)置為構(gòu)造函數(shù)對(duì)象;

把N bind成構(gòu)造函數(shù)的this;

運(yùn)行構(gòu)造函數(shù);

返回新對(duì)象N,不管構(gòu)造函數(shù)返回了什么;

new被定義成關(guān)鍵字是為了兼容其他語(yǔ)言使用者的習(xí)慣,寫成函數(shù)也一樣:

function NEW(constructor, ...args) {
  let obj = Object.create(constructor.prototype)
  obj.construtor = constructor
  constructor.bind(obj)(...args)
  return obj
}

另一個(gè)關(guān)鍵字instanceof,則反過來(lái)工作,如果表達(dá)式是A instanceof B,如果不考慮繼承問題,就去判斷A.constructor === B即可;繼承的問題后面討論。

理解了這個(gè)過程就會(huì)明白,JavaScript里的構(gòu)造函數(shù)問題,其實(shí)并非在發(fā)明構(gòu)造函數(shù)的新語(yǔ)法,而是保持語(yǔ)言模型不變,讓他能夠構(gòu)造共享原型的對(duì)象的一種方式。

這就是為什么在ES5語(yǔ)法里看到的構(gòu)造函數(shù)和它的原型的代碼是類似這樣的:

function X(name) {  this.name = name }

X.prototype.hello = function() { console.log("hello " + this.name) }

var x1 = new X("alice")
x1.hello()

var x2 = new X("bob")
x2.hello()

但即使需要這樣做,上面的寫法也不是唯一的寫法,也可以這樣直接寫工廠方法:

let methods = {
  hello: function() {
    console.log("hello" + this.name)
  }
}

function createX(name) {
  let obj = Object.create(Object.assign({}, methods)) // 使用Object.assign可以merge多個(gè)methods
  obj.name = name
  return obj
}

同樣實(shí)現(xiàn)構(gòu)造共享原型的對(duì)象,只是返回的對(duì)象不具有constructor屬性,instanceof沒法用,但如果你不需要instanceof,也不需要設(shè)計(jì)多層的繼承,這是可用的方法;

總結(jié)一下關(guān)于構(gòu)造函數(shù)的這一節(jié);

首先JavaScript在定義函數(shù)時(shí),并不區(qū)分這個(gè)函數(shù)是不是構(gòu)造函數(shù),是否是構(gòu)造函數(shù)取決于你是否使用new調(diào)用;

其次,如果一個(gè)函數(shù)是構(gòu)造函數(shù),它不是一個(gè)人在戰(zhàn)斗,它需要和它的prototype屬性指向的對(duì)象合作,該對(duì)象將是構(gòu)造的對(duì)象的原型,請(qǐng)把兩個(gè)對(duì)象而不是一個(gè)對(duì)象印在腦子里,這對(duì)后面理解繼承非常關(guān)鍵;

第三,和Java里那種數(shù)據(jù)成員和方法成員在心理上位于一個(gè)對(duì)象容器內(nèi)不同,JavaScript的對(duì)象在設(shè)計(jì)上就要理解為數(shù)據(jù)(或者狀態(tài))在自己身上,方法(函數(shù)對(duì)象)在原型身上,這仍然是兩個(gè)對(duì)象在合作,表現(xiàn)得象一個(gè)對(duì)象。

繼承

JavaScript里的繼承仍然不是語(yǔ)言特性,在這個(gè)問題上我們繼續(xù)沿用前面的思路:用JavaScript的原型重用能力,去模擬,或者說實(shí)現(xiàn)Java語(yǔ)言里的繼承形式

我們先說思路,假想我們就是Brenden Eich幾分鐘。

假如我們已經(jīng)用構(gòu)造共享原型的對(duì)象的思路,寫了一個(gè)構(gòu)造函數(shù)BaseConstructor,它負(fù)責(zé)創(chuàng)建每個(gè)對(duì)象的數(shù)據(jù)或狀態(tài)屬性,也有了一個(gè)合作者BaseConstructor.prototype,它提供了方法BaseMethod1, ...;現(xiàn)在我們需要拓展它,要增加一部分狀態(tài)或者屬性,也要增加一部分方法,我們?cè)撛趺醋觯?/p>

首先我們考慮拓展方法,這不難,如果我們構(gòu)建一個(gè)對(duì)象,把它的原型設(shè)置為BaseConstructor.prototype,然后在新對(duì)象里添加方法即可;

其次我們未來(lái)需要使用的對(duì)象應(yīng)該都以該對(duì)象為原型,因?yàn)樵蟹椒ê蛿U(kuò)展方法都能通過它訪問;這預(yù)示了我們需要一個(gè)新的構(gòu)造函數(shù)以該對(duì)象作為prototype屬性;邏輯上可以是這樣:

Base     <-> Base.prototype
  ^            ^
  |            *
  | call       * __proto__
  |            *
Extended <-> Extended.prototype

Extended函數(shù)可以創(chuàng)建Extended.prototype里擴(kuò)展方法所需要的狀態(tài)或數(shù)據(jù)成員;但是Base.prototype里需要的狀態(tài)或者數(shù)據(jù)成員需要Base來(lái)創(chuàng)建,我們肯定不希望把Base里的代碼復(fù)制一份到Extended內(nèi);我們需要調(diào)用它來(lái)創(chuàng)建原有方法所需的狀態(tài)或數(shù)據(jù)成員。

function Base(name) { this.name = name}
Base.prototype.printName = function() { console.log(this.name) }

function Extended(name, age) {
  Base.bind(this)(name)
  this.age = age
}
Extended.prototype = Object.create(Base.prototype)
Extended.prototype.constructor = Extended
Extended.prototype.printAge = function() { console.log(this.age) }

這里tricky的地方有幾處:

第一,在Extended函數(shù)內(nèi),先把this bind到Base構(gòu)造函數(shù)上,然后提供name參數(shù)調(diào)用它,這樣this就會(huì)具有printName所需的name屬性,實(shí)現(xiàn)結(jié)構(gòu)繼承;

第二,我們使用Object.create方法創(chuàng)建了一個(gè)以Base.prototype為原型的新對(duì)象,把它設(shè)置為Extended.prototype,實(shí)現(xiàn)行為繼承;

第三,把Extended.prototype.constructor設(shè)置為Extended構(gòu)造函數(shù),這樣我們可以使用instanceof語(yǔ)法糖;

最后我們?cè)?b>Extended函數(shù)內(nèi)創(chuàng)建新的狀態(tài)或數(shù)據(jù)屬性,我們也在Extended.prototype上添加新的函數(shù)方法;

或者我們說我們找到了一種方式既拓展了構(gòu)造函數(shù)構(gòu)造的新對(duì)象的數(shù)據(jù)屬性,也拓展了它的函數(shù)屬性,沿著兩條鏈平行實(shí)施,達(dá)到了我們的目的。

在JavaScript里使用這種在原有構(gòu)造函數(shù)及其prototype對(duì)象上拓展出一對(duì)新的構(gòu)造函數(shù)和prototype對(duì)象的拓展方式,我們稱之為繼承。

因?yàn)閷?duì)象可以重載原型對(duì)象的屬性,所以在function.prototype的原型鏈上,重載函數(shù)的能力也具有了。

Class

JavaScript里沒有type系統(tǒng)意義上的Class的概念。class關(guān)鍵字仍然是語(yǔ)法糖。

class A {

  constructor () { // 這是構(gòu)造函數(shù)
  }

  method() { // 這是A.prototype上的方法
  }
}

這個(gè)語(yǔ)法比前面分開寫構(gòu)造函數(shù)和prototype對(duì)象的寫法要簡(jiǎn)潔干凈很多,但是帶著Java的Class的概念試圖去理解它,更容易被誤導(dǎo)了。

A在這里仍然是函數(shù)對(duì)象,只不過它只能當(dāng)構(gòu)造函數(shù)用,必須用new調(diào)用;其他還有一些細(xì)節(jié)差異,不贅述了;

如果是繼承呢?

class Base {
  constructor() {}
  method1() {}
}

class Extended extends Base {
  constructor() {
    super()
    //...
  }
  method2() {}
}

也是大同小異;Extended構(gòu)造函數(shù)內(nèi)需要調(diào)用super()來(lái)實(shí)現(xiàn)調(diào)用Base構(gòu)造函數(shù)構(gòu)造屬性;這一句必須調(diào)用,否則沒有this,這是class語(yǔ)法和前面ES5語(yǔ)法的一個(gè)差異,在ES5語(yǔ)法內(nèi),新對(duì)象是在調(diào)用Extended構(gòu)造函數(shù)時(shí)立刻創(chuàng)建的,在class語(yǔ)法中,這個(gè)對(duì)象是沿著super()向上爬到最頂層構(gòu)造函數(shù)才創(chuàng)建的,所以如果不調(diào)用super就沒this了。

實(shí)際上在JavaScript里的繼承,應(yīng)該當(dāng)作一種Pattern來(lái)理解,即:使用構(gòu)造函數(shù)和它的prototype屬性對(duì)象合作來(lái)模擬傳統(tǒng)OO語(yǔ)言里的繼承形式,把它叫做Inheritance Pattern恰當(dāng)?shù)亩唷?/p> 函數(shù)作用域

前面我們?cè)俺渖系?,假想一個(gè)JavaScript程序啟動(dòng)后,如何從零開始構(gòu)造整個(gè)對(duì)象世界;現(xiàn)在我們得寸進(jìn)尺,冒充上帝他媽,考慮站在執(zhí)行器的視角上,如果拿到一份JavaScript腳本如何執(zhí)行;

假定我們已經(jīng)使用了底層語(yǔ)言,例如C/C++,實(shí)現(xiàn)了JavaScript的對(duì)象模型,即很容易創(chuàng)建對(duì)象,維護(hù)原型鏈。

我們先創(chuàng)建一個(gè)空對(duì)象,把它稱為global,先把標(biāo)準(zhǔn)的內(nèi)置對(duì)象都作為全局變量名稱裝載進(jìn)去;然后開始運(yùn)行。

JavaScript是個(gè)單線程模型,所以假定我們用棧的方式來(lái)實(shí)現(xiàn)計(jì)算;基本操作符和表達(dá)式的棧計(jì)算就不多說了,我們只說遇到函數(shù)怎么辦。

一般來(lái)說遇到函數(shù)應(yīng)該約定在棧上處理參數(shù)和返回值的方式,但這個(gè)無(wú)關(guān)緊要,有關(guān)緊要的問題是我們需要把傳統(tǒng)的Function Frame的概念,即對(duì)一個(gè)函數(shù)在棧上分配局部變量的概念,換個(gè)思維,我們不用Function Frame,而是創(chuàng)建一個(gè)空對(duì)象來(lái)表式一個(gè)Function Frame,我們一行一行的讀入代碼,遇到局部變量聲明就在這個(gè)對(duì)象上裝上一個(gè)屬性,遇到修改局部變量的時(shí)候就給它賦值;

如果這樣做,我們就可以把Function Scope(一般說Function Scope指的是代碼層面的Lexical Scope,這里我們把Function Scope和Function Frame混用)作為原型鏈串起來(lái),詞法域中外圍的Function Scope是原型,內(nèi)部的Function Scope是對(duì)象;這樣Function Scope的引用可能出現(xiàn)在棧上,但它本身并非分配在棧上;Function Scope對(duì)象的創(chuàng)建是在調(diào)用函數(shù)時(shí),它的銷毀我們可以暫時(shí)指望垃圾回收器,可回收的時(shí)間是該函數(shù)已經(jīng)完成執(zhí)行且沒有其他Function Scope引用該Scope;

如果你仔細(xì)觀察在Function Scope構(gòu)成的鏈上查找變量名(Identifier)的時(shí)候,其邏輯和在原型鏈上查找屬性的方式一模一樣;用這樣的方式也可以準(zhǔn)確找到閉包變量,唯一的區(qū)別是這里需要小小的修改一下原型鏈的約定,原型上的屬性可以直接修改,因?yàn)殚]包變量是可以賦值的;

這就是前面我們說Function Scope也可以當(dāng)作是對(duì)象處理的原因。

你可以想象出來(lái)這個(gè)解釋器可以寫得多小和多簡(jiǎn)單,而且如果沒有hoisting,它可以在源文件還沒下載完就開始投入運(yùn)行,而不是一開始就把整個(gè)語(yǔ)法樹都解析出來(lái);

如果你問為什么早期的JavaScript的var沒有block scope支持,因?yàn)閎lock scope按照這種思路來(lái)說,需要為block scope多帶帶創(chuàng)建對(duì)象。

所以在這個(gè)討論里,你能對(duì)JavaScript最初呱呱墜地時(shí)的一些小想法獲得一些感受;它從一開始只想用一個(gè)令人震驚的簡(jiǎn)單的方法做幾件簡(jiǎn)單的小事情,比如賺一個(gè)億,但這并不說明它無(wú)能,相反,在數(shù)學(xué)和編程的世界里,越是簡(jiǎn)單的事情越有無(wú)窮無(wú)盡的能量。

寫到這里,我想我說完了自己對(duì)JavaScript的一切皆對(duì)象的認(rèn)知,歡迎探討。

最后鳴謝少婦白潔愿意出現(xiàn)在本文題目中。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/81674.html

相關(guān)文章

  • 少婦白潔一起學(xué)JavaScript之Async/Await

    摘要:匿名函數(shù)是我們喜歡的一個(gè)重要原因,也是,它們分別消除了很多代碼細(xì)節(jié)上需要命名變量名或函數(shù)名的需要。這個(gè)匿名函數(shù)內(nèi),有更多的操作,根據(jù)的結(jié)果針對(duì)目錄和文件做了不同處理,而且有遞歸。 能和微博上的 @響馬 (fibjs作者)掰扯這個(gè)問題是我的榮幸。 事情緣起于知乎上的一個(gè)熱貼,諸神都發(fā)表了意見: https://www.zhihu.com/questio... 這一篇不是要說明白什么是as...

    Bryan 評(píng)論0 收藏0
  • 少婦白潔一起學(xué)JavaScript之Async/Await II

    摘要:的科學(xué)定義是或者,它的標(biāo)志性原語(yǔ)是。能解決一類對(duì)語(yǔ)言的實(shí)現(xiàn)來(lái)說特別無(wú)力的狀態(tài)機(jī)模型流程即狀態(tài)。容易實(shí)現(xiàn)是需要和的一個(gè)重要原因。 前面寫了一篇,寫的很粗,這篇講講一些細(xì)節(jié)。實(shí)際上Fiber/Coroutine vs Async/Await之爭(zhēng)不是一個(gè)簡(jiǎn)單的continuation如何實(shí)現(xiàn)的問題,而是兩個(gè)完全不同的problem和solution domain。 Event Model 我...

    番茄西紅柿 評(píng)論0 收藏0
  • 少婦白潔如何使用React 001?

    摘要:目的是為了解決在重用的時(shí)候,持久和方法重用的問題。換句話說你不用擔(dān)心把組件寫成模式不好重用,如果你需要傳統(tǒng)的方式使用,一下即可。 這篇文章所述的思想最終進(jìn)化成了一個(gè)簡(jiǎn)單的狀態(tài)管理模式,稱React StateUp Pattern,詳細(xì)介紹請(qǐng)參閱:https://segmentfault.com/a/11... 寫了一個(gè)非常簡(jiǎn)單的實(shí)驗(yàn)性Pattern,暫且稱為PurifiedCompon...

    davidac 評(píng)論0 收藏0
  • 少婦白潔系列之React StateUp模式

    摘要:一般這種情況會(huì)在類的構(gòu)造函數(shù)內(nèi)創(chuàng)建一個(gè)屬性,引用或詞法域的,但后面會(huì)看到我們有更好的辦法,避免這種手工代碼。 換句話說,StateUp模式把面向?qū)ο蟮脑O(shè)計(jì)方法應(yīng)用到了狀態(tài)對(duì)象的管理上,在遵循React的組件化機(jī)制和基于props實(shí)現(xiàn)組件通訊方式的前提之下做到了這一點(diǎn)。 ---- 少婦白潔 閱讀本文之前,請(qǐng)確定你讀過React的官方文檔中關(guān)于Lifting State Up的論述: ht...

    jaysun 評(píng)論0 收藏0
  • 少婦白潔系列之React StateUp Pattern, Explained

    摘要:本文用于闡述模式的算法和數(shù)學(xué)背景,以及解釋了它為什么是里最完美的狀態(tài)管理實(shí)現(xiàn)。歡迎大家討論和發(fā)表意見。 本文用于闡述StateUp模式的算法和數(shù)學(xué)背景,以及解釋了它為什么是React里最完美的狀態(tài)管理實(shí)現(xiàn)。 關(guān)于StateUp模式請(qǐng)參閱:https://segmentfault.com/a/11... P-State, V-State 如果要做組件的態(tài)封裝,從組件內(nèi)部看,存在兩種不同的...

    20171112 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<