上一集中,重點介紹了誰動了你的代碼。這裏先總結一下:我們的代碼從敲下來到運行出結果,經歷了兩個階段:分析期與運行期。在分析期,JavaScript分析器悄悄動了咱們的代碼;在運行期,JavaScript又按照本身的一套機制進行變量尋找。咱們的代碼是如何被動了手腳的,相信看官你已經明白。可是前面所聊均是面向過程的,若是說只是簡單的面向過程言語,那JavaScript可以有基本的數據類型,基本的執行單元那也差很少了。可是故事並無在此結束。接下來劇情的發展,那纔是形成今天鞋同們困惑的地方,那們仍是從故事開始。大夥不要嫌樓主囉嗦(樓主確實是個囉嗦之人),講這故事是爲了讓大夥瞭解當年布大師設計JavaScript的背景,融入布大師的設計思惟,你就知道JavaScript爲何會有哪些奇怪的設計。好,故事開始了。javascript
前幾集的故事中,我們提到了布大師只想設計一個簡單、知足瀏覽器進行數據檢驗的腳本言語。當時的web應用毫無顏值,猶如白紙黑字,頂多再加點圖片。因此,你也別期待當時的布大師會想到如UI交互、動畫效果等等的設計需求。爲此,從一開始布大師設計的JavaScript就是一個過程式的簡單的言語,可是布大師也不是個迂腐落後之人。c的升級版c++、讓編程界有點瘋狂的Java,布大師也不能視而不見,多少受點影響。因而乎,布大師想:我這JavaScript可否也玩點OOP思想呢?布大師這麼一想,一堆問題就來了,原本就沒打算搞個正式的OOP腳本,也沒設計有class、extend,更沒有override啥的。可是今天拍腦殼一想要玩OOP,那總得在現有的設計基礎上去實現OOP三大思想(封裝、繼承、多態)吧。那我們就看看布大師是如何給JavaScript賦予OOP的。java
封裝c++
概念,樓主就不說了。可是你看看JavaScript定義的那些數據類型,壓根就沒class的概念。沒有類何來實例,沒有實例談何封裝?布大師翻來覆去研究已經定義的數據類型,再對比了c++、java。他發現c++、java每次建立對象都離不開調用構造函數。布大師靈感一來「對!繞過class直接調用構造函數建立對象,恰好function能夠做爲構造函數」。因而乎,你見到了今天JavaScript是這樣建立實現對象的:程序員
/*** *定義構造函數 ****/ function clazz(params){ } /**經過構造函數建立實例**/ var ins=new clazz(1);
好了,建立對象的事情是解決了。但也不是說有了new就萬事大吉。碼農們都知道,有類得有類的數據成員吧。布大師也知道這個問題,那他是怎麼賦予數據成員呢?這傢伙又去挖java的idea了,他發現java實例都有this。this這個神器均可以直接訪問數據成員。因而乎,他又把this扯進了function裏面,用於封裝實例的數據成員。這樣看上去封裝的事情解決得差很少了。web
/*** *定義構造函數 ****/ function clazz(p1,p2){ this.p1=p1; this.p2=p2; this.print=function(){ console.log(this.p1+this.p2); } } /**經過構造函數建立實例**/ var ins1=new clazz(1,1); /***調用實例成員 修改ins1.p1 看看是否影響了ins2.p1****/ ins1.p1=9; ins1.print(); //結果1+9=10 var ins2=new clazz(3,1);//結果3+1=4 ins2.print(); console.log("結論修改ins1.p1不影響ins2.p1;證實this下的定義是各自儲存副本的");
至此,JavaScript便可建立實例,有具備實例成員,看上去沒啥問題,知足了OOP的「封裝」思想,可是布大師就是大師,他總以爲上面的代碼還有點問題。this是屬於實例的,這意味着,掛在this下面的成員不是實例共享的(ins一、ins2分別儲存了各自的副本)。如上面p一、p2做爲數據成員,各自實例分別擁有那是十分合理的。可是print這個函數也搞分別擁有彷佛不合理啊。由於函數是基於棧運行的,而棧又是私有的,函數的執行其實就是在私有棧裏面跑代碼,這裏將實例函數定義到this下面有點浪費內存了。布大師又腦洞大開:那我得在function上面再搞點東西來存放實例共享的成員,解決沒法共享成員浪費內存的問題。因而乎,他在每一個function構造函數裏面搞了prototype用於存放實例共享成員,每一個實例實際上都擁有prototype定義的成員。編程
/*** *定義構造函數 ****/ function clazz(p1,p2){ this.p1=p1; this.p2=p2; this.print=function(){ console.log(this.p1+this.p2); } } /***定義實例共享成員***/ clazz.prototype.p3=function(){ console.log("come on man!"); }; /**經過構造函數建立實例**/ var ins1=new clazz(1,1); var ins2=new clazz(3,1); console.log(ins1.p3());//結果:come on man! console.log(ins2.p3());//結果:come on man! /**clazz.prototype.p3 看看是否影響了ins1 ins2**/ clazz.prototype.p3=function(){ console.log("come on girl!"); }; console.log(ins1.p3());//結果:come on girl! console.log(ins2.p3());//結果:come on girl! console.log("定義到prototype的成員是各個實例共享的!");
布大師終於圍繞着funciton搞掂了OOP的封裝思想,對funciton瞬間大愛啊!那接下來的繼承,看來布大師仍是會圍繞function來動腦筋了。看官你據說過「function是JavaScript一等公民」這個說法不?布大師在給JavaScript扯OOP的時候全靠funciton,你說它能不是一等公民嘛?那我們看看布大師是如何又對function作手腳來實現繼承的。設計模式
繼承瀏覽器
說到繼承,那得先有個繼承鏈上的上帝,Java繼承鏈上的上帝是Object。毫無疑問,布大師又將JavaScript的Object封爲上帝,JavaScript裏面的一切對象實例(引用類型)都是這個上帝的子民。有了上帝那還得有個維繫上帝與子民之間關係的紐帶啊。布大師又頭痛了,我這裏沒有設計extend啊,怎麼創建繼承關係呢?他又將目光投到了function身上。話說function都具備建立實例的能力了,那就在它上面再加點料讓它具備指向上帝的功能,那既不是解決了問題啊。因而他隨意一劃,在function上加了個"__proto__"用於指向當前子民的上帝。"__proto__"是加上去了,可是要不要讓程序員手動去編寫」function.__proto__==上帝「才創建子民與上帝的關係呢?布大師想了想,仍是搞個內部自動實現吧,因而乎,你看到了每一個對象都默認有個「__proto__」指向上帝Object。「__proto__」有了,同時還有個"prototype",老布看着兩玩兒,好像都差很少,也是他又想,既然都看着差很少,那麼內部實現就都合併一塊兒吧,因此我們定義的prototype成員其實是被合併到了「__proto__」中。「__proto__」是隱形的而prototype則是開放給程序員的。 app
話說,布大師解決了子民如何找到上帝的問題的,可是這還不夠。子民自己有本身的成員,上帝也有上帝的成員。咱們在調用子民成員的時候,如何兼顧調用上帝成員的能力呢?那這個調用邏輯得作下處理。經過前面的封裝故事,你已經知道了數據被封裝到了實例對象[this],咱們調用成員的時候都是找實例[this]成員,並無向那個指向上帝的「__proto__」要數據啊。因而他作下調用邏輯的調整:先找當前實例this上是否存在被調用的成員,沒有則在指向上帝的"__proto__"裏查找。因而乎繼承實現了。這裏強調一下:在function.prototype上定義的成員實際上也是被調整到了」__proto__「中,而」__proto__「中又有」__proto__「,這樣就造成了所謂的原型鏈。ide
看到這裏你應該明白了,既然「__proto__」是用於指向父類的,而prototype最終也是和」__proto__「合併一塊兒,若是咱們經過修改prototype的指向是否是就實現了對父類的繼承呢。正確,這也是布大師所想的,但這麼一修改就存在一個問題了,prototype原本是屬於某個fuction的,修改後指向了另外的對象,因而乎,布大師又往」__proto__「加了個constructor用於指向歸屬的function(構造函數),以代表這個」__proto__「的歸屬,若是咱們經過修改prototype指向父類,還得手動將其constructor指向修正回來。因而布大師的繼承是這樣實現的。
/***定義一我的類構造函數,做爲男人、女人的基類***/ function human(sex,name){ this.sex=sex; this.name=name; } /**人都會說話* ***/ human.prototype.say=function(){ console.log("人都會說話!"); console.log("個人性別是:"+this.sex); console.log("個人名字是:"+this.name); } /** * 定義一個女人構造函數 * ***/ function lady(hobby){ this.hobby=hobby; } /**原型繼承**/ lady.prototype=human.prototype; /**記得修改歸屬**/ lady.prototype.constructor=lady; var girl = new lady("化妝"); /**女人擁有了人類能說話的能力***/ girl.say();//結果 人都會說話! 個人性別是:undefined 個人名字是:undefined
結果有問題啊!女人是能夠說話了,可是人類的性別、名字怎麼繼承啊?布大師又遇到了惱火的問題:原型繼承只能繼承prototype上定義的成員,沒法繼承父類對象上的this成員?他又想點子了。還得拿function開刀。他想了想:構造函數裏的this成員變量是實例私有的,this.sex、this.name只歸屬與human實例的,沒法歸屬於lady實例呢?除非有個偷樑換柱的辦法,運行下human函數,運行期間讓裏面的this換成lady的this。這樣藉助JavaScript的動態屬性,讓lady的this偷偷加上sex和name。因而乎他在function上加了兩個方法:call、applay。專門幹偷樑換柱的事情。上面的代碼進一步改良:
/***定義一我的類構造函數,做爲男人、女人的基類***/ function human(sex,name){ this.sex=sex; this.name=name; } /**人都會說話* ***/ human.prototype.say=function(){ console.log("人都會說話!"); console.log("個人性別是:"+this.sex); console.log("個人名字是:"+this.name); } /** * 定義一個女人構造函數 * ***/ function lady(hobby,sex,name){ //依賴javascript的動態屬性功能,經過父類構造函數的call方法實現偷樑換柱 human.call(this,sex,name); this.hobby=hobby; } /**原型繼承**/ lady.prototype=human.prototype; /**記得修改歸屬**/ lady.prototype.constructor=lady; var girl = new lady("化妝","woman","pretty-mm"); /**女人擁有了人類能說話的能力***/ girl.say();//結果 人都會說話! 個人性別是:woman 個人名字是:pretty-mm
看到這裏我以爲看官你應該明白了JavaScript的是怎麼玩對象建立,怎麼玩繼承的了。上述內容也只能說抓住要點進行故事般推理,實際詳盡的知識點確定不是這寥寥幾段文字可以說清的,要否則《JavaScript高級程序設計》那書怎麼能像枕頭那般。樓主只是以爲理解了這些要點,再加以進一步學習,玩轉JavaScript的oop確定不在話下。這段是廢話了,面向對象的三大特性,我們只講了封裝、繼承、那多態性呢?JavaScript又如何體現?
多態性
JavaScript實現OOP三大特性,多態性是弱爆的了。布大師實現的封裝、繼承,雖然確實是非主流,但至少也是實現了。而多態性,布大師就搞得有點潦草了。我估計布大師當時已經有點不耐煩了,因此也沒像前面封裝、繼承那樣用心考慮設計,再說了多態性這玩兒層次較高,JavaScript完美實現多態性思想,按當時的需求彷佛也有點多餘。因而,布大師對多態性的實現,能夠說比較簡單,那下面就一塊兒聊聊JavaScript的多態性。
因爲多態性是個高層次的思想,在說JavaScript多態性以前,樓主得先表達下本身對多態性的理解。樓主認爲:多態性就是某個事物、行爲呈現的多種狀態,在OOP編程裏面能夠說處處都是多態性的體現。如一個Class能夠根據不一樣的數據產生不一樣的instance,同一份Class不一樣的instance那是否是多種狀態了?如函數的重寫、重載是否是同一個函數能夠產生多種不一樣的行爲結果?這又是多態性的體現了。更甚設計模式裏面的」工廠模式「,是否是一個工廠去產生不一樣的實例?還有面向接口編程,一個接口,N份實現......。能夠說在OOP編程裏面多態無處不在。樓主有時候甚至想:既然都無處不在了,多態性這玩兒還有必要拿出來談嘛?誰要是知道這個問題的答案,歡迎給樓主答疑。廢話了,我們仍是迴歸布大師是如何給JavaScript弄點多態性的。
話說,多態性最經典的表現就是函數的重寫、重載。重寫是函數實現的覆蓋、重載則是函數簽名的多種形式。布大師也明白,JavaScript已經被他賦予動態成員(屬性)超級能力,對於重寫,經過這個超級能力絕不費勁就實現了,如上面human的say函數,若是女人們想說點女人才說的話,那得重寫human的say函數,這個輕鬆實現:
/***定義一我的類構造函數,做爲男人、女人的基類***/ function human(sex,name){ this.sex=sex; this.name=name; } /**人都會說話* ***/ human.prototype.say=function(){ console.log("人都會說話!"); console.log("個人性別是:"+this.sex); console.log("個人名字是:"+this.name); } /** * 定義一個女人構造函數 * ***/ function lady(hobby,sex,name){ //偷樑換柱 human.call(this,sex,name); this.hobby=hobby; } /**原型繼承**/ lady.prototype=human.prototype; /**記得修改歸屬**/ lady.prototype.constructor=lady; /***重寫say,讓女人們都說女人愛說的話***/ lady.prototype.say=function(){ console.log("我是女人,我固然喜歡化妝啊!"); } var girl = new lady("化妝","woman","pretty-mm"); /**女人擁有了人類能說話的能力***/ girl.say();//結果 我是女人,我固然喜歡化妝啊!
得益於動態成員(屬性)的超級能力,JavaScript實現重寫,絕不費勁。布大師也能夠偷懶一下。可是重載呢?重載呢?重載呢?布大師仍是得費腦子了。大師就大師,人家實現重載也是驚豔加省事(固然他這麼一搞,我們寫JS的就不省事了)。爲了應對一個函數,N個參數的重載實現,布大師又拿function開到,給function加了個arguments用於存放函數的參數,無論你有多少個參數,均可以任意在這個argments裏面獲取,你也不須要寫function(p1,p2,p3.......)這樣的代碼,你只須要在函數體內根據arguments的長度走不通的邏輯便可。以下
function f(){ var args=arguments; if(args.length==0){ console.log("當f函數沒有傳參的邏輯"); }else if(args.length==1){ console.log("當f函數只有一個傳參的邏輯"); }else{ console.log("當f函數參數多個的邏輯"); } }
看,布大師是夠省事了,可是苦了我們寫代碼的了,要走一大段if...else...。那後來布大師還對此作改進啥的不?抱歉,好像沒有了,固然也可能樓主孤陋寡聞,沒有了解到的可能。各位看官要是有興趣,能夠進一步研究研究。
寫了那麼多,看樣子對JavaScript尋蹤OOP之路也吹得差很少了。以上內容要是有何錯誤、紕漏,還請客觀斧正!