JavaScript闭包

什么是闭包?

闭包,又称词法闭包或函数闭包,是引用了自由变量的函数。这个被引用的自由变量和这个函数一同存在,即使已经离开了创造它的环境也不例外。
JS中闭包实现实例

1
2
3
4
5
6
7
8
9
function foo(){
var temp = 3;
return function(){
console.log(temp++);
}
}
var bar = foo();
bar(); //3
bar(); //4

上述例子中,bar 就是闭包,它引用了自由变量 temp
并且即使离开了创造它的环境( foo() 函数已经执行完毕),自由变量( temp )和这个函数( bar )也一同存在。

闭包应用场景

闭包内,变量无法释放,只要 bar 一直存在,temp 就不会释放,而且只能通过 bar 来改变 temp 的值,
所以我们可以利用闭包来实现以下这些功能:
1、管理私有变量
2、实现函数的柯里化和反柯里化

函数柯里化(Currying)

函数柯里化就是把接受多个参数的函数变成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

1
2
3
4
var addTwo = function(x,y){
return x+y
}
addTwo(5,10) //15

将addTwo柯里化之后就是:

1
2
3
4
5
6
var addTwoCurry = function(x){
return function(y){
return x+y
}
}
addTwoCurry(5)(10) //15

或者:

1
2
3
4
var add5 = addTwoCurry(5);
add5(10) //15
var add6 = addTwoCurry(6);
add6(10) //16

通用的currying函数

1
2
3
4
5
6
7
8
9
currying(fn,args);
//参数fn为你想要柯里化的函数
var currying = function(fn){
var args = [].prototype.slice.call(arguments,1)//获取currying传进来的非fn的所有参数
return function(){
var nArgs = args.concat(Array.prototype.slice.call(arguments)); // 把之前传的柯里化的参数和之后调用柯里化函数的参数拼接在一起
fn.apply(null,nArgs); //实际上最后执行的还是,一个函数调用所有的参数
}
}

函数的反柯里化

我们在写代码的过程中,经常会遇到一些 “类数组对象”,比如 HTMLColleciton 或者 arguments ,他们没有原生的 Array 方法,
有一个技巧就是通过,[].method.call(likeArray,arg) ; 来使类数组对象调用Array的原生方法
但是我们每次调用的时候,都需要写一遍 [].method.call() 或者 Array.prototype.method.call(),比较麻烦。
因此我们可以利用反柯里化,把这些方法进行反柯里化处理,从而让更多的对象能够调用这个方法。

通用的uncurrying函数

1
2
3
4
5
6
7
8
9
10
Function.prototype.uncurrying = function(){
var _this = this; //获取你想要 反柯里化 的函数
return function(){
return Function.prototype.call.apply(_this,arguments) //然后利用call改变this指向,并接受参数调用
}
}
var obj = {}
var push = Array.prototype.push.uncurrying()
push(obj,'a')
obj //{0:'a',length:1}

延伸阅读(JS闭包的实现)

作者:wyatt-pan
链接:https://www.zhihu.com/question/19554716/answer/12763637
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

JavaScript 中的闭包实现与 JavaScript 的 Scope Chain 是密不可分的. 首先在 JavaScript 的执行中会一直存在一个 Execute Context Stack (想想 JavaScript 解释器在看到一个 alert(x) 的时候, 如果没有上下文他怎么知道这个 x 是什么?), Execute Context Stack 中最下面一个一定是 GlobalContext, 而在每一个函数的执行开始就会向这个 stack 中压入一个此 Function 的 Execution Context; 而一个 Execution Context 的组成分为三部分:

  1. Variable Object: 存储方法内的变量 vars, 方法传入的参数, 函数内定义的函数等等(函数表达式不保存), Variable Object 在任何时候是不可以被直接访问到的, 当然不同的 JS 引擎提供了访问接口就说不定了;
  2. Scope Chain: 这个函数执行的时候用以寻找值的 Scope Chain, 这个 Scope Chain 由 Variable Object + All Parent Scopes 组成, Variable Object 会放在这个 Scope Chain 的最前面, 这也是为什么函数内的变量会被最先找到;
  3. thisValue, 函数被调用的时候的 this 对象, 存储的就是函数的调用者(caller)的引用;

对于 Variable Object 在不同的情况下会有不同的定义, 例如在全局的时候被称为 Global Object, 而在函数中则被称为 Activation Object 激活对象;

正是由于有了 Execution Context 中的 Scope Chain, JavaScript 才能够使得在方法 bar()
的内部访问到方法 foo() 中的变量 a, 才能够使方法 bar() 将变量 a 关闭在自己的作用范围内不让他随 foo() 方法的执行完毕而销毁;

有趣的闭包图解


最后用阮一峰大神的文章问题结尾:

1
2
3
4
5
6
7
8
9
10
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    return function(){
      return this.name;
    };
  }
};
alert(object.getNameFunc()()); //'The Window'
1
2
3
4
5
6
7
8
9
10
11
var name = "The Window";
var object = {
  name : "My Object",
  getNameFunc : function(){
    var that = this;
    return function(){
      return that.name;
    };
  }
};
alert(object.getNameFunc()()); //'My Object