首頁 >web前端 >js教程 >js中的閉包是如何使用的?

js中的閉包是如何使用的?

零下一度
零下一度原創
2017-06-28 13:38:101596瀏覽

這篇文章主要介紹了JavaScript閉包,閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現

函數作為返回值

高階函數除了可以接受函數當參數外,還可以把函數當作結果值回傳。

我們來實作一個對Array的求和。通常情況下,求和的函數是這樣定義的:


function sum(arr) {
  return arr.reduce(function (x, y) {
    return x + y;
  });
}

sum([1, 2, 3, 4, 5]); // 15

但是,如果不需要立刻求和,而是在後面的程式碼中,根據需要再計算怎麼辦?可以不傳回求和的結果,而是傳回求和的函數!


function lazy_sum(arr) {
  var sum = function () {
    return arr.reduce(function (x, y) {
      return x + y;
    });
  }
  return sum;
}

當我們呼叫lazy_sum()時,傳回的不是求和結果,而是求和函數:


var f = lazy_sum([1, 2, 3, 4, 5]); // function sum()

呼叫函數f時,才真正計算求和的結果:


f(); // 15

在這個範例中,我們在函數lazy_sum中又定義了函數sum,並且,內部函數sum可以引用外在函數 lazy_sum的參數和局部變量,當lazy_sum返回函數sum時,相關參數和變數都會保存在傳回的函數中,這種稱為「閉包(Closure )」的程序結構擁有極大的威力。

請再注意一點,當我們呼叫lazy_sum()時,每次呼叫都會回傳一個新的函數,即使傳入相同的參數:


var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

f1()f2()的呼叫結果互不影響。

閉包

注意到傳回的函數在其定義內部引用了局部變數arr,所以,當一個函數回傳了一個函數後,其內部的局部變數還被新函數引用,所以,閉包用起來簡單,實作起來可不容易。

另一個需要注意的問題是,傳回的函數並不是立刻執行,而是直到呼叫了f()才執行。我們來看一個例子:


function count() {
  var arr = [];
  for (var i=1; i<=3; i++) {
    arr.push(function () {
      return i * i;
    });
  }
  return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

在上面的例子中,每次循環,都創建了一個新的函數,然後,把創建的3個函數都加入到一個Array中返回了。

你可能認為呼叫f1() f2()f3()結果應該是149,但實際結果是:


f1(); // 16
f2(); // 16
f3(); // 16

全部都是16 !原因就在於傳回的函數引用了變數i,但它並非立刻執行。等到3個函數都回傳時,它們所引用的變數i已經變成了4,因此最終結果為16

返回閉包時牢記的一點是:返回函數不要引用任何循環變量,或後續會發生變化的變數。

如果一定要引用循環變數怎麼辦?方法是再建立一個函數,用該函數的參數綁定循環變數目前的值,無論該循環變數後續如何更改,已綁定到函數參數的值不變:


function count() {
  var arr = [];
  for (var i=1; i<=3; i++) {
    arr.push((function (n) {
      return function () {
        return n * n;
      }
    })(i));
  }
  return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

注意這裡用了一個「建立一個匿名函數並立刻執行」的語法:


(function (x) {
  return x * x;
})(3); // 9

理論上講,創建一個匿名函數並立刻執行可以這麼寫:


function (x) { return x * x } (3);

但是由於JavaScript語法解析的問題,會報SyntaxError錯誤,因此需要用括號把整個函數定義括起來:


(function (x) { return x * x }) (3);

通常,一個立即執行的匿名函數可以把函數體拆開,一般這麼寫:


(function (x) {
  return x * x;
})(3);

說了這麼多,難道閉包就是為了傳回一個函數然後延遲執行嗎?

當然不是!閉包有非常強大的功能。舉個栗子:

在物件導向的程式設計語言裡,例如Java和C++,要在物件內部封裝一個私有變量,可以用private修飾一個成員變數。

在沒有class機制,只有函數的語言裡,借助閉包,同樣可以封裝一個私有變數。我們用JavaScript建立一個計數器:


&#39;use strict&#39;;

function create_counter(initial) {
  var x = initial || 0;
  return {
    inc: function () {
      x += 1;
      return x;
    }
  }
}

它用起來像這樣:


##

var c1 = create_counter();
c1.inc(); // 1
c1.inc(); // 2
c1.inc(); // 3

var c2 = create_counter(10);
c2.inc(); // 11
c2.inc(); // 12
c2.inc(); // 13

在傳回的物件中,實作了一個閉包,該閉包攜帶了局部變數

x,並且,從外部程式碼根本無法存取到變數x。換句話說,閉包就是攜帶狀態的函數,而且它的狀態可以完全對外隱藏起來。

闭包还可以把多参数的函数变成单参数的函数。例如,要计算xy可以用Math.pow(x, y)函数,不过考虑到经常计算x2或x3,我们可以利用闭包创建新的函数pow2pow3


function make_pow(n) {
  return function (x) {
    return Math.pow(x, n);
  }
}

// 创建两个新函数:
var pow2 = make_pow(2);
var pow3 = make_pow(3);

pow2(5); // 25
pow3(7); // 343

脑洞大开

很久很久以前,有个叫阿隆佐·邱奇的帅哥,发现只需要用函数,就可以用计算机实现运算,而不需要0、1、2、3这些数字和+、-、*、/这些符号。

JavaScript支持函数,所以可以用JavaScript用函数来写这些计算。来试试:


&#39;use strict&#39;;

// 定义数字0:
var zero = function (f) {
  return function (x) {
    return x;
  }
};

// 定义数字1:
var one = function (f) {
  return function (x) {
    return f(x);
  }
};

// 定义加法:
function add(n, m) {
  return function (f) {
    return function (x) {
      return m(f)(n(f)(x));
    }
  }
}

以上是js中的閉包是如何使用的?的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn