JS面向對象——OOP

閉包

定義

  官方定義爲,閉包即一個擁有許多變量和綁定了這些變量的環境的表達式(一般爲函數)數組

原理

  實際上,不用解釋那麼費勁,閉包就的原理就是函數能夠訪問其外部的全局變量閉包

  本來在函數外部訪問不到函數裏面的局部變量,爲了能夠訪問函數內的局部變量,在函數中添加一個函數,並讓函數的執行結果返回這個函數體app

  接下來,把函數的執行結果賦值給一個全局變量,並再次執行,便可以讓這個函數內的函數經過做用域鏈的方式,向上查找並訪問到函數體內部的局部變量函數

 案例

  一個比較經典的例子,先把案例放上來,最後再放執行的結果this

  

  執行的結果以下:prototype

  

  整個執行過程解析以下:3d

  1.第一步var c = f1(),把函數f1的執行結果返回給全局變量c,其實就是函數f2,此時c就是函數f2的函數體對象

  2.因爲在執行函數f1過程當中,函數f1內部聲明瞭一個全局變量nAdd(其實是方法)和i,因此能夠在執行完f1後訪問到這兩個變量blog

  3.執行函數c,也就是執行函數f2,因爲函數f2是在f1中聲明的,雖然f2中沒有局部變量n,但能夠經過做用域鏈,訪問到其上一級的f1的局部變量n,返回999繼承

  4.因爲f1執行時生成了一個函數做用域,n是這個做用域中的,能夠理解成這個做用域中的全局變量,而nAdd雖然是全局的,可是聲明時候是在f1內,所以能夠訪問到f1內的局部變量n,能夠經過執行n+1讓該環境中的局部變量值發生改變,那麼下一次訪問n的時候就變成1000了

 用途

  利用閉包,在函數外讀取函數內部的局部變量,並讓函數內部的局部變量保存在內存中

優缺點

  優勢:封裝性強,可訪問局部變量

  缺點:局部變量長時間佔用內存,容易產生內存泄漏

 

封裝

定義

  官方解釋(先放個再說,至於看不看得懂不重要):把對象內部數據和操做細節隱藏,只對外提供一個對象的專門訪問的接口

  不少語言中有關鍵字來實現封裝

原理

  接着上面的定義,很遺憾的是,JS中,沒有關鍵字來實現封裝,卻是能夠利用閉包來實現封裝的效果(注意是效果)

案例

案例1

  先上案例的代碼,具體的執行結果和解析在下方(養成個先思考再看答案的好習慣)

  注意這裏綠色的註釋部分和test的效果是同樣的,一個是用了字面量的聲明方式,一個是用了構造函數的方式聲明test方法

  

  執行結果以下

  

  整個執行過程以下:

  1.這裏用了閉包的方法,在demo這個函數中建立了局部變量n方法test,並讓函數的執行結果返回test(標準的閉包)

  2.這樣一來,用一個全局變量at承接demo的執行結果,就獲得了test這個函數的本體

  3.此時執行at函數,因爲at裏沒有n這個局部變量,沿着做用域鏈向上,在demo函數中找到了demo的局部變量n,讓其加1後返回爲2,改變了這個局部變量的值,若是再次執行at函數則返回3

案例2

  再來一個案例,不過和上面的案例有所不一樣,全局下的函數還用原型模式來添加了一個普通方法

  先放上代碼

  

  執行結果以下

  

  整個執行過程以下:

  1.用了混合模式建立了A這個對象,接着構造了一個A的實例a

  2.建立了一個全局變量b,用來接收a裏的xx方法,而xx方法是返回a裏的_xx方法的,所以實際上b接收的是a裏的_xx方法

  3.調用了函數b,實際上調用了a裏的_xx方法,輸出11******

  4.調用a的oth方法,這個方法是經過原型模式添加給a的原型A的,所以輸出「普通方法

