徹底理解JavaScript執行上下文

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"前面我們已經學習了JavaScript的"},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/8cbe43f79e69b4b645a09e3db","title":""},"content":[{"type":"text","text":"事件循環機制"}]},{"type":"text","text":",瞭解了一段代碼是如何被JavaScript引擎執行的,這是粒度最粗的執行單位。接下來,我們開始學習粒度較小的單位:函數的執行機制,以及和函數執行過程相關的所有問題。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"閉包closure"}]},{"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":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"在編譯原理中,它是處理語法產生式的一個步驟;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"在計算幾何中,它表示包裹平面點集的凸多邊形;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"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":"上個世界60年代,主流編程語言是基於lambda演算的函數式編程語言,所以最初的閉包定義,使用了大量的函數式術語。一個比較模糊的定義是“帶有一系列信息的λ表達式”。其實,λ表達式就是函數。"}]},{"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":"普通的函數就是一系列表達式的集合,只要給定參數,就會得到確定的結果。而閉包還可以攜帶大量上下文信息,這函數的執行有時候很難理解,但這也是閉包之所以強大的地方。"}]},{"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":"bulletedlist","content":[{"type":"listitem","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},"content":[{"type":"text","text":" - 環境"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 標識符列表"}]},{"type":"bulletedlist","content":[{"type":"listitem","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":"這個定義對應到JavaScript標準中,則是:"}]},{"type":"bulletedlist","content":[{"type":"listitem","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},"content":[{"type":"text","text":" - 環境:函數的詞法環境(執行上下文的一部分)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" - 標識符列表:函數中用到的未聲明變量"}]},{"type":"bulletedlist","content":[{"type":"listitem","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":"我們可以認爲,JavaScript函數完全符合閉包的定義。它的環境部分是由函數詞法環境部分組成,標識符列表是函數中用到的未聲明變量,表達式部分就是函數體。"}]},{"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"每個編程語言中都有作用域這個概念,JavaScript也不例外。"}]},{"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":"JavaScript中的作用域無非兩種:全局變量和局部變量,函數內部可以直接讀取全局變量,而函數外部無法獲取內部的局部變量。這裏有一個地方需要注意,函數內部聲明變量的時候,一定要使用"},{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"命令。如果不用的話,你實際上聲明瞭一個全局變量!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function f1(){\n    n = 999;\n }\n\n f1();\n\n alert(n); // 999"}]},{"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":"javascript"},"content":[{"type":"text","text":"  function f1(){\n\n    var n=999;\n\n    function f2(){\n      alert(n);\n    }\n\n    return f2;\n\n  }\n\n  var result=f1();\n\n  result(); // 999"}]},{"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}},{"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":"javascript"},"content":[{"type":"text","text":"  function f1(){\n\n    var n=999;\n\n    nAdd = function(){n+=1} //沒有使用var關鍵字,所以定義了一個全局變量\n\n    function f2(){\n      alert(n);\n    }\n\n    return f2;\n\n  }\n\n  var result=f1();\n\n  result(); // 999\n\n  nAdd();\n\n  result(); // 1000"}]},{"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":"f1"}]},{"type":"text","text":"運行之後,就會將局部變量"},{"type":"codeinline","content":[{"type":"text","text":"n"}]},{"type":"text","text":"保存在內存中,同時在全局作用域中加入"},{"type":"codeinline","content":[{"type":"text","text":"nAdd"}]},{"type":"text","text":",運行"},{"type":"codeinline","content":[{"type":"text","text":"nAdd"}]},{"type":"text","text":"保存在內存中的局部變量"},{"type":"codeinline","content":[{"type":"text","text":"n"}]},{"type":"text","text":"就會被加1,從而得到1000。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"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":"執行一段JavaScript代碼,不光需要全局變量和局部變量,還需要處理"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"with"}]},{"type":"text","text":"等特殊語法,這些信息讓JavaScript代碼的執行變得更加複雜。"}]},{"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","marks":[{"type":"strong"}],"text":"JavaScript標準把一段代碼,執行所需要的一切信息定義爲“執行上下文”。"}]},{"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":"ES3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行上下文在ES3中,包含三個部分。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"scope:作用域,也常常被叫做作用域鏈。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"variable object:變量對象,用於存儲變量的對象。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"this value:this值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES5"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ES5中,改進了命名方式,把執行上下文最初的三個部分改爲下面這個樣子。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lexical environment:詞法環境,當獲取變量時使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"variable environment:變量環境,當聲明變量時使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"this value:this值。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ES2018"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ES2018中,執行上下文又變成了這個樣子,this值被歸入lexical environment,但是增加了不少內容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"lexical environment:詞法環境,當獲取變量或者this值時使用。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"variable environment:變量環境,當聲明變量時使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code evaluation state:用於恢復代碼執行位置。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Function:執行的任務是函數時使用,表示正在被執行的函數。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ScriptOrModule:執行的任務是腳本或者模塊時使用,表示正在被執行的代碼。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Realm:使用的基礎庫和內置對象實例。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Generator:僅生成器上下文有這個屬性,表示當前生成器。"}]}]}]},{"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":"javascript"},"content":[{"type":"text","text":" var b = {}\n let c = 1\n this.a = 2;"}]},{"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":"numberedlist","attrs":{"start":"1","normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":" 把 "},{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" 聲明到哪裏;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" 表示哪個變量;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"b"}]},{"type":"text","text":" 的原型是哪個對象;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":" 把 "},{"type":"codeinline","content":[{"type":"text","text":"c"}]},{"type":"text","text":" 聲明到哪裏;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"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":"var"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"是用於聲明變量的關鍵字,它最大的缺陷就是會穿透當前作用域,讓變量跑到到上層作用域中。例如下面的代碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" if (true) {\n var test = true;\n }\n\n alert(test); // true\n\n for (var i = 0; i < 10; i++) {\n console.log(i)\n }\n\n alert(i); // 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":"var"}]},{"type":"text","text":"會穿透"},{"type":"codeinline","content":[{"type":"text","text":"if"}]},{"type":"text","text":"、"},{"type":"codeinline","content":[{"type":"text","text":"for"}]},{"type":"text","text":"等代碼塊,進入更上層的作用域。但是,如果在function內定義的變量,則不會影響函數外部。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function sayHi() {\n if (true) {\n var phrase = \"Hello\";\n }\n\n alert(phrase); // works\n }\n\n sayHi();\n alert(phrase); // Error: phrase is not defined"}]},{"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":"var"}]},{"type":"text","text":"的這種問題,在沒有"},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":"的時代,用"},{"type":"text","marks":[{"type":"strong"}],"text":"立即執行的函數表達式(IIFE)"},{"type":"text","text":",通過創建一個函數,並立即執行,就像上面的例子一樣,可以完美解決變量提升的缺陷。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" (function() {\n var message = \"Hello\";\n alert(message); // Hello\n })();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"let"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":"是從ES6開始引入的新的變量聲明方式,比起"},{"type":"codeinline","content":[{"type":"text","text":"var"}]},{"type":"text","text":"的諸多弊病,"},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"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":"let"}]},{"type":"text","text":",JavaScript在運行時引入了塊級作用域。以下語句中,都會產生let使用的作用域:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"for"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"if"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"switch"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"try/catch/finally"}]}]}]}]},{"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":"let"}]},{"type":"text","text":"重新變量,會報錯。如下所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"let user;\nlet user; // Uncaught SyntaxError: Identifier 'user' has already been declared"}]},{"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":"let"}]},{"type":"text","text":",甚至全用 "},{"type":"codeinline","content":[{"type":"text","text":"let"}]},{"type":"text","text":" 聲明變量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"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","marks":[{"type":"strong"}],"text":"執行上下文"},{"type":"text","text":"是JavaScript代碼執行所需要的一切信息。也就是說,一段代碼的執行結果依賴於執行上下文的內容,如果執行上下文不一樣了,相同的代碼很可能產生不同的結果。在JavaScript中,切換執行上下文最重要的場景就在函數調用。下面,我們先來認識一下,JavaScript中一共有多少種函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"普通函數是用"},{"type":"codeinline","content":[{"type":"text","text":"function"}]},{"type":"text","text":"關鍵字定義的函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"js"},"content":[{"type":"text","text":" function foo() {\n // code\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"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":"=>"}]},{"type":"text","text":" 運算符定義的函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const foo = () => {\n // code\n}"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"class中定義的函數"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":"中定義的函數,也就是類的訪問器屬性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" class C {\n foo(){\n // code\n }\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"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":"function *"}]},{"type":"text","text":" 定義的函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function foo*(){\n // code\n }"}]},{"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":"codeinline","content":[{"type":"text","text":"class"}]},{"type":"text","text":"定義的類,實際上也是函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" class Foo {\n constructor(){\n // code\n }\n }"}]},{"type":"heading","attrs":{"align":null,"level":3},"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":"async"}]},{"type":"text","text":"關鍵字。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" async function foo(){\n // code\n }\n const foo = async () => {\n // code\n }\n async function foo*(){\n // code\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":"總共8種函數類型,它們的執行上下文,對於普通變量沒有什麼特殊之處,都是遵循了“繼承定義時環境”的規則,主要差異來自 "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 關鍵字。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"this"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"普通函數的this"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":" 關鍵字是JavaScript執行上下文中非常重要的一個組成部分,同一個函數調用方式不同,得到的this值也不同。例如下面這段代碼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function showThis(){\n console.log(this);\n }\n\n var o = {\n showThis: showThis\n }\n\n showThis(); // global\n\n o.showThis(); // o"}]},{"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":"this"}]},{"type":"text","text":" 指向函數運行所在的環境。例如,在上面的例子中,"},{"type":"codeinline","content":[{"type":"text","text":"showThis()"}]},{"type":"text","text":"運行在全局環境中,而 "},{"type":"codeinline","content":[{"type":"text","text":"o.showThis()"}]},{"type":"text","text":" 運行在 "},{"type":"codeinline","content":[{"type":"text","text":"o"}]},{"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","marks":[{"type":"strong"}],"text":"this"}]},{"type":"text","marks":[{"type":"strong"}],"text":" 是由調用它所使用的引用決定的"},{"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},"content":[{"type":"text","text":" 我們獲取函數的表達式,實際上返回的並非函數本身,而是一個Reference類型(JavaScript七種標準類型之一)。"}]},{"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":" Reference類型由兩部分組成:一個對象和一個屬性值。在上面的例子中,"},{"type":"codeinline","content":[{"type":"text","text":"showThis()"}]},{"type":"text","text":" 產生的Reference類型便是全局對象"},{"type":"codeinline","content":[{"type":"text","text":"global"}]},{"type":"text","text":" 或者 "},{"type":"codeinline","content":[{"type":"text","text":"window"}]},{"type":"text","text":",和屬性showThis構成;"},{"type":"codeinline","content":[{"type":"text","text":"o.showThis()"}]},{"type":"text","text":" 產生的Reference類型又是對象o和屬性 "},{"type":"codeinline","content":[{"type":"text","text":"showThis"}]},{"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":"this"}]},{"type":"text","text":" 的真正含義:"},{"type":"text","marks":[{"type":"strong"}],"text":"調用函數時使用的引用Reference,決定了函數執行時刻的 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":" 值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"箭頭函數的this"}]},{"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":"javascript"},"content":[{"type":"text","text":"\t\tvar showThis = () => {\n console.log(this);\n }\n\n var o = {\n showThis: showThis\n }\n\n showThis(); // global\n\n o.showThis(); // global\n\n var o = {}\n\n o.foo = function foo(){\n console.log(this);\n return () => {\n console.log(this);\n return () => console.log(this);\n }\n }\n\n o.foo()()(); // o, o, o"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"訪問器屬性的this"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" class C {\n showThis() {\n console.log(this);\n }\n }\n var o = new C();\n var showThis = o.showThis;\n\n showThis(); // undefined\n o.showThis(); // o"}]},{"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":"在類中的“方法”,結果又不太一樣,使用showThis這個引用去調用方法時,得到了undefined,在對象上調用得到對象本身。"}]},{"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":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"this的機制"}]},{"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":"this"}]},{"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":"爲什麼能記住這些值?實際上,JavaScript標準中,爲函數規定了用於保存定義時上下文信息的私有屬性[["},{"type":"codeinline","content":[{"type":"text","text":"Environment"}]},{"type":"text","text":"]]。當一個函數執行時,會創建一條執行環境記錄,記錄的外層詞法環境會被設置爲函數的[["},{"type":"codeinline","content":[{"type":"text","text":"Environment"}]},{"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":"JavaScript用一個棧來管理執行上下文,這個棧中的每一項又包含一個鏈表。如下圖所示:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/87/870a597f321793e79bcef82c5cbcc3c1.jpeg","alt":null,"title":"執行上下文棧結構","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"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":"codeinline","content":[{"type":"text","text":" this "}]},{"type":"text","text":"是一個更爲複雜的機制,JavaScript標準定義了 [["},{"type":"codeinline","content":[{"type":"text","text":"thisMode"}]},{"type":"text","text":"]] 私有屬性。它有三個取值:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"lexical"},{"type":"text","text":":表示從上下文中找"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":",這對應了箭頭函數。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"global"},{"type":"text","text":":表示當"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"爲"},{"type":"codeinline","content":[{"type":"text","text":"undefined"}]},{"type":"text","text":"時,取全局對象,對應了普通函數。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"strict"},{"type":"text","text":":當嚴格模式時使用,"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"嚴格按照調用時傳入的值,可能爲"},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":"或者"},{"type":"codeinline","content":[{"type":"text","text":"undefined"}]},{"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":" this "}]},{"type":"text","text":"有差異,就是因爲class被設計爲默認按照strict模式執行。"}]},{"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":"Environment"}]},{"type":"text","text":"]]。除此之外,還會根據"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"關鍵字的[["},{"type":"codeinline","content":[{"type":"text","text":"thisMode"}]},{"type":"text","text":"]]來標記此記錄的[["},{"type":"codeinline","content":[{"type":"text","text":"ThisBindingStatus"}]},{"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":"this"}]},{"type":"text","text":"關鍵字時,會逐層檢查當前環境中的[["},{"type":"codeinline","content":[{"type":"text","text":"ThisBindingStatus"}]},{"type":"text","text":"]],當找到有"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"的環境記錄時,便可獲取當前的"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"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":"this"}]},{"type":"text","text":"都指向外層"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" var o = {}\n o.foo = function foo(){\n console.log(this);\n return () => {\n console.log(this);\n return () => console.log(this);\n }\n }\n\n o.foo()()(); // o, o, o"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"操作this的內置函數"}]},{"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":"JavaScript提供了兩個函數:"},{"type":"codeinline","content":[{"type":"text","text":"Function.prototype.call"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"Function.prototype.apply"}]},{"type":"text","text":" 可以指定函數調用時傳入的"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":" function foo(a, b, c){\n console.log(this);\n console.log(a, b, c);\n }\n foo.call({}, 1, 2, 3);\n foo.apply({}, [1, 2, 3]);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"call "}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":" apply "}]},{"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":"Function.prototype.bind"}]},{"type":"text","text":",它可以生成一個綁定過的函數。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"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":"要想徹底理解JavaScript的代碼執行機制,就必須理解執行上下文、"},{"type":"codeinline","content":[{"type":"text","text":"this"}]},{"type":"text","text":"、閉包和函數,它們共同構成JavaScript最常用的代碼執行單元。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"執行上下文:一段代碼執行所需要的所有信息,包括變量、"},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"等,經歷了多個版本的更迭,內容越來越豐富;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"函數:一段邏輯上相關的代碼組織形式,是最基本的代碼執行單元,JavaScript中共有8種函數;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"this"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":":指向函數運行所在的環境,由調用它所使用的的引用Reference決定,裏面有更加複雜的內在機制;"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"閉包:其實就是綁定了執行環境信息的函數,這讓函數的執行變得複雜,同時也是強大的原因。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章