官方定義爲,閉包即一個擁有許多變量和綁定了這些變量的環境的表達式(一般爲函數)數組
實際上,不用解釋那麼費勁,閉包就的原理就是函數能夠訪問其外部的全局變量閉包
本來在函數外部訪問不到函數裏面的局部變量,爲了能夠訪問函數內的局部變量,在函數中添加一個函數,並讓函數的執行結果返回這個函數體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中,沒有關鍵字來實現封裝,卻是能夠利用閉包來實現封裝的效果(注意是效果)
先上案例的代碼,具體的執行結果和解析在下方(養成個先思考再看答案的好習慣)
注意這裏綠色的註釋部分和test的效果是同樣的,一個是用了字面量的聲明方式,一個是用了構造函數的方式聲明test方法
執行結果以下
整個執行過程以下:
1.這裏用了閉包的方法,在demo這個函數中建立了局部變量n和方法test,並讓函數的執行結果返回test(標準的閉包)
2.這樣一來,用一個全局變量at承接demo的執行結果,就獲得了test這個函數的本體
3.此時執行at函數,因爲at裏沒有n這個局部變量,沿着做用域鏈向上,在demo函數中找到了demo的局部變量n,讓其加1後返回爲2,改變了這個局部變量的值,若是再次執行at函數則返回3
再來一個案例,不過和上面的案例有所不一樣,全局下的函數還用原型模式來添加了一個普通方法
先放上代碼
執行結果以下
整個執行過程以下:
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.調用的時候,這裏的name是person的,輸出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這個對象,這裏this爲child,也就是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都是對象的一個方法,第一個參數用來改變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方法一行代碼的效果
好了,終於完全理解這裏的閉包、封裝和繼承了