特權方法

  第一個案例中,test這個方法值得注意,它是整個demo函數中惟一能夠訪問到其內部的局部變量n的方法

  鑑於其特殊性,咱們叫test方法爲特權方法

  而第二個案例主要是想說明,封裝不影響原型方式添加的普通方法的訪問,例如這裏咱們仍然能夠訪問到oth這個方法

缺點

  封裝佔用了內存,不利於繼承

 

繼承

  在說明什麼是繼承以前,先引入兩個基本概念:原型原型鏈

原型

  官方解釋爲:用prototype給對象添加屬性和方法

  前面建立對象的原型模式裏就說起了,經過對象.prototype來給某個指定的對象添加屬性和方法

原型鏈

  那原型知道了,原型又是如何構成一條「鏈」的呢?

  JS建立對象時,新對象內部有一個__proto__內置屬性,指向建立其函數對象的原型對象prototype

  咱們不妨瞭解一下用構造函數方式建立一個對象的具體過程,以下圖

  

  分析一下,整個建立student對象的過程以下:

  1.var student = {},也就是讓student這個變量指向一個空的對象,此時student就成爲了對象

  2.student.__proto__ = person.prototype,這也是最關鍵的一步,讓student的__proto__這個內置屬性指向建立其的函數對象person的原型對象prototype

  3.建立對象,也叫作初始化對象,用person.call(student),也就是把student傳入person的call方法裏,完成這個對象的建立

  能夠這麼理解整個過程:

  首先,誕生了student這個寶寶,這個寶寶是一片空白的,不過能夠肯定是我的(對象);

  接下來,student認出了他的爹person,用本身的內置屬性__proto__和他爹的原型對象prototype鏈接上,之後就能夠從爹爹那裏學東西了(原型繼承)

  最後,他爹費了九牛二虎之力,用本身的方法讓student獲得了最最基本的生存能力

  

  建立對象的過程知道了,接下來這個例子就好理解了

  

  執行結果以下:

  

  整個過程的解析以下:

  1.首先,用原型模式聲明瞭對象person,這個person原型中有salary屬性500),以及say方法天氣不錯

  2.接着,用原型模式聲明瞭對象programmer,這個programmer原型中有salary屬性1000),以及wcd方法明每天氣也不錯

     此外,建立了一個person的實例,programmer的原型爲person,也就是programmer.prototype.__proto__ = person.prototype

  3.最後,構建了一個programmer的實例p

  因此,調用say方法過程以下:

  因爲p裏沒有say方法,p在本身的__proto__屬性裏找say方法,也就是p.__proto__,即programmer.prototype裏查找,可是這裏也沒有say方法,繼續沿着programmer.prototype.__proto__查找,也就是person.prototype裏查找say方法,這裏發現了say方法,因而調用

  而調用wcd方法過程以下:

  因爲p裏沒有wcd方法,p在本身的__proto__屬性裏找say方法,也就是p.__proto__,即programmer.prototype裏查找,這裏有wcd方法,因此調用這個方法

  p的salary屬性調用過程也相似:

  因爲p自身沒有salary屬性,會在本身的__proto__屬性裏找say屬性,也就是p.__proto__,即programmer.prototype裏查找,而這裏有salary屬性,屬性值爲1000,返回並輸出1000,因此輸出不是500

 

  當使用對象的方法時,先在當前對象中查找,若是有則使用,沒有就查找當前對象的__proto__屬性中是否有方法,有則調用,無則繼續向上查找,如此層層往上「遞歸」,造成一條無形的鏈子即爲原型鏈

  

