摘要:空格一般沒意義會被忽略解析語法分析這個過程會將詞法單元轉(zhuǎn)換成抽象語法樹。小結本章節(jié)我們深入理解了的作用域,提升,閉包等概念,希望你能有所收獲,下一部分整理下解析對象原型等一些概念。
第一章: 作用域是什么 1、 編譯原理
JavaScript 被列為 ‘動態(tài)’ 或 ‘解釋執(zhí)行’ 語言,于其他傳統(tǒng)語言(如 java)不同的是,JavaScript是邊編譯邊執(zhí)行的。
一段源碼在執(zhí)行前會經(jīng)歷三個步驟: 分詞/詞法分析 -> 解析/語法分析 -> 代碼生成
分詞/詞法分析
這個過程將字符串分解成詞法單元,如 var a = 2; 會被分解成詞法單元 var、 a、 = 、2、;??崭褚话銢]意義會被忽略
解析/語法分析
這個過程會將詞法單元轉(zhuǎn)換成 抽象語法樹(Abstract Syntax Tree,AST)。
如 var a = 2; 對應的 抽象語法樹 如下, 可通過 在線可視化AST 網(wǎng)址在線分析
{ "type": "Program", "start": 0, "end": 10, "body": [ { "type": "VariableDeclaration", "start": 0, "end": 10, "declarations": [ { "type": "VariableDeclarator", "start": 4, "end": 9, "id": { "type": "Identifier", "start": 4, "end": 5, "name": "a" }, "init": { "type": "Literal", "start": 8, "end": 9, "value": 2, "raw": "2" } } ], "kind": "var" } ], "sourceType": "module" }
代碼生成
將 AST 轉(zhuǎn)換成可執(zhí)行的代碼,存放于內(nèi)存中,并分配內(nèi)存和轉(zhuǎn)化為一些機器指令
2、理解作用域其實結合上面提到的編譯原理,作用域就好理解了。作用域就是當前執(zhí)行代碼對這些標識符的訪問權限。
編譯器會在當前作用域中聲明一些變量,運行時引擎會去作用域中查找這些變量(其實就是一個尋址的過程),如果找到這些變量就可以操作變量,找不到就往上一層作用域找(作用域鏈的概念),或者返回 null
每聲明一個函數(shù)都會形成一個作用域,那作用域有什么用呢,它能讓該作用域內(nèi)的變量和函數(shù)不被外界訪問到,也可以反過來說是不讓該作用域內(nèi)的變量或函數(shù)污染全局。
對比:
var a = 123 function bar() { //... }
和
function foo() { var a = 123 function bar() { //... } }
變量 a 和函數(shù) bar 用一個函數(shù) foo 包裹起來,函數(shù) foo 會形成一個作用域,變量 a 和函數(shù) bar 外界將無法訪問,同時變量或函數(shù)也不會污染全局。
2、函數(shù)作用域進一步思考,上面例子的變量 a 和函數(shù) bar 有了作用域,但函數(shù) foo 不也是暴露在全局,也對全局造成污染了啊。是的,JavaScript對這種情況提出了解決方案: 立即執(zhí)行函數(shù) (IIFE)
(function foo() { var a = 123 function bar() { //... } })()
第一個()將函數(shù)變成表達式,第二個()執(zhí)行了這個函數(shù),最終函數(shù) foo 也形成了自己的作用域,不會污染到全局,同時也不被全局訪問的到。
3、塊作用域es6之前JavaScript是沒有塊作用域這個概念的,這與一般的語言(如Java ,C)很大不同,看下面這個例子:
for (var i = 0; i < 10; i++) { console.log("i=", i); } console.log("輸出", i); // 輸出 10
for 循環(huán)定義了變量 i,通常我們只想這個變量 i 在循環(huán)內(nèi)使用,但忽略了 i 其實是作用在外部作用域(函數(shù)或全局)的。所以循環(huán)過后也能正常打印出 i ,因為沒有塊的概念。
甚至連 try/catch 也沒形成塊作用域:
try { for (var i = 0; i < 10; i++) { console.log("i=", i); } } catch (error) {} console.log("輸出", i); // 輸出 10
解決方法1
形成塊作用域的方法當然是使用 es6 的 let 和 const 了, let 為其聲明的變量隱式的劫持了所在的塊作用域。
for (let i = 0; i < 10; i++) { console.log("i=", i); } console.log("輸出", i); // ReferenceError: i is not defined
將上面例子的 var 換成 let 最后輸出就報錯了 ReferenceError: i is not defined ,說明被 let 聲明的 i 只作用在了 for 這個塊中。
除了 let 會讓 for、if、try/catch 等形成塊,JavaScript 的 {} 也能形成塊
{ let name = "曾田生" } console.log(name); //ReferenceError: name is not defined
解決方法2
早在沒 es6 的 let 聲明之前,常用的做法是利用 函數(shù)也能形成作用域 這么個概念來解決一些問題的。
看個例子
function foo() { var result = [] for (var i = 0; i < 10; i++) { result[i] = function () { return i } } console.log(i)// i 作用在整個函數(shù),for 執(zhí)行完此時 i 已經(jīng)等于 10 了 return result } var result = foo() console.log(result[0]()); // 輸出 10 期望 0 console.log(result[1]()); // 輸出 10 期望 1 console.log(result[2]()); // 輸出 10 期望 2
這個例子出現(xiàn)的問題是執(zhí)行數(shù)組函數(shù)最終都輸出了 10, 因為 i 作用在整個函數(shù),for 執(zhí)行完此時 i 已經(jīng)等于 10 了, 所以當后續(xù)執(zhí)行函數(shù) result[x]() 內(nèi)部返回的 i 已經(jīng)是 10 了。
利用函數(shù)的作用域來解決
function foo() { var result = [] for (var i = 0; i < 10; i++) { result[i] = function (num) { return function () { // 函數(shù)形成一個作用域,內(nèi)部變量被私有化了 return num } }(i) } return result } var result = foo() console.log(result[0]()); // 0 console.log(result[1]()); // 1 console.log(result[2]()); // 2
上面的例子也是挺典型的,一般面試題比較考基礎的話就會被問道,上面例子不僅考察到了塊作用域的概念,函數(shù)作用域的概念,還考察到了閉包的概念(閉包后續(xù)講但不影響這個例子的理解),多琢磨一下就理解了。
第四章: 提升提升指的是變量提升和函數(shù)提升,為什么JavaScript會有提升這個概念呢,其實也很好理解,因為JavaScript代碼是先 編譯 后 執(zhí)行 的,所以在編譯階段就會先對變量和函數(shù)做聲明,在執(zhí)行階段就出現(xiàn)了所謂的變量提升和函數(shù)提升了。
1、變量提升console.log(a); // undefined var a = 1;
上面代碼 console.log(a); // undefined 就是因為編譯階段先對變量做了聲明,先聲明了個變量 a, 并默認賦值 undefined
var a; console.log(a); // undefined a = 1;2、函數(shù)提升
函數(shù)同樣也存在提升,這就是為什么函數(shù)能先調(diào)用后聲明了
foo(); function foo() { console.log("---foo----"); }
注意:函數(shù)表達式不會被提升
foo(); var foo = function() { console.log("---foo----"); } // TypeError: foo is not a function
注意:函數(shù)會首先被提升,然后才是變量
var foo = 1; foo(); function foo() { console.log("---foo----"); } // TypeError: foo is not a function
分析一下,因為上面例子編譯后是這樣的
var foo = undefined; // 變量名賦值 undefined function foo() { // 函數(shù)先提升 console.log("---foo----"); } foo = 1; // 但接下去是變量被重新賦值了 1,是個Number類型 foo(); // Number類型當然不能用函數(shù)方式調(diào)用,就報錯了 // TypeError: foo is not a function第五章: 作用域閉包
閉包問題一直會在JavaScript被提起,是JavaScript一個比較奇葩的概念
1、閉包的產(chǎn)生閉包的概念: 當函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包
概念貌似挺簡單的,簡單分析下,首先閉包是 產(chǎn)生的,是在代碼執(zhí)行中產(chǎn)生的,有的一些網(wǎng)絡博文直接將閉包定義為 某一個特殊函數(shù) 是錯的。
閉包是怎么產(chǎn)生的呢,一個函數(shù)能訪問到所在函數(shù)作用域就產(chǎn)生了閉包,注意到作用域的概念,咱們最上面的章節(jié)有提到,看下面例子:
function foo() { var a = 0; function bar() { a++; console.log(a); } return bar; } var bat = foo() bat() // 1 bat() // 2 bat() // 3
結合例子分析一下: 函數(shù) foo 內(nèi)部返回了函數(shù) bar ,外部聲明個變量 bat 拿到 foo 返回的函數(shù) bar ,執(zhí)行 bat() 發(fā)現(xiàn)能正常輸出 1 ,注意前面章節(jié)提到的作用域,變量 a 是在函數(shù) foo 內(nèi)部的一個私有變量,不能被外界訪問的,但外部函數(shù) bat 卻能訪問的到私有變量 a,這說明了 外部函數(shù) bat 持有函數(shù) foo 的作用域 ,也就產(chǎn)生了閉包。
閉包的形成有什么用呢,JavaScript 讓閉包的存在明顯有它的作用,其中一個作用是為了模塊化,當然你也可以利用外部函數(shù)持有另一個函數(shù)作用域的閉包特性去做更多的事情,但這邊就暫且討論模塊化這個作用。
函數(shù)有什么作用呢,私有化變量或方法呀,那函數(shù)內(nèi)的變量和方法被私有化了函數(shù)怎么和外部做 交流 呢, 暴露出一些變量或方法呀
function foo() { var _a = 0; var b = 0; function _add() { b = _a + 10 } function bar() { _add() } function getB() { return b } return { bar: bar, getB: getB } } var bat = foo() bat.bar() bat.getB() // 10
上面例子函數(shù) foo 可以理解為一個模塊,內(nèi)部聲明了一些私有變量和方法,也對外界暴露了一些方法,只是在執(zhí)行的過程中順帶產(chǎn)生了一個閉包
2、模塊機制上面提到了閉包的產(chǎn)生和作用,貌似在使用 es6語法 開發(fā)的過程中很少用到了閉包,但實際上我們一直在用閉包的概念的。
foo.js
var _a = 0; var b = 0; function _add() { b = _a + 10 } function bar() { _add() } function getB() { return b } export default { bar: bar, getB: getB }
bat.js
import bat from "foo" bat.bar() bat.getB() // 10
上面例子是 es6 模塊的寫法,是不是驚奇的發(fā)現(xiàn)變量 bat 可以記住并訪問模塊 foo 的作用域,這符合了閉包的概念。
小結:本章節(jié)我們深入理解了JavaScript的 作用域,提升,閉包等概念,希望你能有所收獲,下一部分整理下 this解析、對象、原型 等一些概念。
如果有興趣也可以去我的 github-blog 提 issues ,github也整理了幾篇文章會定期更新,歡迎 star
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/95714.html
摘要:最近剛剛看完了你不知道的上卷,對有了更進一步的了解。你不知道的上卷由兩部分組成,第一部分是作用域和閉包,第二部分是和對象原型。附錄詞法這一章并沒有說明機制,只是介紹了中的箭頭函數(shù)引入的行為詞法。第章混合對象類類理論類的機制類的繼承混入。 最近剛剛看完了《你不知道的 JavaScript》上卷,對 JavaScript 有了更進一步的了解。 《你不知道的 JavaScript》上卷由兩部...
摘要:但是如果非全局的變量如果被遮蔽了,無論如何都無法被訪問到。但是如果引擎在代碼中找到,就會完全不做任何優(yōu)化。結構的分句中具有塊級作用域。第四章提升編譯器函數(shù)聲明會被提升,而函數(shù)表達式不會被提升。 本書屬于基礎類書籍,會有比較多的基礎知識,所以這里僅記錄平常不怎么容易注意到的知識點,不會全記,供大家和自己翻閱; 上中下三本的讀書筆記: 《你不知道的JavaScript》 (上) 讀書筆記...
摘要:這時候控制臺看到的是對象的快照,然而點開看詳情的話是這段代碼在運行的時候,瀏覽器可能會認為需要把控制臺延遲到后臺,這種情況下,等到瀏覽器控制臺輸出對象內(nèi)容時,可能已經(jīng)運行,因此會在點開的時候顯示,這是的異步化造成的。 本書屬于基礎類書籍,會有比較多的基礎知識,所以這里僅記錄平常不怎么容易注意到的知識點,不會全記,供大家和自己翻閱; 上中下三本的讀書筆記: 《你不知道的JavaScri...
摘要:強制類型轉(zhuǎn)換本章介紹了的數(shù)據(jù)類型之間的轉(zhuǎn)換即強制類型轉(zhuǎn)換包括顯式和隱式。強制類型轉(zhuǎn)換常常為人詬病但實際上很多時候它們是非常有用的。隱式強制類型轉(zhuǎn)換則沒有那么明顯是其他操作的副作用。在處理強制類型轉(zhuǎn)換的時候要十分小心尤其是隱式強制類型轉(zhuǎn)換。 前言 《你不知道的 javascript》是一個前端學習必讀的系列,讓不求甚解的JavaScript開發(fā)者迎難而上,深入語言內(nèi)部,弄清楚JavaSc...
摘要:一先有雞還有先有蛋直覺上會認為代碼在執(zhí)行時是由上到下一行一行執(zhí)行的。不幸的是兩種猜測都是不對的。換句話說,我們的問題先有雞還是先有蛋的結論是先有蛋聲明后有雞賦值。 一、先有雞還有先有蛋? 直覺上會認為javascript代碼在執(zhí)行時是由上到下一行一行執(zhí)行的。但實際上這并不完全正確,有一種特殊情況會導致這個假設是錯誤的。 a = 2; var a; console.log(a); 大家...
閱讀 1371·2021-09-10 10:51
閱讀 2835·2019-08-30 15:54
閱讀 3377·2019-08-29 17:11
閱讀 936·2019-08-29 16:44
閱讀 1399·2019-08-29 13:47
閱讀 1095·2019-08-29 13:47
閱讀 1495·2019-08-29 12:23
閱讀 1052·2019-08-28 18:18