Dart 進階 | 深入理解 Function & Closure

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在最初設計 Dart 的時候,參考了 "},{"type":"codeinline","content":[{"type":"text","text":"JavaScript"}]},{"type":"text","text":" 許多特性。無論是在異步處理,還是在語法上,都能看到它的影子。熟悉 Dart 的同學應該明白,在 Dart 中一切皆爲對象。不僅 "},{"type":"codeinline","content":[{"type":"text","text":"int"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"bool"}]},{"type":"text","text":" 是通過 core library 提供的類創建出的對象,連函數也被看作是對象。(本文中可能會出現 "},{"type":"text","marks":[{"type":"strong"}],"text":"函數"},{"type":"text","text":" / "},{"type":"text","marks":[{"type":"strong"}],"text":"方法"},{"type":"text","text":" 二者僅叫法不同)而本文將帶你深入理解 Dart 的函數 (Function)&閉包(Closure)以及它們的用法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"什麼是 Closure(閉包)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你從未聽說過閉包,沒關係,本節將會從零開始引入閉包這個概念。在正式介紹閉包之前,我們需要先來了解一下 "},{"type":"text","marks":[{"type":"strong"}],"text":"Lexical scoping"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"詞法作用域 Lexical scoping"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也許你對這個詞很陌生,但是它卻是最熟悉的陌生人。我們先來看下面一段代碼。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n var a = 0;\n var a = 1; // Error:The name 'a' is already defined\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你肯定已經發現了,我們在該段代碼中犯了一個明顯的錯誤。那就是定義了兩次變量 "},{"type":"codeinline","content":[{"type":"text","text":"a"}]},{"type":"text","text":",而編譯器也會提示我們,a 這個變量名已經被定義了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這是由於,我們的變量都有它的 "},{"type":"text","marks":[{"type":"strong"}],"text":"詞法作用域"},{"type":"text","text":" ,在同一個詞法作用域中僅允許存在一個名稱爲 "},{"type":"codeinline","content":[{"type":"text","text":"a"}]},{"type":"text","text":" 的變量,且在編譯期就能夠提示語法錯誤。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這很好理解,如果一個 "},{"type":"text","marks":[{"type":"strong"}],"text":"Lexical scoping"},{"type":"text","text":" 中存在兩個同名變量 "},{"type":"codeinline","content":[{"type":"text","text":"a"}]},{"type":"text","text":",那麼我們訪問的時候從語法上就無法區分到底你是想要訪問哪一個 "},{"type":"codeinline","content":[{"type":"text","text":"a"}]},{"type":"text","text":" 了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" > 上述代碼中,我們在 "},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":" 函數的詞法作用域中定義了兩次 a"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"僅需稍作修改"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ``` dart"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" void main() {"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" var a = 1; "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" print(a); // => 1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" var a = 0;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" ```"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們就能夠正常打印出 "},{"type":"codeinline","content":[{"type":"text","text":"a"}]},{"type":"text","text":" 的值爲 1。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單的解釋,"},{"type":"codeinline","content":[{"type":"text","text":" var a = 0;"}]},{"type":"text","text":" 是該 "},{"type":"text","marks":[{"type":"strong"}],"text":"dart 文件"},{"type":"text","text":"的 *"},{"type":"text","marks":[{"type":"italic"}],"text":"Lexical scoping"},{"type":"text","text":"* 中定義的變量,而 "},{"type":"codeinline","content":[{"type":"text","text":"var a = 1;"}]},{"type":"text","text":" 是在 main 函數的 "},{"type":"text","marks":[{"type":"strong"}],"text":"Lexical scoping"},{"type":"text","text":" 中定義的變量,二者不是一個空間,所以不會產生衝突。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Function is Object"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,要證明方法(函數)是一個對象這很簡單。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"print( (){} is Object ); // true"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"(){}"}]},{"type":"text","text":" 爲一個匿名函數,我們可以看到輸出爲 "},{"type":"codeinline","content":[{"type":"text","text":"true"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"知道了 Function is Object 還不夠,我們應該如何看待它呢。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n var name = 'Vadaski';\n \n var printName = (){\n print(name);\n };\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以很清楚的看到,我們可以在 "},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":" 函數內定義了一個新的方法,而且還能夠將這個方法賦值給一個變量 "},{"type":"codeinline","content":[{"type":"text","text":"printName"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是如果你運行這段代碼,你將看不到任何輸出,這是爲什麼呢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上我們在這裏定義了 "},{"type":"codeinline","content":[{"type":"text","text":"printName"}]},{"type":"text","text":" 之後,並沒有真正的去執行它。我們知道,要執行一個方法,需要使用 "},{"type":"codeinline","content":[{"type":"text","text":"XXX()"}]},{"type":"text","text":" 才能真正執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n var name = 'Vadaski';\n \n var printName = (){\n print(name);\n };\n \n printName(); // Vadaski\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面這個例子非常常見,在 "},{"type":"codeinline","content":[{"type":"text","text":"printName"}]},{"type":"text","text":" 內部訪問到了外部定義的變量 "},{"type":"codeinline","content":[{"type":"text","text":"name"}]},{"type":"text","text":"。也就是說,一個 Lexical scoping "},{"type":"text","marks":[{"type":"strong"}],"text":"內部"},{"type":"text","text":" 是能夠訪問到 "},{"type":"text","marks":[{"type":"strong"}],"text":"外部"},{"type":"text","text":" Lexical scoping 中定義的變量的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Function + Lexical scoping"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"內部"},{"type":"text","text":"訪問*"},{"type":"text","marks":[{"type":"italic"}],"text":"外部"},{"type":"text","text":"*定義的變量是 ok 的,很容易就能夠想到,外部是否可以訪問內部定義的變量呢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果是正常訪問的話,就像下面這樣。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n \n var printName = (){\n var name = 'Vadaski';\n };\n printName();\n \n print(name); // Error:Undefined name 'name'\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏出現了"},{"type":"text","marks":[{"type":"strong"}],"text":"未定義該變量"},{"type":"text","text":"的錯誤警告,可以看出 "},{"type":"codeinline","content":[{"type":"text","text":"printName"}]},{"type":"text","text":" 中定義的變量,對於 "},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":" 函數中的變量是不可見的。Dart 和 JavaScript 一樣具有鏈式作用域,也就是說,"},{"type":"text","marks":[{"type":"strong"}],"text":"子作用域"},{"type":"text","text":"可以訪問"},{"type":"text","marks":[{"type":"strong"}],"text":"父(甚至是祖先)作用域"},{"type":"text","text":"中的變量,而反過來不行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"訪問規則"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上面的例子我們可以看出,"},{"type":"text","marks":[{"type":"strong"}],"text":"Lexical scoping"},{"type":"text","text":" 實際上是以鏈式存在的。一個 scope 中可以開一個新的 scope,而不同 scope 中是可以允許重名變量的。那麼我們在某個 scope 中訪問一個變量,究竟是基於什麼規則來訪問變量的呢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n var a = 1;\n firstScope(){\n var a = 2;\n print('$a in firstScope'); //2 in firstScope\n }\n print('$a in mainScope'); //1 in mainScope\n firstScope();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在上面這個例子中我們可以看到,在 main 和 firstScope 中都定義了變量 a。我們在 "},{"type":"codeinline","content":[{"type":"text","text":"firstScope"}]},{"type":"text","text":" 中 print,輸出了 "},{"type":"codeinline","content":[{"type":"text","text":"2 in firstScope"}]},{"type":"text","text":" 而在 main 中 print 則會輸出 "},{"type":"codeinline","content":[{"type":"text","text":"1 in mainScope"}]},{"type":"text","text":" 。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們已經可以總結出規律了:"},{"type":"text","marks":[{"type":"strong"}],"text":"近者優先"},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你在某個 scope 中訪問一個變量,它首先會看當前 scope 中是否已經定義該變量,如果已經定義,那麼就使用該變量。如果當前 scope 沒找到該變量,那麼它就會在它的上一層 scope 中尋找,以此類推,直到最初的 scope。如果所有 scope 鏈上都不存在該變量,則會提示 "},{"type":"codeinline","content":[{"type":"text","text":"Error:Undefined name 'name'"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tip: Dart scope 中的變量是靜態確定的,如何理解呢?"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"``` dart"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"void main() {"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" print(a); // Local variable 'a' can't be referenced before it is declared"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" var a;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"var a = 0;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"```"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":">"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們可以看到,雖然在 main 的父 scope 中存在變量 a,且已經賦值,但是我們在 main 的 scope 中也定義了變量 a。因爲是靜態確定的,所以在 print 的時候會優先使用當前 scope 中定義的 a,而這時候 a 的定義在 print 之後,同樣也會導致編譯器錯誤:Local variable 'a' can't be referenced before it is declared。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Closure 的定義"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有了上面這些知識,我們現在可以來看看 Closure 的定義了。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"A closure is a function object that has access to variables in its lexical scope, even when the function is used outside of its original scope."}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閉包 即一個函數對象,即使函數對象的調用在它原始作用域之外,依然能夠訪問在它詞法作用域內的變量。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你可能對這段話還是很難一下就理解到它到底在說什麼。如果簡要概括 Closure 的話,它實際上就是"},{"type":"text","marks":[{"type":"strong"}],"text":"有狀態"},{"type":"text","text":"的函數。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"函數狀態"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"無狀態函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通常我們執行一個函數,它都是"},{"type":"text","marks":[{"type":"strong"}],"text":"無狀態"},{"type":"text","text":"的。你可能會產生疑問,啥?狀態??我們還是看一個例子。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n printNumber(); // 10\n printNumber(); // 10\n}\n\nvoid printNumber(){\n int num = 0;\n for(int i = 0; i < 10; i++){\n num++;\n }\n print(num);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面的代碼很好預測,它將會輸出兩次 10,我們多次調用一個函數的時候,它還是會得到一樣的輸出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,當我們理解 Function is Object 之後,我們應該如何從 Object 的角度來看待函數的執行呢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顯然 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber();"}]},{"type":"text","text":" 創建了一個 Function 對象,但是我們沒有將它賦值給任何變量,下次一個 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber();"}]},{"type":"text","text":" 實際上創建了一個新的 Function,兩個對象都執行了一遍方法體,所以得到了相同的輸出。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"有狀態函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無狀態函數很好理解,我們現在可以來看看有狀態的函數了。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n var numberPrinter = (){\n int num = 0;\n return (){\n for(int i = 0; i < 10; i++){\n num++;\n }\n print(num);\n };\n };\n \n var printNumber = numberPrinter();\n printNumber(); // 10\n printNumber(); // 20\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面這段代碼同樣執行了兩次 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber();"}]},{"type":"text","text":",然而我們卻得到了不同的輸出 10,20。好像有點 "},{"type":"text","marks":[{"type":"strong"}],"text":"狀態"},{"type":"text","text":" 的味道了呢。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但看上去似乎還是有些難以理解,讓我們一層一層來看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"var numberPrinter = (){\n int num = 0;\n /// execute function\n };"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先我們定義了一個 Function 對象,然後把交給 "},{"type":"codeinline","content":[{"type":"text","text":"numberPrinter"}]},{"type":"text","text":" 管理。在創建出來的這個 Function 的 "},{"type":"text","marks":[{"type":"strong"}],"text":"Lexical scoping"},{"type":"text","text":" 中定義了一個 num 變量,並賦值爲 0。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注意:這時候該方法並不會立刻執行,而是等調用了 "},{"type":"codeinline","content":[{"type":"text","text":"numberPrinter()"}]},{"type":"text","text":" 的時候才執行。所以這時候 num 是不存在的。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"return (){\n for(int i = 0; i < 10; i++){\n num++;\n }\n print(num);\n};"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後返回了一個 Function。這個 Function 能夠拿到其父級 scope 中的 num ,並讓其增加 10,然後打印 "},{"type":"codeinline","content":[{"type":"text","text":"num"}]},{"type":"text","text":" 的值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"var printNumber = numberPrinter();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後我們通過調用 numberPrinter(),創建了該 Function 對象,"},{"type":"text","marks":[{"type":"strong"}],"text":"這就是一個 Closure!"},{"type":"text","text":" 這個對象"},{"type":"text","marks":[{"type":"strong"}],"text":"真正執行"},{"type":"text","text":"我們剛纔定義的 "},{"type":"codeinline","content":[{"type":"text","text":"numberPrinter"}]},{"type":"text","text":",並且在它的內部的 scope 中就定義了一個 int 類型的 "},{"type":"codeinline","content":[{"type":"text","text":"num"}]},{"type":"text","text":"。然後返回了一個方法給 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上返回的 匿名 Function 又是另一個閉包了。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後我們執行第一次 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber()"}]},{"type":"text","text":",這時候將會獲得閉包儲存的 num 變量,執行下面的內容。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"// num: 0\nfor(int i = 0; i < 10; i++){\n num++; \n}\nprint(num);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最開始 printNumber 的 scope 中儲存的 num 爲 0,所以經過 10 次自增,num 的值爲 10,最後 "},{"type":"codeinline","content":[{"type":"text","text":"print"}]},{"type":"text","text":" 打印了 10。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而第二次執行 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber()"}]},{"type":"text","text":" 我們使用的還是同一個 "},{"type":"codeinline","content":[{"type":"text","text":"numberPrinter"}]},{"type":"text","text":" 對象,這個對象在第一次執行完畢後,其 num 已經爲 10,所以第二次執行後,是從 10 開始自增,那麼最後 "},{"type":"codeinline","content":[{"type":"text","text":"print"}]},{"type":"text","text":" 的結果自然就是 20 了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在整個調用過程中,printNumber 作爲一個 closure,它保存了內部 num 的狀態,只要 printNumber 不被回收,那麼其內部的所有對象都不會被 GC 掉。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以我們也需要注意到閉包可能會造成內存泄漏,或帶來內存壓力問題。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"到底啥是閉包"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再回過頭來理解一下,我們對於閉包的定義就應該好理解了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閉包 即一個函數對象,即使函數對象的調用在它原始作用域之外,依然能夠訪問在它詞法作用域內的變量。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在剛纔的例子中,我們的 num 是在 "},{"type":"codeinline","content":[{"type":"text","text":"numberPrinter"}]},{"type":"text","text":" 內部定義的,可是我們可以通過返回的 Function 在外部訪問到了這個變量。而我們的 "},{"type":"codeinline","content":[{"type":"text","text":"printNumber"}]},{"type":"text","text":" 則一直保存了 "},{"type":"codeinline","content":[{"type":"text","text":"num"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"分階段看閉包"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在我們使用閉包的時候,我將它看爲三個階段。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"定義階段"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這個階段,我們定義了 Function 作爲閉包,但是卻沒有真正執行它。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main() {\n var numberPrinter = (){\n int num = 0;\n return (){\n print(num);\n };\n };"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時候,由於我們只是定義了閉包,而沒有執行,所以 num 對象是不存在的。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"創建階段"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"var printNumber = numberPrinter();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這時候,我們真正執行了 nu mberPrinter 閉包的內容,並返回執行結果,num 被創建出來。這時候,只要 printNumber 不被 GC,那麼 num 也會一直存在。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"訪問階段"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"printNumber(); \nprintNumber();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後我們可以通過某種方式訪問 numberPrinter 閉包中的內容。(本例中間接訪問了 num)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上三個階段僅方便理解,不是嚴謹描述。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Closure 的應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果僅是理解概念,那麼我們看了可能也就忘了。來點實在的,到底 Closure 可以怎麼用?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"在傳遞對象的位置執行方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"比如說我們有一個 Text Widget 的內容有些問題,直接給我們 show 了一個 Error Widget。這時候,我想打印一下這個內容看看到底發生了啥,你可以這樣做。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"Text((){\n print(data);\n return data;\n}())"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是不是很神奇,竟然還有這種操作。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tip 立即執行閉包內容:我們這裏通過閉包的語法 "},{"type":"codeinline","content":[{"type":"text","text":"(){}()"}]},{"type":"text","text":" 立刻執行閉包的內容,並把我們的 data 返回。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"雖然 Text 這裏僅允許我們傳一個 String,但是我依然可以執行 "},{"type":"codeinline","content":[{"type":"text","text":"print"}]},{"type":"text","text":" 方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一個 case 是,如果我們想要僅在 debug 模式下執行某些語句,也可以通過 closure 配合斷言來實現,具體可以看我"},{"type":"link","attrs":{"href":"https://xinlei.dev/2020/04/29/fluent-creates-statements-that-run-only-in-debug-mode/","title":""},"content":[{"type":"text","text":"這篇文章"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"實現策略模式"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 closure 我們可以很方便實現策略模式。"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"void main(){\n var res = exec(select('sum'),1 ,2);\n print(res);\n}\n\nFunction select(String opType){\n if(opType == 'sum') return sum;\n if(opType == 'sub') return sub;\n return (a, b) => 0;\n}\n\nint exec(NumberOp op, int a, int b){\n return op(a,b);\n}\n\nint sum(int a, int b) => a + b;\nint sub(int a, int b) => a - b;\n\ntypedef NumberOp = Function (int a, int b);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過 select 方法,可以動態選擇我們要執行的具體方法。你可以在 "},{"type":"link","attrs":{"href":"https://dartpad.cn/143c33897a0eac7e2d627b01983b7307","title":""},"content":[{"type":"text","text":"這裏"}]},{"type":"text","text":" 運行這段代碼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"實現 Builder 模式 / 懶加載"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你有 Flutter 經驗,那麼你應該使用過 "},{"type":"codeinline","content":[{"type":"text","text":"ListView.builder"}]},{"type":"text","text":",它很好用對不對。我們只向 builder 屬性傳一個方法,"},{"type":"codeinline","content":[{"type":"text","text":"ListView"}]},{"type":"text","text":" 就可以根據這個 "},{"type":"codeinline","content":[{"type":"text","text":"builder"}]},{"type":"text","text":" 來構建它的每一個 item。實際上,這也是 closure 的一種體現。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"ListView.builder({\n//...\n @required IndexedWidgetBuilder itemBuilder,\n//...\n })\n \ntypedef IndexedWidgetBuilder = Widget Function(BuildContext context, int index);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 通過 typedef 定義了一種 Function,它接收 "},{"type":"codeinline","content":[{"type":"text","text":"BuildContext"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"int"}]},{"type":"text","text":" 作爲參數,然後會返回一個 Widget。對這樣的 Function 我們將它定義爲 "},{"type":"codeinline","content":[{"type":"text","text":"IndexedWidgetBuilder"}]},{"type":"text","text":" 然後將它內部的 Widget 返回出來。這樣外部的 scope 也能夠訪問 "},{"type":"codeinline","content":[{"type":"text","text":"IndexedWidgetBuilder"}]},{"type":"text","text":" 的 scope 內部定義的 Widget,從而實現了 builder 模式。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣,ListView 的懶加載(延遲執行)也是閉包很重要的一個特性哦~"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"牛刀小試"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在學習了 closure 以後,我們來道題檢驗一下你是否真正理解了吧~"}]},{"type":"codeblock","attrs":{"lang":"dart"},"content":[{"type":"text","text":"main(){\n var counter = Counter(0);\n fun1(){\n var innerCounter = counter;\n Counter incrementCounter(){\n print(innerCounter.value);\n innerCounter.increment();\n return innerCounter;\n }\n return incrementCounter;\n }\n\n var myFun = fun1();\n print(myFun() == counter);\n print(myFun() == counter);\n}\n\nclass Counter{\n int value;\n Counter(int value) \n : this.value = value;\n\n increment(){\n value++;\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面這段代碼會輸出什麼呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你已經想好了答案,就來"},{"type":"link","attrs":{"href":"https://dartpad.cn/75e338c727ae608cd31d389f7557a0f1","title":""},"content":[{"type":"text","text":"看看是否正確"}]},{"type":"text","text":"吧!也歡迎大家在底下評論區一起討論~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"寫在最後"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文非常感謝彥博哥 "},{"type":"link","attrs":{"href":"https://juejin.im/user/5b5ae02df265da0f9e58a9a7","title":""},"content":[{"type":"text","text":"@Realank Liu"}]},{"type":"text","text":" 的 Review 以及寶貴的建議~"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Closure 在實現 Flutter 的諸多功能上都發揮着重要的作用,可以說它已經深入你編程的日常,默默幫助我們更好地編寫 Dart 代碼,作爲一名不斷精進的 Dart 開發者,是時候用起來啦~之後的文章中,我會逐漸轉向 Dart,給大家帶來更深入的內容,敬請期待!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果您對本文還有任何疑問或者文章的建議,歡迎在下方評論區以及我的郵箱 [email protected] 與我聯繫,我會及時回覆!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"後續我的博文將首發 "},{"type":"link","attrs":{"href":"https://www.xinlei.dev","title":""},"content":[{"type":"text","text":"xinlei.dev"}]},{"type":"text","text":",歡迎關注!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章