理解作用域链是理解闭包的基础:js_作用域链
闭包的定义?
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
什么是词法作用域?
词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能够预测代码在执行过程中如何查找标识符。
那这些闭包是如何使用的呢?
当执行到 bar.setName 方法中的myName = "极客邦"这句代码时,JavaScript 引擎会沿着“当前执行上下文–>foo 函数闭包–> 全局执行上下文”的顺序来查找 myName 变量,你可以参考下面的调用栈状态图:
如何产生闭包?
当函数嵌套时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局作用域下可访问时,就形成了闭包。
产生闭包的条件?函数嵌套且内部函数引用了外部函数的数据(变量/函数),执行内部函数定义时就会产生闭包(不用调用内部函数,但是外部函数必须调用才会产生闭包)。
思考题:
下面代码有闭包吗?
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = "极客时间"
return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()
答案是没有闭包产生且两次打印都为‘极客邦’,详见:极客时间——作用域链和闭包
常见的闭包:
1,将函数作为另一个函数的返回值(fn外面的作用域可以访问fn内部的局部变量,访问但不可以修改)将函数作为实参传递给另一个函数调用。
function fn(){
var num=10;
function fun(){
console.log(num);
}
return fun;
}
var f=fn();
//类似于f=function fun(){
// console.log(num);
//}
f();
或者直接返回一个匿名函数(高阶函数)
function fn(){
var num=10;
return function(){//匿名函数
console.log(num);
}
}
var f=fn();
f();
闭包的应用场景:
1>循环注册点击事件。
点击li输出当前li的索引号
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
//1,利用动态添加属性的方式
var lis=document.querySelector('.nav').querySelectorAll('li');
for(var i=0;i<lis.length;i++){
lis[i].index=i;
lis[i].onclick=function(){
console.log(this.index);
}
}
//利用闭包的方式得到当前小li的索引号
for(var i=0;i<lis.length;i++){
(function(i){
lis[i].onclick=function(){
console.log(i);
}
})(i);
}
</script>
</body>
2>循环中的setTimeout()
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
//闭包应用:3s之后,打印所有li元素的内容
var lis =document.querySelector('.nav').querySelectorAll('li');
for(var i=0;i<lis.length;i++){
(function(j){
setTimeout(function(){
console.log(lis[j].innerHTML);
},3000)
})(i);
}
</script>
</body>
闭包的优缺点:
优点:延申了变量的作用范围,(以前,函数内的局部变量仅限于本函数内使用,以外不能使用,但是闭包可以实现在函数作用域外访问函数内部的局部变量。num这种局部变量在函数执行完就会销毁,但是有了闭包以后,函数执行完不会立即销毁,下面还有函数调用要访问它)
缺点:函数执行完后,函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。