javascript閉包介紹

概念

閉包(closure)是一個擁有任意變量以及綁定這些變量的環境(environment)的表達式(通常來講是就是function)javascript

A "closure" is an expression (typically a function) that can have free variables together with an environment that binds those variables (that "closes" the expression).html

在做用域內且在function定義時被訪問的變量,那麼這個變量就一直可以被那個function訪問。java

variables that are in scope and accessed from a function declaration will stay accessible by that function.express

下面這個例子就是閉包,displayName函數可以訪問到不在其代碼塊裏的name變量。閉包

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

函數的做用域 functional scoping

一個變量的做用域是以其所在的源代碼的位置來定義的,嵌套在裏面的function能夠訪問到聲明在外層做用域的變量ide

The scope of a variable is defined by its location within the source code, and nested functions have access to variables declared in their outer scope.函數

仍是拿剛纔那個例子來講,displayName函數是嵌套在init函數裏的,因此它可以訪問到init函數裏的變量性能

function init() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  displayName();
}
init();

閉包的組成

先看一下這個例子:學習

function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
myFunc();

按照java或C++的經驗,局部變量name的生命週期在函數的執行後就結束了,因此會推斷namemakeFunc()訪問後應該就訪問不到了。
然而事實偏偏相反,惟一的解釋就是myFunc是一個閉包(closure)。ui

閉包由兩部分組成:

  1. function

  2. 建立該function的環境(建立閉包時,做用域內的全部局部變量)

對應到上面的這個例子裏:

  1. function: displayName

  2. 環境:name="Mozilla"

再看一個例子:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

alert(add5(2));  // 7
alert(add10(2)); // 12

這個例子說明閉包的function能夠是相同的,可是環境能夠是不一樣的,所以就會有不一樣的結果。

概括

所以能夠將閉包概括爲:

  1. 定義時,肯定可訪問變量

  2. 執行時,肯定變量的值

常見錯誤

下面這段代碼實際上執行的時候並非alert 0,1,2,3,4,而是alert 5次5。
這是爲何?由於i變量在for循環後變成了5,而在執行的時候咱們纔會肯定閉包裏i的值,在定義的時候不會記住i的值是什麼的。

var funcs = [];
for(var i=0; i < 5; i++) {
  funcs[i] = function() { alert(i); }
}

for(var j=0; j < funcs.length; j++) {
  funcs[j]();
}

正確的寫法是:

var funcs = [];
function makeFunc(x) {
  return function() { alert(x); }
}
for(var i=0; i < 5; i++) {
  funcs[i] = makeFunc(i)
}

for(var j=0; j < funcs.length; j++) {
  funcs[j]();
}

閉包實踐

函數工廠

function makeSizer(size) {
  return function() {
    document.body.style.fontSize = size + 'px';
  };
}

var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);

私有變量

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }
})();

alert(Counter.value()); /* Alerts 0 */
Counter.increment();
Counter.increment();
alert(Counter.value()); /* Alerts 2 */
Counter.decrement();
alert(Counter.value()); /* Alerts 1 */

在這個例子裏:

  • 外界不能訪問: privateCounter,changeBy

  • 外界間接訪問: increment,decrement,value

私有變量+函數工廠

var makeCounter = function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }  
};

var Counter1 = makeCounter();
var Counter2 = makeCounter();
alert(Counter1.value()); /* Alerts 0 */
Counter1.increment();
Counter1.increment();
alert(Counter1.value()); /* Alerts 2 */
Counter1.decrement();
alert(Counter1.value()); /* Alerts 1 */
alert(Counter2.value()); /* Alerts 0 */

Counter1和Counter2綁定的環境相互獨立。

性能問題

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
  this.getName = function() {
    return this.name;
  };

  this.getMessage = function() {
    return this.message;
  };
}

執行一次,就會從新構造兩個函數。

正確的作法應該是:

function MyObject(name, message) {
  this.name = name.toString();
  this.message = message.toString();
}
MyObject.prototype = {
  getName: function() {
    return this.name;
  },
  getMessage: function() {
    return this.message;
  }
};

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;
};

參考資料

相關文章
相關標籤/搜索