我的理解:能访问父函数的参数并且能立即执行或者被直接return出来的函数为闭包
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f();
}
checkScope(); //=> "local scope"
分析一下上面的代码,该代码定义了一个全局变量 scope,以及一个函数checkScope,在函数checkScope中,定一个一个局部变量,同样命名为scope,以及一个函数f(嵌套函数)。
1.在js中,函数可以用来创建函数作用域;
2.函数就像一层半透明玻璃,在函数内部可以看到函数外部的变量,但是在函数外部,看不到函数内部的变量。
3.变量的搜索是从内向外而不是从外向内搜索的。
代码执行过程分析
checkScope被调用时,return f(),运行内部嵌套函数f,f沿着作用域链从内向外寻找变量scope,找到“local scope”,停止寻找,因此,函数返回 “local scope”;
var scope = "global scope";
function checkScope() {
var scope = "local scope";
function f() {
return scope;
}
return f;
}
checkScope()(); //=> "这次返回什么?"
代码执行过程分析:
checkScope被invoke时,将内部嵌套的函数f返回,因此checkScope()()这句执行时,其实运行的是f(),f函数返回scope变量,在这种情况下,f会从哪个作用域里去寻找变量scope呢?
记住词法作用域的基础规则:函数被执行时使用的作用域链(scope chain)是被定义时的作用域链,而不是执行时的作用域链。
闭包的神奇特性:闭包可以捕获到外部函数的变量和参数,即便外部函数的调用已经结束。
场景描述:假如页面上有5个button,要给button绑定onclick事件,点击的时候,弹出对应button的索引编号。(循环给多个button绑定事件)
先看一段不是闭包的代码
<html>
<head>
<meta charset="UTF-8">
head>
<body>
<button>Button0button>
<button>Button1button>
<button>Button2button>
<button>Button3button>
<button>Button4button>
body>
html>
<script>
var btns = document.getElementsByTagName('button');
for(var i = 0, len = btns.length; i < len; i++) {
btns[i].onclick = function() {
alert(i);
}
}
script>
通过执行该段代码,发现不论点击哪个button ,均alert 5;
原因:onclick事件是被异步触发了,当事件被触发时,for循环早已结束,此时变量 i 的值已经是 5 。所以,当onlick事件函数顺着作用域链从内向外查找变量 i 时,找到的值总是 5 。
Tip: 在js中,var是函数作用域,let是块级作用域
解决方法1:var改成let
解决方法2: 采用立即执行函数创建作用域
var btns = document.getElementsByTagName('button');
for(var i = 0, len = btns.length; i < len; i++) {
(function(i) {
btns[i].onclick = function() {
alert(i);
}
}(i))
}
以上解决方法就是闭包的使用场景之一:在闭包的作用下,定义事件函数的时候,每次循环的i值都被封闭起来,这样在函数执行时,会查找定义时的作用域链,这个作用域链里的i值是在每次循环中都被保留的,因此点击不同的button会alert出来不同的i。
闭包可以将一些不希望暴露在全局的变量封装成“私有变量”。
假如有一个计算乘积的函数,mult函数接收一些number类型的参数,并返回乘积结果。为了提高函数性能,我们增加缓存机制,将之前计算过的结果缓存起来,下次遇到同样的参数,就可以直接返回结果,而不需要参与运算。这里,存放缓存结果的变量不需要暴露给外界,并且需要在函数运行结束后,仍然保存,所以可以采用闭包。
var mult = (function(){
var cache = {};
var calculate = function() {
var a = 1;
for(var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
return function() {
var args = Array.prototype.join.call(arguments, ',');
if(args in cache) {
return cache[args];
}
return cache[args] = calculate.apply(null, arguments);
}
}())
img对象经常用于数据上报,如下
var report = function(src) {
var img = new Image();
img.src = src;
}
report('http://xxx.com/getUserInfo');
这段代码在运行时,发现在一些低版本浏览器上存在bug,会丢失部分数据上报,原因是img是report函数中的局部变量,当report函数调用结束后,img对象随即被销毁,而此时可能还没来得及发出http请求,所以此次请求就会丢失。
因此,我们使用闭包把img对象封闭起来,就可以解决数据丢失的问题:
var report = (function() {
var imgs = [];
return function(src) {
var img = new Image();
imgs.push(img);
img.src = src;
}
}())