javascript
JavaScript 闭包详解
2019獨角獸企業重金招聘Python工程師標準>>>
閉包是什么?
閉包(英語:Closure),又稱詞法閉包(Lexical Closure)或函數閉包(Function Closures),是引用了自由變量的函數。這個被引用的自由變量將和這個函數一同存在,即使已經離開了創造它的環境也不例外。所以,有另一種說法認為閉包是函數和聲明該函數的詞法環境的組合。你也可以理解為閉包就是一個帶狀態(狀態即引用的自由變量)的函數。
?
1、函數作為返回值
在高階函數中函數不僅可以作為參數進行傳遞,還可以作為返回值返回。
?我們先來實現一個兩個數字相加的例子
function add(x, y) {return x + y; } add(1, 2) // 3但是如果我們不想立馬計算,在后面根據需求在進行計算應該怎么辦?答案是返回求和的方法,而不是返回求和的結果。
function lazy_add(x, y) {var add = function(x, y) {return x + y;}return add; }當我們調用lazy_add()?時返回的并不是求和的結果,而是返回的求和函數
var fun = lazy_add(1, 2) // function add(){}當真正需要進行計算的時候我們可以調用fun()
var sum = fun(); //3那么我們調用的是fun方法,也并沒有將他怎么就知道是1和2相加了呢?原因就是fun方法是通過lazy_add方法傳遞兩個參數1和2生成的,lazy_add方法在返回add方法的同時將變量x和y也一并保存返回了。這就形成了自由變量x,y和函數add組成的閉包。
需要注意的是,當我們調用lazy_add()時,每次調用都會返回一個新的函數,即使傳入相同的參數:
var f1 = lazy_add(1, 2); var f2 = lazy_sum(1, 2); f1 === f2; // false?
2、循環中的閉包
?我們再看一個例子
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];在上面的代碼中在每次循環中生成了一個函數放到了數組中。你可能認為調用f1(),f2()和f3()結果應該是1,4,9,但實際結果是:三個都是 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另外可以通過ES6中的語法更輕松地解決這個問題,那就是通過let聲明循環變量,該方法可以避免使用過多的閉包(過多的閉包影響性能)代碼如下:
function count() {var arr = [];for (let 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];這個例子使用let而不是var,因此每個閉包都綁定了塊作用域的變量,這意味著不再需要額外的閉包。
說了這么多,難道閉包就是為了返回一個函數然后延遲執行嗎?當然不是!讓我們接著往下看。
?
3、函數柯里化(curry)
柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,并且返回接受余下的參數且返回結果的新函數的技術。讀起來可能優點繞,看了下面的例子你就會明白。
想必大家應該都用過 Math.pow(x, y)方法,是的這個就是計算x的y次方的方法,但是由于我們通常都是計算2次方和3次方,所以我們要通過閉包基數對該方法進行柯里化。
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(5); // 125?
4、對象封裝
在面向對象的程序設計語言里,比如Java和C++,要在對象內部封裝一個私有變量,可以用private修飾一個成員變量。
在沒有class機制,只有函數的語言里,借助閉包,同樣可以封裝一個私有變量。下面我們就用javascript創建一個包含私有變量的計數器。
function create_counter(initial) {var x = initial || 0;return {inc: function () {x += 1;return x;}} }var c1 = create_counter(1); c1.inc(); // 2 c1.inc(); // 3 c1.inc(); // 4在返回的對象中,實現了一個閉包,該閉包攜帶了局部變量x,并且,從外部代碼根本無法訪問到變量x?
?
5、閉包性能考量
如果不是某些特定任務需要使用閉包,在其它函數中創建函數是不明智的,因為閉包在處理速度和內存消耗方面對腳本性能具有負面影響。
例如,在創建新的對象或者類時,方法通常應該關聯于對象的原型,而不是定義到對象的構造器中。原因是這將導致每次構造器被調用時,方法都會被重新賦值一次(也就是,每個對象的創建)。
function MyObject(name, message) {this.name = name.toString();this.message = message.toString(); } MyObject.prototype.getName = function() {return this.name; }; MyObject.prototype.getMessage = function() {return this.message; };var obj1 = new MyObject('name1', 'message1'); var obj2 = new MyObject('name2', 'message2'); obj1.getName == obj2.getName; // true上面的代碼中構造的每個實例中的方法都指向同一個
但是如果是用閉包封裝的對象會怎么樣,接著用之前的計數器例子做一下測試
var count1 = create_counter(1); var count2 = create_counter(2);count1.inc == count2.int; //false從測試結果可以看出每個對象中的方法都是獨立的方法,這樣違背了對象方法公用原則
萬物皆有利弊,所以具體要怎么使用還是要仔細考量。
轉載于:https://my.oschina.net/kimyeongnam/blog/733809
總結
以上是生活随笔為你收集整理的JavaScript 闭包详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: easyui accordion全部是
- 下一篇: iOS - OC 与 C 互相操作