前言
最近在看《你不知道的JavaScript》這本書上卷,看書的過程中,有一個很明顯的感覺就是自己懂得太少。JavaScript既是一門充滿吸引力,簡單易用的語言,又是一門具有較多複雜微妙技術的語言。本著知其然知其所以然的精神,我對讀完這本書做了一個簡單的總結,不過很多東西要反覆琢磨才能真正理解。
1. 作用域是什麼?
作用域是一套規則,用於確定在何處以及如何查詢變數(識別符號)。如果查詢的目的是對變數的賦值,那麼就會使用LHS查詢;如果目的是獲取變數的值,就會使用就會使用RHS查詢。賦值操作符會導致LHS查詢,=操作符或呼叫函式時傳入引數的操作都會導致關聯作用域的賦值操作。
像var = 2這樣的宣告會被分解兩個獨立的步驟
1.首先,var a在其作用域宣告新變數,這是在最開始的階段,也就是程式碼執行前進行
2.接著,a = 2會查詢(LHS查詢)變數a並對其賦值 LHS和RHS查詢都會在當前執行作用域開始,如果有需要,就會向上級作用域繼續查詢目標識別符號,這樣每次上升一級作用域,最後抵達全域性作用域,無論找到或沒找到都將停止
什麼是LHS和RHS?
LHS和RHS的含義是賦值操作的左側或右側,但並不意味就是"=賦值操作符的左側或右側" LHS可以理解為賦值操作的目標是誰。RHS可以理解為誰是賦值操作的源頭
2. 詞法作用域
詞法作用域是定義在詞法階段的作用域。換句話說,詞法作用域是由你在寫程式碼時將變數和塊作用域寫在哪裡決定的,因此詞法分析器處理程式碼時會保持作用域不變
function foo(a){
var b = a*2;
function bar(c){
console.log(a,b,c)
}
bar(b*3)
}
foo(2)//2,4,12
複製程式碼
在上面的例子中有三個逐級巢狀的作用域
- 包含整個的全域性作用域,其中有一個識別符號:foo
- 包含foo所建立的作用域,其中有三個識別符號:a,bar和b
- 包含bar所建立的作用域,其中只有一個識別符號:c
3. let
let關鍵字可以變數繫結到所在的任意作用域中(通常是{..}的內部,換句話說,let為其宣告的變數隱式地了所在的塊作用域 使用let進行的宣告不會在塊作用域中進行提升。宣告的程式碼被執行之前,宣告並不“存在” 例如
console.log(bar);//ReferenceError
let bar = 2 ;
複製程式碼
let的for迴圈
for迴圈頭部的let不僅將i繫結到for迴圈中,事實上它將其重新繫結到了迴圈的每一個迭代中,確保使用上一個迴圈迭代結束時的值重新進行賦值
let j;
for(j=0; j<10;j++){
let i = j;//每個迭代重新繫結
console.log(i)
}
複製程式碼
let宣告附屬於一個新的作用域而不是當前的函式作用域
4. const
const也可以建立塊作用域變數,但是其值是固定的(常量
var foo = true;
if (foo){
var a = 2;
const b = 3; //包含在if中的塊作用域常量
a = 3;//正常
b = 4;//錯誤
}
console.log(a);//3
console.log(b);//ReferenceError
複製程式碼
函式是javaScript中最常見的作用域單元。但函式不是唯一的作用域單元,塊作用域指的是變數和反函式不僅可以屬於所在的作用域,也可予以屬於某個程式碼塊(通常指{..}內部) 從ES6引入了let關鍵字(var關鍵字的表親),用來在任意程式碼塊中宣告變數,if(..){let a = 2}會宣告一個劫持了if的{..}塊的變數,並且將變數加到這個塊中
5. 作用域閉包
閉包是基於詞法作用域書寫程式碼時所產生的自然結果 下面一段程式碼,清晰的展示了閉包
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
}
var baz = foo();
baz();//2 這就是閉包的效果
複製程式碼
函式bar()的詞法作用域能訪問到foo()的內部作用域,然後我們把bar()函式本身當做一個值型別進行傳遞,在這個例子中bar所引用的函式物件本身當作返回值bar()顯然可以被正常執行。但它是在自己定義的詞法作用域以外的地執行 在foo()執行後,通常期待foo()的整個作用域後被銷燬。而閉包的"神奇"之處就是阻止這件事情的發生。事實上內部作用域依然存在,bar()本身在使用。
拜bar()所宣告的位置所賜,它擁有涵蓋foo()內部作用域的閉包,使得該作用域能夠一直存活,以供bar()在之後的任何時間進行引用 bar()依然持有對該作用域的引用,而這個引用就叫做閉包
function wait(message){
setTimeout(function timer(){
console.log(message);
},1000);
}
wait("hello,closure!");
複製程式碼
將一個內部函式(名為timer)傳遞給setTimeout(..).timer具有涵蓋wait(..)作用域的閉包,因此還保有對變數message的引用 wait(..)執行1000毫秒後,它的內部作用域並不會消失,timer函式依然保有wait(..)作用域的閉包,這就是閉包。
未完待續。。。。。。