作用域:
所谓作用域是指函数能够影响的范围,通常在这个范围内存在着各种常量、变量、函数等。同时在JS中还存在着作用域链,就是在一个作用域中包含着其他作用域。
例如:
function NewFun() {
var a = 0;
console.log(a);
}
NewFun();
console.log(a);
上述代码在访问变量a时,只有在NewFun方法的作用域中打印a才能正确打印值,而在NewFun函数的作用域范围外,则会提示存在a这个变量,这就是JS的函数作用域。
输出结果:
在ES6版本之前,JS只有全局作用域和函数作用域,在ES6版本中引入了块级作用域。同时引入了两个支持块级作用域的变量类型let 和 const
注:var不支持块级作用域,只支持全局和函数作用域。
最常见的块级作用域是for循环体中的条件变量
在老版本中定义for循环如下:
for (var i = 0; i <= 5; i++) {
console.log(i);
}
console.log('外部访问:' + i);
输出结果:
我们发现在外部仍然能够访问到i的值,在大多数情况下,我们不希望在循环结束后依旧能够访问到i的值,希望它能够被系统回收,所以在ES6中利用let支持块级作用域的情况下循环可以改写为:
for (let i = 0; i <= 5; i++) {
console.log(i);
}
console.log('外部访问:' + i);
输出结果为:
我们可以发现i并未定义,所以利用块级作用域,我们可以很好的回收掉变量i。
闭包 :
所谓闭包就是指函数能够访问到外部的变量,反之则不成立,例如
let c = -1;
function NewFun() {
let b = 0;
function Fun2() {
let a = 2;
console.log(a, b, c);
}
Fun2()
}
NewFun()
Fun2函数中可以访问到NewFun中的b变量和全局中的c变量,这就是闭包。
通过对闭包和作用域的了解,我们可以推断出几个函数之间还存在着某种联系,我们称这种联系为作用域链。
我们把上述代码改写为:
let a = -1;
function NewFun() {
let a = 0;
function Fun2() {
let a = 2;
console.log(a);
}
Fun2()
}
NewFun()
此时我们可以将函数之间的关系绘制成一张图:
从全局----->NewFun---->Fun2的这条线路就是作用域链,当Fun2输出变量在自己作用域中找不到时就会顺着这条作用域链向上查找,直至找到这个输出的变量。
由闭包引发的内存泄漏问题:
在老版本中的浏览器中由于垃圾回收机制是引用计数器,使用闭包特性引用外部变量,会使得变量计数器加1,计数器不为0,所以JS垃圾回收始终不会回收掉该变量,使得无用的变量在内存中越来越多,从而引发内存泄漏。
在下列代码中:
<body>
<button>1</button>
<button>1</button>
<button>1</button>
</body>
<script>
function SetBtnClick() {
let btns = document.querySelectorAll('button');
btns.forEach(btn => {
btn.addEventListener('click', function() {
btn.innerHTML = Number(btn.innerHTML) + 1;
})
})
}
SetBtnClick()
</script>
我在设置按钮的监听事件中利用闭包特性引用了外部的btn变量获取了innerHTML的值,所以由于JS的垃圾回收机制,btn被点击时引用了,所以垃圾机制回收不掉btn这个变量,又因btn存在与SetBtnClick---->btn.forEach中的回调函数---->btn监听回调函数这条作用域链上,导致整条作用域链的计数器都不为0,没有被回收,若存在着许多这样没有被回收掉的作用域,则会照成内存拥堵,从而引发内存泄漏。
解决方法:
function SetBtnClick() {
let btns = document.querySelectorAll('button');
btns.forEach(btn => {
let this_btn = btn;
btn.addEventListener('click', function() {
this_btn.innerHTML = Number(this_btn.innerHTML) + 1;
})
btn = null;
})
}
SetBtnClick()
我们将foreach循环的btn赋值给临时变量this_btn,同时在操作按钮对象时获取对象的任何属性都从临时变量中获取,同时在btn使用完成后,手动清除btn变量,从而解决内存变量的问题。