




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、 下載APP 10 | 閉包: 理解了原理,它就不反直覺(jué)了2019-09-04 宮文學(xué)編譯原理之美進(jìn)入課程 講述:宮文學(xué)時(shí)長(zhǎng) 14:31 大小 13.30M在講作用域和生存期時(shí),我提到函數(shù)里的本地變量只能在函數(shù)內(nèi)部訪問(wèn),函數(shù)退出之后,作用域就沒(méi)用了,它對(duì)應(yīng)的棧楨被彈出,作用域中的所有變量所占用的內(nèi)存也會(huì)被收回。但偏偏跑出來(lái)閉包(Closure)這個(gè)怪物。在 JavaScript 中,用外層函數(shù)返回一個(gè)內(nèi)層函數(shù)之后,這個(gè)內(nèi)層函數(shù)能一直訪問(wèn)外層函數(shù)中的本地變量。按理說(shuō),這個(gè)時(shí)候外層函數(shù)已經(jīng)退出了,它里面的變量也該作廢了??砷]包卻非常執(zhí)著,即使外層函數(shù)已經(jīng)退出,但內(nèi)層函數(shù)仿佛不知道這個(gè)事實(shí)一樣,還繼
2、續(xù)訪問(wèn)外層函數(shù)中聲明的變量,并且還真的能夠正常訪問(wèn)。不過(guò),閉包是很有用的,對(duì)庫(kù)的編寫者來(lái)講,它能隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié);對(duì)面試者來(lái)講,它幾乎是前端面試必問(wèn)的一個(gè)問(wèn)題,比如如何用閉包特性實(shí)現(xiàn)面向?qū)ο缶幊??等等。本?jié)課,我會(huì)帶你研究閉包的實(shí)現(xiàn)機(jī)制,讓你深入理解作用域和生存期,更好地使用閉包特性。為此,要解決兩個(gè)問(wèn)題:函數(shù)要變成 playscript 的一等公民。也就是要能把函數(shù)像普通數(shù)值一樣賦值給變量,可以作為參數(shù)傳遞給其他函數(shù),可以作為函數(shù)的返回值。要讓內(nèi)層函數(shù)一直訪問(wèn)它環(huán)境中的變量,不管外層函數(shù)退出與否。我們先通過(guò)一個(gè)例子,研究一下閉包的特性,看看它另類在哪里。閉包的內(nèi)在矛盾來(lái)測(cè)試一下 JavaSc
3、ript 的閉包特性: 復(fù)制代碼 1 /*2 * clojure.js3 * 測(cè)試閉包特性 4 * 作者:宮文學(xué) 5*/6 var a = 0;78var fun1 = function()9var b = 0;/ 函數(shù)內(nèi)的局部變量 1011var inner = function()/ 內(nèi)部的一個(gè)函數(shù) 12a = a+1;13b = b+1;14return b;/ 返回內(nèi)部的成員 151617return inner;/ 返回一個(gè)函數(shù) 181920console.log(outside:a=%d,a);2122 var fun2 = fun1();/ 生成閉包 23 for (var i
4、= 0; i 2; i+)24console.log(fun2: b=%d a=%d,fun2(), a); / 通過(guò) fun2() 來(lái)訪問(wèn) b25 2627 var fun3 = fun1();/ 生成第二個(gè)閉包 28 for (var i = 0; i 2; i+)29console.log(fun3: b=%d a=%d,fun3(), a); / b 等于 1,重新開始 30 在 Node.js 環(huán)境下運(yùn)行上面這段代碼的結(jié)果如下: 復(fù)制代碼 1 outside: a=0 2 fun2: b=1 a=1 3 fun2: b=2 a=2 4 fun3: b=1 a=3 5 fun3: b=
5、2 a=4觀察這個(gè)結(jié)果,可以得出兩點(diǎn):內(nèi)層的函數(shù)能訪問(wèn)它“看得見(jiàn)”的變量,包括自己的本地變量、外層函數(shù)的變量 b 和全局變量 a。內(nèi)層函數(shù)作為返回值賦值給其他變量以后,外層函數(shù)就結(jié)束了,但內(nèi)層函數(shù)仍能訪問(wèn)原來(lái) 外層函數(shù)的變量 b,也能訪問(wèn)全局變量 a。這樣似乎讓人感到困惑:站在外層函數(shù)的角度看,明明這個(gè)函數(shù)已經(jīng)退出了,變量 b 應(yīng)該失效了,為什么還可以繼續(xù)訪問(wèn)?但是如果換個(gè)立場(chǎng),站在 inner 這個(gè)函數(shù)的角度來(lái)看, 聲明 inner 函數(shù)的時(shí)候,告訴它可以訪問(wèn) b,不能因?yàn)榘?inner 函數(shù)賦值給了其他變量, inner 函數(shù)里原本正確的語(yǔ)句就不能用了啊。其實(shí),只要函數(shù)能作為值傳來(lái)傳去,就
6、一定會(huì)產(chǎn)生作用域不匹配的情況,這樣的內(nèi)在矛盾是語(yǔ)言設(shè)計(jì)時(shí)就決定了的。我認(rèn)為,閉包是為了讓函數(shù)能夠在這種情況下繼續(xù)運(yùn)行所提供的一個(gè)方案。這個(gè)方案有一些不錯(cuò)的特點(diǎn),比如隱藏函數(shù)所使用的數(shù)據(jù),歪打正著反倒成了一個(gè)優(yōu)點(diǎn)了!在這里,我想補(bǔ)充一下靜態(tài)作用域(Static Scope)這個(gè)知識(shí)點(diǎn),如果一門語(yǔ)言的作用域是靜態(tài)作用域,那么符號(hào)之間的引用關(guān)系能夠根據(jù)程序代碼在編譯時(shí)就確定清楚,在運(yùn)行時(shí)不會(huì)變。某個(gè)函數(shù)是在哪聲明的,就具有它所在位置的作用域。它能夠訪問(wèn)哪些變量,那么就跟這些變量綁定了,在運(yùn)行時(shí)就一直能訪問(wèn)這些變量。看一看下面的代碼,對(duì)于靜態(tài)作用域而言,無(wú)論在哪里調(diào)用 foo() 函數(shù),訪問(wèn)的變量 i
7、 都是全局變量: 復(fù)制代碼 1 int i = 1;2 void foo()3 println(i); / 訪問(wèn)全局變量 4 56 foo();/ 訪問(wèn)全局變量 78 void bar()9 int i = 2;10 foo();/ 在這里調(diào)用 foo(),訪問(wèn)的仍然是全局變量 11 我們目前使用的大多數(shù)語(yǔ)言都是采用靜態(tài)作用域的。playscript 語(yǔ)言也是在編譯時(shí)就形成一個(gè) Scope 的樹,變量的引用也是在編譯時(shí)就做了消解,不再改變,所以也是采用了靜態(tài)作用域。反過(guò)來(lái)講,如果在 bar() 里調(diào)用 foo() 時(shí),foo() 訪問(wèn)的是 bar() 函數(shù)中的本地變量 i,那就說(shuō)明這門語(yǔ)言使用
8、的是動(dòng)態(tài)作用域(Dynamic Scope)。也就是說(shuō),變量引用跟變量聲明不是在編譯時(shí)就綁定死了的。在運(yùn)行時(shí),它是在運(yùn)行環(huán)境中動(dòng)態(tài)地找一個(gè)相同名稱的變量。在macOS 或 Linux 中用的 bash 腳本語(yǔ)言,就是動(dòng)態(tài)作用域的。靜態(tài)作用域可以由程序代碼決定,在編譯時(shí)就能完全確定,所以又叫做詞法作用域(Lexcical Scope)。不過(guò)這個(gè)詞法跟我們做詞法分析時(shí)說(shuō)的詞法不大一樣。這里,跟Lexical 相對(duì)應(yīng)的詞匯可以認(rèn)為是 Runtime,一個(gè)是編寫時(shí),一個(gè)是運(yùn)行時(shí)。用靜態(tài)作用域的概念描述一下閉包,我們可以這樣說(shuō):因?yàn)槲覀兊恼Z(yǔ)言是靜態(tài)作用域的,它能夠訪問(wèn)的變量,需要一直都能訪問(wèn),為此,需要
9、把某些變量的生存期延長(zhǎng)。當(dāng)然了,閉包的產(chǎn)生還有另一個(gè)條件,就是讓函數(shù)成為一等公民。這是什么意思?我們又怎樣實(shí)現(xiàn)呢?函數(shù)作為一等公民在 JavaScript 和 Python 等語(yǔ)言里,函數(shù)可以像數(shù)值一樣使用,比如給變量賦值、作為參數(shù)傳遞給其他函數(shù),作為函數(shù)返回值等等。這時(shí),我們就說(shuō)函數(shù)是一等公民。作為一等公民的函數(shù)很有用,比如它能處理數(shù)組等集合。我們給數(shù)組的 map 方法傳入一個(gè)回調(diào)函數(shù),結(jié)果會(huì)生成一個(gè)新的數(shù)組。整個(gè)過(guò)程很簡(jiǎn)潔,沒(méi)有出現(xiàn)啰嗦的循環(huán)語(yǔ)句,這也是很多人提倡函數(shù)式編程的原因之一: 復(fù)制代碼 1 var newArray = 1,2,3.map(2 fucntion(value,ind
10、ex,array)3 return parseInt(value,10)4)那么在 playscript 中,怎么把函數(shù)作為一等公民呢?我們需要支持函數(shù)作為基礎(chǔ)類型,這樣就可以用這種類型聲明變量。但問(wèn)題來(lái)了,如何聲明一個(gè)函數(shù)類型的變量呢?在 JavaScript 這種動(dòng)態(tài)類型的語(yǔ)言里,我們可以把函數(shù)賦值給任何一個(gè)變量,就像前面示例代碼里的那樣:inner 函數(shù)作為返回值,被賦給了 fun2 和 fun3 兩個(gè)變量。然而在 Go 語(yǔ)言這樣要求嚴(yán)格類型匹配的語(yǔ)言里,就比較復(fù)雜了: 復(fù)制代碼 1 type funcType func(int) int / Go 語(yǔ)言,聲明了一個(gè)函數(shù)類型 funcTy
11、pe2 var myFun funType/ 用這個(gè)函數(shù)類型聲明了一個(gè)變量 它對(duì)函數(shù)的原型有比較嚴(yán)格的要求:函數(shù)必須有一個(gè) int 型的參數(shù),返回值也必須是 int 型的。而 C 語(yǔ)言中函數(shù)指針的聲明也是比較嚴(yán)格的,在下面的代碼中,myFun 指針能夠指向一個(gè)函數(shù),這個(gè)函數(shù)也是有一個(gè) int 類型的參數(shù),返回值也是 int:1 int (*myFun) (int);/C 語(yǔ)言,聲明一個(gè)函數(shù)指針 復(fù)制代碼 playscript 也采用這種比較嚴(yán)格的聲明方式,因?yàn)槲覀兿雽?shí)現(xiàn)一個(gè)靜態(tài)類型的語(yǔ)言:1 function int (int) myFun;/playscript 中聲明一個(gè)函數(shù)型的變量 復(fù)制
12、代碼 寫成上面這樣是因?yàn)槲覀€(gè)人喜歡把變量名稱左邊的部分看做類型的描述,不像 Go 語(yǔ)言把類型放在變量名稱后面。最難讀的就是 C 語(yǔ)言那種聲明方式了,竟然把變量名放在了中間。當(dāng)然,這只是個(gè)人喜好。把上面描述函數(shù)類型的語(yǔ)法寫成 Antlr 的規(guī)則如下: 復(fù)制代碼 1 functionType2 : FUNCTION typeTypeOrVoid ( typeList? )3;45 typeList6 : typeType (, typeType)*7;在 playscript 中,我們用 FuntionType 接口代表一個(gè)函數(shù)類型,通過(guò)這個(gè)接口可以獲得返回值類型、參數(shù)類型這兩個(gè)信息: 復(fù)制代碼
13、1 package play;2 import java.util.List;3 /*4* 函數(shù)類型 5*/6 public interface FunctionType extends Type 7 public Type getReturnType();/ 返回值類型 8 public List getParamTypes(); / 參數(shù)類型 9 試一下實(shí)際使用效果如何,用 Antlr 解析下面這句的語(yǔ)法: 復(fù)制代碼 1 function int(long, float) fun2 = fun1();它的意思是:調(diào)用 fun1() 函數(shù)會(huì)返回另一個(gè)函數(shù),這個(gè)函數(shù)有兩個(gè)參數(shù),返回值是 int
14、 型的。我們用 grun 顯示一下 AST,你可以看到,它已經(jīng)把 functionType 正確地解析出來(lái)了:目前,我們只是設(shè)計(jì)完了語(yǔ)法,還要實(shí)現(xiàn)運(yùn)行期的功能,讓函數(shù)真的能像數(shù)值一樣傳來(lái)傳去,就像下面的測(cè)試代碼,它把 foo() 作為值賦給了 bar(): 復(fù)制代碼 1/*2FirstClassFunction.play 函數(shù)作為一等公民。 3也就是函數(shù)可以數(shù)值,賦給別的變量。 4支持函數(shù)類型,即 FunctionType。 5*/6int foo(int a)7println(in foo, a = + a);8return a;91011int bar (function int(int)
15、 fun)12int b = fun(6);13println(in bar, b = + b);14return b;151617function int(int) a = foo;/ 函數(shù)作為變量初始化值18a(4);1920function int(int) b;21 b = foo;/ 函數(shù)用于賦值語(yǔ)句 22 b(5);2324 bar(foo);/ 函數(shù)做為參數(shù) 運(yùn)行結(jié)果如下: 復(fù)制代碼 1 in foo, a = 42 in foo, a = 53 in foo, a = 64 in bar, b = 6運(yùn)行這段代碼,你會(huì)發(fā)現(xiàn)它實(shí)現(xiàn)了用函數(shù)來(lái)賦值,而實(shí)現(xiàn)這個(gè)功能的重點(diǎn),是做好語(yǔ)義分
16、析。比如編譯程序要能識(shí)別賦值語(yǔ)句中的 foo 是一個(gè)函數(shù),而不是一個(gè)傳統(tǒng)的值。在調(diào)用a() 和 b() 的時(shí)候,它也要正確地調(diào)用 foo() 的代碼,而不是報(bào)“找不到 a() 函數(shù)的定義”這樣的錯(cuò)誤。實(shí)現(xiàn)了一等公民函數(shù)的功能以后,我們進(jìn)入本講最重要的一環(huán):實(shí)現(xiàn)閉包功能。實(shí)現(xiàn)我們自己的閉包機(jī)制在這之前,我想先設(shè)計(jì)好測(cè)試用例,所以先把一開始提到的那個(gè) JavaScript 的例子用playscript 的語(yǔ)法重寫一遍,來(lái)測(cè)試閉包功能: 1/*2* clojure.play3* 測(cè)試閉包特性 4*/5int a = 0;67function int() fun1() 復(fù)制代碼 / 函數(shù)的返回值是一個(gè)
17、函數(shù) 8int b = 0;/ 函數(shù)內(nèi)的局部變量910int inner()/ 內(nèi)部的一個(gè)函數(shù) 11a = a+1;12b = b+1;13return b;/ 返回內(nèi)部的成員 14151617 18return inner;/ 返回一個(gè)函數(shù) 19 function int() fun2 = fun1();20 for (int i = 0; i 3; i+)21println(b = + fun2() + , a = +a);22 2324 function int() fun3 = fun1();25 for (int i = 0; i 3; i+)26println(b = + fun3
18、() + , a = +a);27 代碼的運(yùn)行效果跟 JavaScript 版本的程序是一樣的: 復(fù)制代碼 1b=1,a= 12b=2,a= 23b=3,a= 34b=1,a= 45b=2,a= 56b=3,a= 6這段代碼的 AST 我也讓 grun 顯示出來(lái)了,并截了一部分圖,你可以直觀地看一下外層函數(shù)和內(nèi)層函數(shù)的關(guān)系:現(xiàn)在,測(cè)試用例準(zhǔn)備好了,我們著手實(shí)現(xiàn)一下閉包的機(jī)制。前面提到,閉包的內(nèi)在矛盾是運(yùn)行時(shí)的環(huán)境和定義時(shí)的作用域之間的矛盾。那么我們把內(nèi)部環(huán)境中需要的變量,打包交給閉包函數(shù),它就可以隨時(shí)訪問(wèn)這些變量了。在 AST 上做一下圖形化的分析,看看給 fun2 這個(gè)變量賦值的時(shí)候,發(fā)生了
19、什么事情:簡(jiǎn)單地描述一下給 fun2 賦值時(shí)的執(zhí)行過(guò)程:先執(zhí)行 fun1() 函數(shù),內(nèi)部的 inner() 函數(shù)作為返回值返回給調(diào)用者。這時(shí),程序能訪問(wèn)兩層作用域,最近一層是 fun1(),里面有變量 b;外層還有一層,里面有全局變量 a。這時(shí)是把環(huán)境變量打包的最后的機(jī)會(huì),否則退出 fun1() 函數(shù)以后,變量 b 就消失了。然后把內(nèi)部函數(shù)連同打包好的環(huán)境變量的值,創(chuàng)建一個(gè) FunctionObject 對(duì)象,作為fun1() 的返回值,給到調(diào)用者。給 fun2 這個(gè)變量賦值。調(diào)用 fun2() 函數(shù)。函數(shù)執(zhí)行時(shí),有一個(gè)私有的閉包環(huán)境可以訪問(wèn) b 的值,這個(gè)環(huán)境就是第二步所創(chuàng)建的 Functi
20、onObject 對(duì)象。最終,我們實(shí)現(xiàn)了閉包的功能。在這個(gè)過(guò)程中,我們要提前記錄下 inner() 函數(shù)都引用了哪些外部變量,以便對(duì)這些變量打包。這是在對(duì)程序做語(yǔ)義分析時(shí)完成的,你可以參考一下ClosureAnalyzer.java中的代碼: 復(fù)制代碼 1/*2* 為某個(gè)函數(shù)計(jì)算閉包變量,也就是它所引用的外部環(huán)境變量。 3* 算法:計(jì)算所有的變量引用,去掉內(nèi)部聲明的變量,剩下的就是外部的。 4* param function5* return6*/7 private Set calcClosureVariables(Function function)8Set refered = variab
21、lesReferedByScope(function);9Set declared = variablesDeclaredUnderScope(function);10refered.removeAll(declared);11return refered;12下面是 ASTEvaluator.java 中把環(huán)境變量打包進(jìn)閉包中的代碼片段,它是在當(dāng)前的棧里獲取數(shù)據(jù)的: 復(fù)制代碼 1 /*2 * 為閉包獲取環(huán)境變量的值 3 * param function 閉包所關(guān)聯(lián)的函數(shù)。這個(gè)函數(shù)會(huì)訪問(wèn)一些環(huán)境變量。 4 * param valueContainer存放環(huán)境變量的值的容器 5*/6 priva
22、te void getClosureValues(Function function, PlayObject valueContainer)7 if (function.closureVariables != null) 8 for (Variable var : function.closureVariables) 9 / 現(xiàn)在還可以從棧里取,退出函數(shù)以后就不行了 10 LValue lValue = getLValue(var);11 Object value = lValue.getValue();12 valueContainer.fields.put(var, value);1314
23、15 你可以把測(cè)試用例跑一跑,修改一下,試試其他閉包特性。體驗(yàn)一下函數(shù)式編程現(xiàn)在,我們已經(jīng)實(shí)現(xiàn)了閉包的機(jī)制,函數(shù)也變成了一等公民。不經(jīng)意間,我們似乎在一定程度上支持了函數(shù)式編程(functional programming)。它是一種語(yǔ)言風(fēng)格,有很多優(yōu)點(diǎn),比如簡(jiǎn)潔、安全等。備受很多程序員推崇的 LISP 語(yǔ)言就具備函數(shù)式編程特征,Java 等語(yǔ)言也增加了函數(shù)式編程的特點(diǎn)。函數(shù)式編程的一個(gè)典型特點(diǎn)就是高階函數(shù)(High-order function)功能,高階函數(shù)是這樣一種函數(shù),它能夠接受其他函數(shù)作為自己的參數(shù),javascript 中數(shù)組的 map 方法,就是一個(gè)高階函數(shù)。我們通過(guò)下面的例子測(cè)
24、試一下高階函數(shù)功能: 復(fù)制代碼 1 /*2 LinkedList.play3 實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的鏈表,并演示了高階函數(shù)的功能,比如在 javascript 中常用的 map 功能, 4 它能根據(jù)遍歷列表中的每個(gè)元素,執(zhí)行一個(gè)函數(shù),并返回一個(gè)新的列表。給它傳不同的函數(shù),會(huì)返回不同的列 5 */6 / 鏈表的節(jié)點(diǎn) 7 class ListNode8 int value;9 ListNode next; / 下一個(gè)節(jié)點(diǎn) 1011 ListNode (int v)12 value = v;1314 1516 / 鏈 表 17 class LinkedList18 ListNode start;19 Li
25、stNode end;2021 / 添加新節(jié)點(diǎn) 22 void add(int value)23 ListNode node = ListNode(value);24 if (start = null)25 start = node;26 end = node;2728 else29 end.next = node;30 end = node;31323334 / 打印所有節(jié)點(diǎn)內(nèi)容 35 void dump()36 ListNode node = start;37 while (node != null)38 println(node.value);39 node = node.next;40
26、4142434445464748495051525354 55/ 高階函數(shù)功能,參數(shù)是一個(gè)函數(shù),對(duì)每個(gè)成員做一個(gè)計(jì)算,形成一個(gè)新的 LinkedList LinkedList map(function int(int) fun)ListNode node = start;LinkedList newList = LinkedList(); while (node != null)int newValue = fun(node.value); newList.add(newValue);node = node.next;return newList;56 / 函數(shù):平方值 57 int squa
27、re(int value)58 return value * value;59 6061 / 函數(shù):加 162 int addOne(int value)63 return value + 1;64 6566 LinkedList list = LinkedList();67 list.add(2);68 list.add(3);69 list.add(5);7071 println(original list:);72 list.dump();7374 println();75 println(add 1 to each element:);76 LinkedList list2 = lis
28、t.map(addOne);77 list2.dump();7879 println();80 println(square of each element:);81 LinkedList list3 = list.map(square);82 list3.dump();運(yùn)行后得到的結(jié)果如下: 復(fù)制代碼 1 original list:2 23 34 556 add 1 to each element:7 38 49 61011 square of each element:12 413 914 25高階函數(shù)功能很好玩,你可以修改程序,好好玩一下。課程小結(jié)閉包這個(gè)概念,對(duì)于初學(xué)者來(lái)講是一個(gè)挑戰(zhàn)
29、。其實(shí),閉包就是把函數(shù)在靜態(tài)作用域中所訪問(wèn)的變量的生存期拉長(zhǎng),形成一份可以由這個(gè)函數(shù)單獨(dú)訪問(wèn)的數(shù)據(jù)。正因?yàn)檫@些數(shù)據(jù)只能被閉包函數(shù)訪問(wèn),所以也就具備了對(duì)信息進(jìn)行封裝、隱藏內(nèi)部細(xì)節(jié)的特性。聽上去是不是有點(diǎn)兒耳熟?封裝,把數(shù)據(jù)和對(duì)數(shù)據(jù)的操作封在一起,這不就是面向?qū)ο缶幊搪?!一個(gè)閉包可以看做是一個(gè)對(duì)象。反過(guò)來(lái)看,一個(gè)對(duì)象是不是也可以看做一個(gè)閉包呢?對(duì)象的屬性,也可以看做被方法所獨(dú)占的環(huán)境變量,其生存期也必須保證能夠被方法一直正常的訪問(wèn)。你看,兩個(gè)不相干的概念,在用作用域和生存期這樣的話語(yǔ)體系去解讀之后,就會(huì)很相似, 在內(nèi)部實(shí)現(xiàn)上也可以當(dāng)成一回事?,F(xiàn)在,你應(yīng)該更清楚了吧?一課一思思考一下我在開頭提到的那
30、個(gè)面試題:如何用閉包做類似面向?qū)ο蟮木幊??其?shí),我在課程中提供了一個(gè) closure-mammal.play 的示例代碼,它完全用閉包的概念實(shí)現(xiàn)了面向?qū)ο缶幊痰亩鄳B(tài)特征。而這個(gè)閉包的實(shí)現(xiàn),是一種更高級(jí)的閉包,比普通的函數(shù)閉包還多了一點(diǎn)有用的特性,更像對(duì)象了。我希望你能發(fā)現(xiàn)它到底不同在哪里,也能在代碼中找到實(shí)現(xiàn)這些特性的位置。你能發(fā)現(xiàn),我一直在講作用域和生存期,不要嫌我啰嗦,把它們吃透,會(huì)對(duì)你使用語(yǔ)言有很大幫助。比如,有同學(xué)非常困擾 JavaScript 的 this,我負(fù)責(zé)任地講,只要對(duì)作用域有清晰的了解,你就能很容易地掌握 this。那么,關(guān)于作用域跟 this 之間的關(guān)聯(lián),如果你有什么想法,也歡迎在留言區(qū)分享。最后,感謝你的閱讀,如果這篇文章讓你有所收獲,也歡迎你將它分享給更多的朋友,特別是分享給那些還沒(méi)搞清楚閉包的朋友。本節(jié)課的示例代碼放在了文末,供你參考。playscript-java(項(xiàng)目目錄): 碼云 GitHub PlayScript.java(入口程序): 碼云 GitHub PlayScript.g4(語(yǔ)法規(guī)則):
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 預(yù)防職業(yè)病教學(xué)課件
- 新生兒肺炎表現(xiàn)及預(yù)防
- 《電子產(chǎn)品制造技術(shù)》課件-第2章 印制電路板認(rèn)知
- 沖床維修培訓(xùn)
- 順利消防2023課件
- 項(xiàng)目現(xiàn)場(chǎng)安全課件
- 《道路勘測(cè)設(shè)計(jì)》課件-第三章 平面設(shè)計(jì)
- 音樂(lè)律動(dòng)介紹課件
- 汽車配套產(chǎn)業(yè)基地項(xiàng)目風(fēng)險(xiǎn)管理方案(范文)
- 城市污水管網(wǎng)建設(shè)工程投資估算方案(模板)
- 課件:曝光三要素
- 2023藍(lán)橋杯科學(xué)素養(yǎng)競(jìng)賽考試題庫(kù)(含答案)
- 中小學(xué)校長(zhǎng)招聘考試試題
- 大報(bào)告廳EASE聲場(chǎng)模擬分析
- (完整版)土的參數(shù)換算(計(jì)算飽和重度)
- 境外匯款申請(qǐng)書樣板
- 抗焦慮藥和抗抑郁藥教學(xué)課件
- 2023年浙江溫州技師學(xué)院招聘教師(共500題含答案解析)高頻考點(diǎn)題庫(kù)參考模擬練習(xí)試卷
- 撫養(yǎng)費(fèi)糾紛答辯狀
- 河南暴雨參數(shù)計(jì)算表
- 產(chǎn)品質(zhì)量證明書
評(píng)論
0/150
提交評(píng)論