原型繼承

  經過原型方法建立新對象時,若是子對象沒有修改/從新定義同名方法,原型中有的方法會繼承到子對象中

  子對象使用方法時也會沿着原型鏈逐步向上查找,只有在最頂層父元素都沒有該方法時才報錯

  咱們把原型鏈中的案例畫一個圖說明

  

  這下咱們能夠清楚看到,p、programmer和person由於彼此之間的原型的關係,構成了一條無形的原型鏈

  因此這裏的person爺爺輩的,programmer父輩的,而p子輩

  當兒子沒有家產時,能夠名正言順地從父親那裏繼承家產,也就是父親有啥兒子用啥(繼承父一級的方法和屬性

  若是父親把家產變動了(例如家產敗光了或者翻倍了),那就用父親獲得的家產,不然的話,父親的家產就跟爺爺那裏的同樣,也就是實際上繼承了爺爺的家產

  (也就是修改了方法或屬性,子代用父輩的屬性或方法)

  

  這裏舉一個例子說明子對象修改屬性/方法時,後代是如何繼承的

  先放上案例的代碼

  

  執行結果以下

  

  sayhello方法的調用過程以下:

  1.對象s是student實例,調用s的sayhello方法時,會先在s對象本身內部找,發現沒有,而後沿着s.__proto__找,也就是student.prototype查找sayhello方法,可是也沒有

  2.不過,student的prototype是person的實例,這個實例裏name爲李四,age爲18,繼續查找的話,查的是student.prototype.__proto__,也就是person.prototype,在這裏發現了sayhello方法,因而調用

  3.調用的時候,這裏的nameperson的,輸出person的name李四

  grade屬性調用過程以下:

  1.s做爲student的實例,會先在s裏找grade這個屬性,發現沒有,因而沿着原型鏈向上查找s.__proto__,也就是student.prototype,在這裏發現grade屬性,值爲3,調用

  這裏person、student和s的關係以下圖

  

 

構造繼承

  在子類內部構造父類的對象實現繼承

  詳細的實現方法參加下面這個例子

  

  執行結果以下

  

  執行過程分析以下:

  1.先構建了一個parents的實例p,其name值爲張三,p對象裏有say方法,輸出父親的名字張三

  2.再構建一個child的實例c,其name傳入李四age傳入24(注意是傳入,由於這裏c沒有name屬性)

  3.傳入這兩個參數後,執行child這個函數,其中pObj這個屬性指向parents這個函數,以後把name這個參數傳入pObj,這裏的this指向child

  4.調用parents函數,name李四傳入其中,但因爲也傳入了child這個對象,這裏thischild,也就是child.name李四,同時child也有了say方法

  5.調用child的sayC方法,這裏child的name就是剛剛經過調用parents函數傳入的李四,而age則是child這個函數自己就傳入的24(並不是parents裏的age)

  關於age用24,緣由有二

  1.child自身有age這個參數

  2.調用parents函數時,也沒有給parents傳入age參數

  (能夠嘗試給pObj傳入age參數,並把child裏的this.age=age提早到函數的最開始,輸出結果就變成20了)

  

  其實也能夠不用理解成父子的繼承,由於我的感受這裏有點「偷師」的意思,也就是經過傳入參數對象,達到給指定的對象添加一個屬性,而不像原型鏈那樣層層遞推

 

call和apply繼承

  call和apply都是對象的一個方法,第一個參數用來改變this的指向,而call和apply的區別在於第二個參數

  call第二個參數爲獨立的一個個參數,apply第二個參數爲一個數組

   具體的用法看下面的這個案例

  

  執行結果以下

  

  執行過程的分析:

  1.構建stu這個student的實例時,接收了xm以及18兩個參數,進入函數體

  2.函數體內,調用了person對象的call方法(全部對象都有的方法),回調person

  3.跳轉到person函數中,把兩個參數傳遞進去,這裏的this表明當前對象,即stu,而後person函數中使用的this.name等,由於傳入stu這個對象,this指向stu,即stu的name、age等接收傳入的參數,同時stu也有了say方法,其中的this通通都是stu

  tea的構建方式相似

  不過這麼看來,與其說是繼承了方法,不如說是「得到」,能夠看作stu調用了person函數,得到了person裏的全部屬性和方法。固然,得到的屬性被賦值給stu本身了

  結合上面構造繼承的方法,咱們能夠理解爲,下圖所示的兩行代碼的效果,等同於apply/call方法一行代碼的效果

  

 

  好了,終於完全理解這裏的閉包、封裝和繼承了

相關文章
相關標籤/搜索