前言
上一篇 JS进阶篇-作用域 中我们对javascript 的作用域进行了介绍,并且提到了 函数作用域 / 词法作用域/ 作用域链 / 执行上下文栈, 这里将对JavaScript 闭包做深入的介绍。
正文
提到闭包,必然不能脱离了作用域(脱离了作用域谈闭包都是在耍流氓),这里先来看下闭包是怎么定义的。
[维基百科] 闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。
看了这个官方定义之后,可以发现其实闭包随时都存在我们写的每一段代码之中,只是我们很少去主动察觉到它的存在,就如我们在全局定义了一个变量,然后在函数内部去使用它(RHS 或者 LHS),其实这也是闭包的一种表现形式。
下面主要列出几种在 javascript 中闭包的常见表现/使用形式,来一一进行介绍。
1. 私有变量与闭包
function Test() {
var _inner = "xxx";
this.setVal = function(val) {
_inner = val;
}
this.getVal = function() {
return _inner;
}
}
var test = new Test();
从运行结果可以看出,test 中并没有 _inner 属性,但是呢,却可以对其进行访问和赋值。
接下来对 test 的 getVal 属性进行展开,可以发现它拥有一个私有属性[[scopes]] -- 也就是其自身作用域,其 [[scopes]] 属性中有个元素是闭包(Closure) 其为 {_innner: "zzz" }
。
原来 _inner 被藏到了 [[scopes]] 里面,我们在进行 getVal 、 setVal 的时候
正是操作的闭包里的 _inner 。 我们通过 test._inner 是无法访问的,因此这种写法就 借助闭包来实现类的私有变量或属性。
是不是很魔法 ~
2. 回调函数与闭包
function operate(id) {
var dom = document.querySelector("#"+id);
var count = 0;
var timer = null;
timer = setInterval(() => {
if(count === 100)
clearInterval(timer);
dom.style.left = count++;
}, 30);
}
operate("id1");
operate("id2");
正如上面代码所表达的,我们创建了一个dom 的动画 放到定时器的回调中, 在两次调用 operate 时候,都会分别创建独立的 id / dom / count / timer
变量,不会造成相互污染,只要有一个通过闭包访问这些变量的函数存在,这个词法环境就一直存在,直到不再使用被清理回收,这可以很好的简化我们的代码,提高代码的易读性/维护性。
3. for 循环与闭包
function test() {
for(var i=0; i<10; i++) {
setTimeout(() => {
console.log(i);
})
}
}
test();
其执行结果如上图所示,定时器回调中 i = 10 打印 10 遍, 这是为什么?
其实仔细思考一下,其作用域范围,这个似乎存在缺陷的代码结果也是在情理之中,因为在10次循环中,定时器回调访问的 i 变量,似乎是一个全局的 i , 并不是每一个循环都会拥有一个循环内所在作用域的 i ,当 i 执行到 i = 10 时循环终止,也就是 i 这个变量最终的值,被每个定时器回调中所引用的值,
其实上面的写法好比全局的 i++ , 然后写了 10 遍定时器,如下所示。
var i;
for(i=0; i<10; i++);
setTimeout(() => {
console.log(i);
})
setTimeout(() => {
console.log(i);
})
// ...
那么怎么才能让它输出为 1 , 2, 3... 9 呢?我们可以为每个定时器回调创建一个独立的作用域,形成 10 个独立的闭包变量,这里我们借助 iife 自执行函数来 实现其效果。
function test() {
for(var i=0; i<10; i++) {
(function() {
var j = i;
setTimeout(() => {
console.log(j);
})
})()
}
}
test();
这样就定时器回调函数可以拥有属于自己作用域的变量 j , 并且每次循环都会创建 一个独立的 j,运行结果如下图所示。
这正是我们想要的运行结果。这段似乎有缺陷的代码 借助闭包作用域的知识 有效得到了解决 ! 是不是很有趣~
结语
经过上面的介绍,想必已经对闭包已经有了一定了解,闭包可以记住并且访问所在的词法作用域,函数在当前作用域之外执行时就产生了闭包。
闭包是 javscript 中一种魔法的存在,也是 javascript 作用域的副作用,既然了解了闭包,那么就去使用它做一些用趣的事情吧。
问题:下面代码执行时,哪个变量是通过闭包访问的?
function Test() {
var _static = "xxx";
this.getStic() = function () {
return _static;
}
this.str = _static + "zzz";
this.getStr = function() {
rteurn this.str;
}
}
var test = new Test();
test.getStr() //
test.getStic() //
ps:如有不足 欢迎指正
一只前端小菜鸟 | 求知若渴 | 梦想与爱皆不可辜负