JavaScript中最容易的犯錯的地方閉包是跑不了的,從從技術上來說,在JavaScript中,每一個function都是閉包,由於它老是能訪問在它外部定義的數據。閉包(Closure)是靜態語言所不具備的特性,閉包具備如下幾個特色:html
①閉包就是函數的局部變量集合,只是這些局部變量在函數返回後會繼續存在;② 閉包就是就是函數的「堆棧」在函數返回後並不釋放,咱們也能夠理解爲這些函數堆棧並不在棧上分配而是在堆上分配③ 當在一個函數內定義另一個函數就會產生閉包。express
爲了更好的理解閉包,咱們能夠先來簡單的看一個例子:閉包
var scope="global"; function outerfunc() { var scope = "博客園-FlyElephant"; function innerfunc() { console.log(scope);//博客園-FlyElephant } innerfunc(); } outerfunc(); innerfunc();//innerfunc is not defined
在函數外部訪問函數內部的嵌套函數是無法訪問,函數中嵌套的函數和函數內部定義的變量處於處於同一個變量做用域中,根據做用域鏈的範圍先從同一做用域鏈中尋找,若是函數內容沒有定義scope變量,那麼最終輸出的結果應該是"global"。這只是最簡單的閉包形式,若是閉包這麼簡單,也不會成爲JavaScript中的難點。函數
針對上面的例子,咱們稍加修改:this
var scope = "global"; function outerfunc() { var scope = "博客園-FlyElephant"; function innerfunc() { console.log(scope); //博客園-FlyElephant } return innerfunc; } outerfunc()();
結果最終輸出的仍是局部變量的值,這個點比較容易誤解由於函數已經調用完成,局部變量應該已經不存在,輸出的應該是「global」,實際上函數內部的嵌套函數對局部變量存在引用,會保持局部變量,專業一點的講法應該是keep alive。spa
相信這個時候你對閉包已經稍微有點感受,來看一下經典的例子:插件
function constfuncs() { var funcs = []; for (var i = 0; i < 10; i++) { funcs[i] = function() { console.log(i); }; } return funcs; } var funcs = constfuncs(); funcs[6]();
按照咱們的應該是輸出的是6,可是最終輸出的結果是10,經過上面的例子咱們知道變量和咱們定義的匿名函數都在同一個做用域,匿名函數訪問的是i最終的值,i的最終值是10,所以輸出的是10,單從這個例子上看可能沒什麼感受,看下來的實際開發中的例子咱們會理解更深入一點:code
$(function(){ var eles=$('.closure'); for (var i = 0; i < eles.length; i++) { eles[i].onclick=function(){ alert(i); } } });
毫無疑問不管點擊哪一個元素最終輸出的結果都是元素的總數,若是咱們想讓上面的例子點擊funcs[6]()輸出6也是能夠的,就是讓每個函數都擁有對應的局部做用域,看接下來的改法:orm
function constfuncs() { var funcs = []; for (var i = 0; i < 10; i++) { (function(i) { funcs[i] = function() { console.log(i); }; })(i); } return funcs; } var funcs = constfuncs(); funcs[6]();
JavaScript還有個很是重要的功能是隱藏數據,這個個插件封裝中用的比較多,先來看一個簡單的計數器:htm
var counter = (function() { var count = 0; return function() { console.log(count); return count++; }; }()); counter(); counter();
這個輸出的是0,1,而不是0,0,至於緣由文中末尾會給出解釋,看一下數據封裝的升級版本:
var db = (function() { // 建立一個隱藏的object, 這個object持有一些數據 // 從外部是不能訪問這個object的 var data = {}; // 建立一個函數, 這個函數提供一些訪問data的數據的方法 return function(key, val) { if (val === undefined) { return data[key] } // get else { return data[key] = val } // set } // 咱們能夠調用這個匿名方法 // 返回這個內部函數,它是一個閉包 })(); db('x'); // 返回 undefined db('x', 1); // 設置data['x']爲1 db('x'); // 返回 1 // 咱們不可能訪問data這個object自己 // 可是咱們能夠設置它的成員
這裏面用到一個小技巧就是讓函數成爲一個當即調用執行的表達式,而後經過內部函數保持外部變量,若是不是當即調用執行的狀態,咱們會發現每次都是一個新的函數,沒法保持數據狀態:
var counter = function() { var count = 0; return function() { console.log(count); return count++; }; }; counter()(); counter()();
關於當即調用的函數表達式也有些人直接稱之爲自執行的匿名函數(self-executing anonymous functions),其實關於當即調用的函數表達式有十幾種書寫的方式,咱們常見的就是左括號和右括號兩種:
(function(){console.log('1');})() (function(){console.log('2');}())
其餘的書寫方式以下:
// Either of the following two patterns can be used to immediately invoke // a function expression, utilizing the function's execution context to // create "privacy." (function(){ /* code */ }()); // Crockford recommends this one (function(){ /* code */ })(); // But this one works just as well // Because the point of the parens or coercing operators is to disambiguate // between function expressions and function declarations, they can be // omitted when the parser already expects an expression (but please see the // "important note" below). var i = function(){ return 10; }(); true && function(){ /* code */ }(); 0, function(){ /* code */ }(); // If you don't care about the return value, or the possibility of making // your code slightly harder to read, you can save a byte by just prefixing // the function with a unary operator. !function(){ /* code */ }(); ~function(){ /* code */ }(); -function(){ /* code */ }(); +function(){ /* code */ }(); // Here's another variation, from @kuvos - I'm not sure of the performance // implications, if any, of using the `new` keyword, but it works. // http://twitter.com/kuvos/status/18209252090847232 new function(){ /* code */ } new function(){ /* code */ }() // Only need parens if passing arguments
參考連接:http://benalman.com/news/2010/11/immediately-invoked-function-expression/#iife