运行如下代码,猜一下运行结果
function foo1 (){
let sum = 1;
function foo2 (){
sum ++ ;
// 打印的值
console.log(sum)
return sum;
}
return foo2;
}
let closureFun = foo1();
// 运行两遍
closureFun(); // 这里console会打印2
closureFun(); // 这里console会打印3
你肯定会有疑惑,为什么运行两次后打印的值不一样呢,这就是闭包所产生的效果,下面会具体介绍一下。在了解闭包之前,先了解一下闭包的概念,函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起构成闭包(closure) 。我先抛出一下自己的见解,闭包是指在内部函数里引用了外部函数定义的变量后,导致函数运行完函数内部的变量无法释放从而构成了闭
一、作用域
在es6之前js作用域分为全局作用域和函数作用域,在es6以后多了一个块级作用域
全局作用域,在函数的最外层定义的变量,任何局部作用域都可访问到
var a = 1;
function foo (){
console.log(a); // 1
}
函数作用域,仅在当前函数作用域可访问到,函数作用域以外会报错
function foo (){
var a = 1;
}
console.log(a); // Uncaught ReferenceError: a is not defined
块级作用域,let和const只在块级作用域生效,代码块之外访问会报错未定义
{
var b = 1;
let c = 1// Uncaught ReferenceError: c is not defined
}
console.log(b)
console.log(c)
作用域链,查找变量的时候会当前作用域这从内到外一层一层查找,这种各层作用域就组成了作用域链
var a = 1;
function foo (){
var a = 2 ;
function foo1 (){
console.log(a); //
}
}
如代码所示,在查找a变量的时候会先在当前foo1函数作用域查找,如果找不到会往外层函数foo作用域查找,找到后会打印,如果foo没有a变量会继续往全局作用域查找,找到也会执行打印操作,否则报错undefined。
二、词法作用域
JS的代码执行顺序由书写顺序确定,而非执行顺序决定
var name = 'Mike' ; //第一次定义name
function showName () {
console.log(name); //输出 Mike 还是 Jay ?
}
function changeName () {
var name = 'Jay' ; //重新定义name
showName(); //调用showName()
}
changeName();
答案是Mike,整体代码可以分为三个作用域,一是全局作用域,二是showName函数作用域,三是changeName函数作用域,在调用changeName函数后,当前作用域切换为changeName函数作用域,当执行showName方法后,当前作用域又会切换为showName函数作用域,当在showName作用域里没找到name变量,则会跳出showName函数向外层寻找,发现全局变量name = 'Mike',则打印结果为Mike
三、闭包
在了解作用域和词法作用域后,我们开始了解闭包
闭包形成的原因:内部函数引用了外部函数定义的变量,没有及时释放,保存在内存中,运行上述代码后,在浏览器如下图位置可以看到,浏览器控制台 ==》source ==》 scope有一个Closure的对象
闭包用途
获取函数内部的变量
内部函数引用外部函数的变量后让变量的值始终保持在内存里,避免全局污染
闭包需注意点
闭包使用的变量会存储在内存中,内存消耗大,过度使用会产生性能问题,在IE中可能会有内存泄漏的问题,解决办法是在退出函数前,将不适用的局部变量全部删除,设置为null
当把闭包的父函数作为object并new出来后,把闭包作为公用方法变量作为私有属性,切记不要改写内部变量的值