Javascript闭包常见用法

说明

学过JavaScript这门语言的同学想必都听说过闭包一词,闭包在日常开发中用的也是非常广泛的。可是当面试问你闭包是什么的时候,总感觉说不到点上。究竟什么是闭包呢?它是怎么起作用的?如何使用闭包去简化开发?如何用闭包提高性能以及如何用闭包解决作用域的问题?本文就为大家解开闭包的真面纱(为js开包…)

闭包

首先要知道什么是闭包就得知道闭包是怎么产生的。在javascript中只有函数存在作用域,并且函数可以被嵌套使用,so当我们使用函数套用的时候就产生了一个有趣的现象,我们在子函数中可以任意访问外部函数中定义的变量和方法,但是外面的函数却得不到里面函数中定义的变量。也就是子作用域被封闭起来的。那么问题来了,如何拿到子作用域里的变量呢?so闭包被产生了。也就是说闭包的唯一目的就是获取子作用域。
知道了闭包产生的目的后,我们来看看MDN对闭包的解释。

闭包是指能够访问自由变量的函数。换句话说,定义在闭包中的函数可以“记忆”它被创建时候的环境。

MDN不愧是官方解释的这么晦涩难懂,摆明不想让大家学会闭包。
来看看jQuery作者对闭包的定义吧:

a closure is the scope created when a function is declared that allows the
function to access and manipulate variables that are external to that function.
Put another way, closures allow a function to access all the variables, as well as other functions, that are in scope when the function itself is declared.

翻译成汉语其实就是闭包可以获取,操作内部作用域中定义的变量。
看完这段英语相信你已经对闭包有一定的感性理解。那么我们来从语法的角度分析一下闭包的组成。
它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。一说环境可能有点高深,我用数学表达来表述就是:闭包=函数(可匿名)+环境(作用域+局部变量)这下是不是很清晰了;
下次面试官再问你的时候直接给他来条公式,然后给他解释下目的啥的,就完事大吉了!!

闭包的常见应用

  1. 模拟私有变量 (Private variable)

JavaScript 并不提供原生的支持,但是可以使用闭包模拟私有方法。私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分。这种方式也称为 模块模式

module import variant

1
2
3
4
5
6
7
8
9
10
(function(window){
//私有属性
var privateThing;
function privateMethod(){
//私有方法
}
window.api = {
//暴露公共接口
}
})(window)

module pattern

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var api = (function(){
// Private and in-place!
var local = 0;
//私有作用域
function counter () {
return ++local;
}
//暴露给api
return {
counter: counter
};
})();
api.counter();
// <- 1
api.counter();
// <- 2

2.回调和计时器 (Callback and Timer)
在处理回调和计时器的时候我们可以使用闭包,这两个函数都需要异步被调用而且会很频繁的获取外部的数据.
异步回调(cb)最常见的是Ajax请求,例子就不举了用过jquery的同学都知道.

Timer

1
2
3
4
5
6
7
8
9
//这是一个60秒倒计时的计时器
var step = 0;
var timer = setInterval(function(){
if(step<60){
step++;
}else{
clearInterval(timer);
}
},1000);

3.绑定函数作用域(Binding function contexts)

1
2
3
4
5
function bind(context,name){
return function(){
return context[name].apply(context,arguments);
};
}

4.参数归并技术以及函数柯里化 (Argument-merging technique and currying)
柯里化是指我们可以部分调用函数(返回我们预定义参数的函数),这个函数可以被以后调用.简单来说就是我们可以首先填充函数的一部分参数(并返回新的函数)这种技术通常被叫做函数柯里化。因为参数开始被填充了一部分到函数,后面一边通过返回新的函数里引用剩余的参数.所以形象的叫做参数归并技术。其本质是一样的。
举个例子:

1
2
3
4
5
6
7
8
Function.prototype.curry = function() {
var fn = this,
args = Array.prototype.slice.call(arguments);
return function() {
return fn.apply(this, args.concat(
Array.prototype.slice.call(arguments)));
};
};

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.partial = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
var arg = 0;
for (var i = 0; i < args.length && arg < arguments.length; i++) {
if (args[i] === undefined) {
args[i] = arguments[arg++];
}
}
return fn.apply(this, args);
};
};

5.记忆和包裹函数(Memoization and Function wrapping)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.memoized = function(key){
this._values = this._values || {};
return this._values[key] !== undefined ?
this._values[key] :
this._values[key] = this.apply(this, arguments);
};
function isPrime(num) {
var prime = num != 1;
for (var i = 2; i < num; i++) {
if (num % i == 0) {
prime = false;
break;
on behavior 107
}
}
return prime;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//记忆
function memoizer(memo,formula){
var recur = function(n){
var result = memo[n];
if (typeof result !== 'number'){
result = formula(recur, n);
memo[n] = result;
}
return result;
};
return recur;
}
//阶乘
var factorial = memoizer([1,1],function(recur,n){
return n*recur(n-1);
})
//斐波那契数列
var fibonacci = memozier([0,1],function(recur,n){
return recur(n-1)+recur(n-2);
})

wrap function

1
2
3
4
5
6
7
function wrap(object, method, wrapper) {
var fn = object[method];
return object[method] = function() {
return wrapper.apply(this, [fn.bind(this)].concat(
Array.prototype.slice.call(arguments)));
};
}

6.立即执行函数(IIFE)

几种立即执行的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
(function() {
// child scope
})();

!function () {
// child scope
}();

+function () {
// child scope
}();

-function () {
// child scope
}();

~function () {
// child scope
}();

void function () {
// child scope
}();

1^function () {
// child scope
}();

1&function () {
// child scope
}();