The Little JavaScript Closures

scarlex發表於2015-12-04

寫在前面

本文嘗試模仿 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 函式是一個閉包嗎?

是的,因為它是一個內部函式,同時引用了自由變數

閉包有什麼特點?

  1. 閉包可以訪問外部變數

  2. 閉包可以在外部函式返回之後依然保留外部變數的引用

  3. 閉包會保留外部變數的引用,不是該變數的值

第一點在前面的例子中已經懂了。

很好

第二點還沒懂,可以舉個例子嗎?

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...

相關文章