前言
这个话题真的是。。。哈哈,对于前端开发的人来说真的是一个神圣的话题。怎么说呢,就是好像搞得闭包知道了你就很牛逼了。
反正我以前是这么想的,主要因为对初学者来说也挺深奥的,看网上人家分享出来的文章看了很多遍,理解了很多遍。感觉这是js开发的一个分水岭,掌握了就可以吹牛逼了哈哈哈。
之前我面试别人的时候,这个问题必问,主要也想看下你会怎么说,相当坏呐哈哈,因为也扯不大清。
作用域
先理解一下小概念。作用域常见的有全局作用域,函数声明里面会生成作用域。
作用域链
链就是把作用域们串起来了。作用域查询变量的时候是先从自己所在的上下文执行环境查找,找不到再问问父级作用域,一层层上去,也就形成了作用域链。
概念
简单来说就是函数内部返回的内部函数。
前面介绍的作用域链它的查找规则是从内而外,但是有些时候,我们需要在外面访问里面的变量。这就需要闭包了。
所以仔细点来说就是函数在定义时的词法作用域以外的地方被调用
,这个函数就是闭包。1
2
3
4
5
6
7
8
9function foo() {
var a = 2;
function bar() {
console.log( a );
}
return bar;
}
var baz = foo();
baz(); // 2 —— 朋友,这就是闭包的效果。
上面展示的例子中。bar函数
就是闭包
。它被调用的时候是在全局的词法作用域,而不是在自己定义时的词法作用域中(foo里面)。
闭包理解为函数嵌套,内部函数操作外部函数的局部变量,且外层函数的引用被赋值给变量,通过变量访问内层函数匿名函数。
形成原因
原因很清晰了,因为js是采用词法作用域
的,作用域兄弟的查询变量规则也是顺从作用域链
的,然后导致父亲没有访问子级的作用域变量的权限。
所以采用了迂回策略,在子级作用域搞一个函数操作这个自己作用域,然后把这个函数抛出去。。。。。
案例
看到这里我觉得也该理解了闭包是个啥了。但是很多小伙伴即使知道是啥,但是也记不住,为啥?因为没有实践呐。平常压根就不知道自己使用了吗。就算看了很多篇文章也只认识个概念,
脑子里还是觉得闭包
离自己很远,实际碰上了也不认识,没印象。其实这讲的是我。反正我以前是这样的。觉得真难!
回调
1 | function wait( message ) { |
这里的setTimeout
其实是一个全局的挂在window的一个外包方法。
这里将内部函数(timer)传递给 setTimeout(..)。timer是具有涵盖 wait(..) 作用域的闭包。看看是不是很清晰。
在定时器、事件监听器、 Ajax请求、跨窗口通信、Web Workers或者任何其他的异步(或者同步)任务中,只要使用了回调函数,实际上就是在使用闭包!
因为本质上它们是把这个回调函数传递出去了,这个回调函数恰巧又能拿到父级的作用域。
循环
1 | for (var i = 1; i <= 5; i ++) { |
上面的demo很熟悉吧,网上的各个分享的文章都会拿来做例子,说这里期望是每隔一秒打印1-5,而实际上却是最后会打印5遍6。
常规解释一把,当到达指定时间执行回调的时候拿到的i是这个for循环里面的,这个i已经是6了,所以都会打印6。那怎么才能达到自己的目的呢?
要达到这个目的有很多种方法。
- 把
var
换成let
,使i不共享 - 变相的把i隔出来
1
2
3
4
5
6
7
8for (var i = 1; i <= 5; i ++) {
(function(i){
setTimeout( function timer() {
console.log( i );
}, i * 1000 );
}
)(i)
}
其实本质上是把i隔开,最初看起来5个timer函数是在各自的for循环中声明的,但是其其实是在共享i,在编译器编译期间,会先var i
在外部的作用域中。
模块
现在的前端发展真快,又要感慨了。
早些那时候还流行requireJs
,cmd
的时候,模块的概念刚刚出来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule(); foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3
这种写法是不是很熟悉!这种就是模块
。里面这个doSomething函数
,doAnother函数
被暴露出来在其他环境中调用,就是闭包。
现在使用vue
,react
的都很熟悉es6
了。es6
也有模块的概念。1
2
3
4
5
6
7
8
9// A.js
function hello(who) {
return "Let me introduce: " + who;
}
export hello;
// B.js
import hello from "A";
// 从A模块导出hello函数,这个也是闭包的一种体现
总结
感觉说道这里肯定是明白了,没啥好总结了,都在上面了。
现在看看,觉得真的简单,所以看问题还是要从本质原理上走。要是以前的时候就这么看的话,就会少走很多弯路。不会那时候也菜,根本没这理解力。
参考资料
推荐你不知道的javascript