本篇博客记录JavaScript中闭包的使用,这也是在面试过程中被问到的点。
概念
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.
首先我们要明白闭包是什么: **闭包是指有权访问另一个函数作用域中的函数。**创建闭包的常见方式为在一个函数内部创建另一个函数,我们以以下代码举例。
function createComparisonFunction(propertyName) {
return function (obj1, obj2) {
var value1 = obj1[propertyName];
var value2 = obj2[propertyName];
if (value1 > value2) {
return 1;
} else if(value1 < value2) {
return -1;
} else {
return 0;
}
}
}
从代码中可以看出,内部匿名函数可以访问外部函数的 propertyName
属性,这是因为内部函数的作用域链中包含了外部函数的作用域,想要这地理解闭包就必须从先理解作用域链,可以参考我往期的博客,这里再做一下简单介绍。
作用域链
函数调用的详细介绍
- 创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在函数内部的
[[Scope]]
中 - 当某个函数被调用时,会创建一个执行环境(execution context)
- 复制函数中的
[[Scope]]
属性创建作用域链 - 创建一个新的活动对象并被推入作用域链前端
- 使用 arguments 和其他命名参数来初始化函数的活动对象(activation object)
无论什么时候在函数中访问一个变量时,就会从作用域链中搜索具有相应名字的变量,一般当函数执行完毕后,局部活动对象就会被销毁,内存中仅保存全局作用域。
几个代码实例
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //"The Window"
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()()); //"MyObject"
对比以上两个代码的结果可知,闭包函数被调用时,会自动搜索两个对象 this
和 arguments
, 二这两个变量只会搜索到活动对象位置, 在这里我们可以理解为第一个代码中闭包在搜索 this时 首先搜索闭包函数内部有没有 name
, 然后再搜索 getNameFunc
中有没有 name
, 最后在全局变量中搜索 name
, 所以最终打印 The window
执行环境与活动对象的区别
var与let在闭包中的体现
看下面几段代码:
function createFuncitions() {
var result = new Array();
for (let i=0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
function createFuncitions() {
var result = new Array();
for (var i=0; i < 10; i++) {
result[i] = function () {
return i;
};
}
return result;
}
当我们分别调用 createFuncitions
返回的函数数组时,发现循环中使用 let
时,调用结果为0到9, 但是循环中使用 var
时, 调用结果都为10。这是因为闭包只能取得包含函数中任何变量中的最后一个值,当使用 var
时,每个函数都引用着保存变量 i
的同一个变量对象。
风险
闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多。
可用调用匿名函数的方法来减少闭包内存占用的问题,因为没有指向匿名函数的引用,只要函数执行完毕,就可以立即销毁其作用域了。
(function () {
}) ()