寫在前面
本文嘗試模仿 The Little Schema 的風格,介紹 JavaScript 的閉包。本文同時也是我學習 JavaScript 閉包的一次總結。歡迎一起討論。
簡介
什麼是閉包?
閉包是一個函式
閉包都是函式嗎?
是
函式都是閉包嗎?
不
我怎麼判斷一個函式是不是閉包?
你現在還不能回答,因為你還不知道以下概念:
全域性變數(Global Variable)
區域性變數(Local Variable)
自由變數(Free Variable)
詞法作用域(Lexical Scope)
變數與作用域
var a = 1;
a 是什麼變數?
全域性變數
a = 1;
a 是什麼變數?
全域性變數
function foo() {
a = 1;
var b = 1;
}
這裡的 a,b 分別是什麼變數?
a 是全域性變數,b 是區域性變數
為什麼 a 在函式中定義還是全域性變數?
因為 a 不是用 var 宣告的
不用 var
宣告的變數都是全域性變數?
是的
用 var
宣告的變數都是區域性變數?
不是
為什麼?
在全域性作用域中宣告的變數都是全域性變數,即使這個變數是用 var 宣告的
全域性作用域是什麼?
函式作用域以外的地方都是就是全域性作用域
函式作用域又是什麼?
函式內部
可以舉個例子嗎?
var foo = 1; function bar() { var baz = 2; }
foo 變數和 bar 函式都處於全域性作用域中,baz 變數處於函式作用域中
function foo() {
var bar = 1;
}
這段程式碼中有多少個作用域?
2 個,foo 函式所處的全域性作用域和 bar 變數所處的函式作用域
function foo() {
var bar = 1;
function baz() {
var test = 1;
}
}
這段程式碼中有多少個作用域?
3 個,foo 函式所處的全域性作用域,bar 所處的函式作用域,和 test 所處的函式作用域
上面的 bar 變數和 baz 函式處於同一個作用域嗎?
是的,因為它們都在 foo 函式中
上面 test 變數和 bar,baz處於同一個作用域中嗎?
不是,因為 test 變數在 baz 函式中
JavaScript 用函式來劃分作用域嗎?
是的
function foo() {
var bar = 1;
}
console.log(bar);
會輸出什麼?
Uncaught ReferenceError: bar is not defined
為什麼會報錯呢?
因為外部作用域不能訪問內部作用域
var foo = 1;
function bar() {
console.log(foo);
}
bar();
會輸出什麼?
1
為什麼不會報錯?
因為內部作用域可以訪問外部作用域
var x = 1;
function foo() {
var x = 2;
console.log(x);
}
foo();
會輸出什麼?
2
為什麼不是輸出 1 ?
因為區域性變數的優先順序比外部變數高
var x = 1;
function foo() {
console.log(x);
var x = 2;
console.log(x);
}
foo();
會輸出什麼?
undefined
2
為什麼會這麼奇怪?
因為變數宣告有變數提升(Variable Hoisting)的過程
變數提升是什麼?
宣告語句會在執行前被處理,在任何地方宣告一個變數,相當於在頂部位置宣告
可以舉個例子嗎?
bla = 0; var bla; // 相當於 var bla; bla = 0;
這和之前的例子有什麼關係?
函式內部宣告的變數,都會先在函式的頂部宣告。所以之前的例子就相當於
function foo() { var x; console.log(x); x = 1; console.log(x) }
什麼是詞法作用域?
變數的作用域是由它在原始碼中所處位置決定的(詞法),並且巢狀的函式可以訪問到其外層作用域中宣告的變數。
這和上面說到的內部作用域可以訪問外部作用域有什麼區別嗎?
沒有
什麼是自由變數?
在函式內部使用到,但既不是該函式的引數,也不是該函式的區域性變數的變數。
可以舉個例子嗎?
var foo = 1; function bar() { var baz = 2; console.log(foo + baz); }
這裡 bar 函式有三個變數:baz, console, foo
其中 baz 是區域性變數, console 和 foo 都屬於自由變數
為什麼 console 和 foo 都是自由變數?
因為 console 和 foo 都在全域性作用域中,在 bar 函式中是通過引用的方式來使用 console 和 foo 的
還需要了解其他概念嗎?
不需要,現在已經可以深入瞭解閉包了
閉包
什麼是閉包?
閉包是一個內部函式 [注1]
內部函式都是閉包嗎?
不是,引用了自由變數的內部函式才是閉包
var x = 1;
function foo() {
console.log(x + 1);
}
foo 函式是一個閉包嗎?
不是,因為 foo 函式不是一個內部函式
function foo() {
function bar() {
var x = 1;
return x + 1;
}
}
bar 函式是一個閉包嗎?
不是,因為它只是一個內部函式,並沒有引用自由變數
function foo() {
var x = 1;
function bar() {
return x + 1;
}
}
bar 函式是一個閉包嗎?
是的,因為它是一個內部函式,同時引用了自由變數
閉包有什麼特點?
閉包可以訪問外部變數
閉包可以在外部函式返回之後依然保留外部變數的引用
閉包會保留外部變數的引用,不是該變數的值
第一點在前面的例子中已經懂了。
很好
第二點還沒懂,可以舉個例子嗎?
function add(x) { return function(y) { return x + y; } } var add5 = add(5); console.log(add5(10)) // 15
即便 add 函式已經返回,add5 中依然可以訪問 x
第三點還沒懂,可以舉個例子嗎?
function user() { var id = 1; return { getId: function() { return id; }, setId: function(newId) { id = newId } } } var foo = user(); foo.getId(); // 1 foo.setId(2); foo.getId(); // 2
這裡閉包中的 id 是一個引用,不是實際值
有點像私有方法?
是的,我們可以用閉包來實現私有方法
閉包還可以用來做什麼?
閉包是函數語言程式設計的骨架,掌握閉包之後你可以寫出函式式 JavaScript 程式碼。
函數語言程式設計是什麼?
這不是本文的討論範圍,自己去學習吧。
One More Thing
注1] 根據 [Understanding JavaScript Closures 這篇文章,事實上所有函式在建立的時候都會形成閉包。但這種閉包並沒什麼趣味,也沒什麼特別的用途,所以我們更關注的是由內部函式形成的閉包。
出處
https://scarletsky.github.io/2015/12/02/...
參考資料
http://uternet.github.io/TLS/
http://www.ruanyifeng.com/blog/2009/08/l...
https://developer.mozilla.org/zh-CN/docs...
https://developer.mozilla.org/en-US/docs...
http://javascriptissexy.com/understand-j...
http://javascriptissexy.com/javascript-v...
http://stackoverflow.com/questions/12930...
https://javascriptweblog.wordpress.com/2...
http://www.moye.me/2014/12/29/closure_hi...