最近在面试,发现面试官都非常喜欢问闭包相关的东西,所以写一篇文章整理一下自己的思路
一、作用域
在讲闭包之前必须说一下js作用域的问题;js在ES6之前其实是没有块级作用域这个概念的,但是有函数作用域这个概念,函数作用域可以访问父级作用域内部的变量,但是父级作用域无法访问函数内部的变量;
var firstName = '王';
function alertName(){
var lastName = '诶诶';
console.log(firstName);
};
alertName();//王
console.log(lastName);//Uncaught ReferenceError: lastName is not defined
二、闭包-例子1
闭包是指有权访问另一个函数作用域中变量的函数,创建闭包最常见的方式是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用域链,将函数内部的变量和方法传递到外部。
上代码:
for(var i=0;i<5;i++){
setTimeout(function(){console.log(new Date,i)},1000);
}
这个的输出是:
Tue May 15 2018 11:28:25 GMT+0800 (中国标准时间) 5
Tue May 15 2018 11:28:25 GMT+0800 (中国标准时间) 5
Tue May 15 2018 11:28:25 GMT+0800 (中国标准时间) 5
Tue May 15 2018 11:28:25 GMT+0800 (中国标准时间) 5
Tue May 15 2018 11:28:25 GMT+0800 (中国标准时间) 5
这里涉及到的是异步代码的执行问题,为什么会输出5个5?因为函数执行的时候,i已经变成了5
那么如何输出0-1-2-3-4呢?这里就涉及到了闭包问题
var output = function(j){
setTimeout(function(){console.log(new Date,j)},1000);
}
for(var i=0;i<5;i++){
output(i);
}
因为js基本类型的参数传递是按值传递,输出为:
Tue May 15 2018 11:35:17 GMT+0800 (中国标准时间) 0
Tue May 15 2018 11:35:17 GMT+0800 (中国标准时间) 1
Tue May 15 2018 11:35:17 GMT+0800 (中国标准时间) 2
Tue May 15 2018 11:35:17 GMT+0800 (中国标准时间) 3
Tue May 15 2018 11:35:17 GMT+0800 (中国标准时间) 4
三、闭包-例子2
如果我们要实现一个计数器,最简单方法:
var count = 0;
function increse() {
count++;
console.log('count is:',count);
}
increse();//count is: 1
increse();//count is: 2
那么如果要实现两个呢?写两次代码吗?no,我们可以用闭包了。
function counter() {
var count = 0;
function increse(){
count++;
console.log('count is:',count);
}
return increse;
}
var count1 = counter();
var count2 = counter();
count1();//count is: 1
count1();//count is: 2
count2();//count is: 1
在代码中,我们创建了两个独立的计数器counter1与counter2,分别进行计数,互不干挠。代码看着有点奇怪,我们不妨拆分起来分析。
首先,我们来看看counter:
- 创建了一个局部变量counter
- 创建了一个局部函数increse(),它可以对count变量进行加1操作。
- 将局部函数increse()返回。注意,返回的是函数本身,而不是函数调用的结果。
看起来,counter()函数与我们最初定义的计数器非常相似。唯一的不同点在于:counter()将计数器封装在一个函数内,于是我们将它称作闭包。闭包的神奇之处在于。每次使用counter()函数创建计数器时,都会创建一个对应的count变量。并且,返回的increment函数会始终记住count变量。更重要的是,这个count变量是相互独立的。比如,当我们创建2个计数器时,每个计数器都会创建一个新的count变量。