1.变量作用域
变量根据作用域的不同分为两种:全局变量和局部变量。
- 函数内部可以使用全局变量。
- 函数外部不可以使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会销毁。
2. 闭包
- 什么是闭包?
能够访问其他函数内部变量的函数
- 闭包解决了什么问题
由于变量的作用域的原因-----(函数内部能读取全局变量,函数外部无法读取函数内部的变量【局部变量】),为了在函数外部读取局部变量,所以就有了闭包。
- 闭包的作用
1.访问其他函数内部变量
2.保护变量不被内存回收机制回收
3.避免全局变量被污染 方便调用上下文的局部变量 加强封装性
- 闭包的缺点
闭包长期占用内存,内存消耗很大,可能导致内存泄露
<script>
function fn1(){ // fn1 就是闭包函数
var num = 10;
function fn2(){
console.log(num); // 10
}
fn2()
}
fn1();
</script>
2.1 在 chrome 中调试闭包
- 打开浏览器,按 F12 键启动 chrome 调试工具。
- 设置断点。
- 找到 Scope 选项(Scope 作用域的意思)。
- 当我们重新刷新页面,会进入断点调试,Scope 里面会有两个参数(global 全局作用域、local 局部作用域)。
- 当执行到 fn2() 时,Scope 里面会多一个 Closure 参数 ,这就表明产生了闭包。
2.2 闭包的作用 闭包作用:延伸变量的作用范围
提问:我们怎么能在 fn() 函数外面访问 fn() 中的局部变量 num 呢 ?
//我们fun这个函数作用域访问了另一个函数fn里面的局部变量num
<script>
function fn() {
var num = 10;
return function {
console.log(num); // 10
}
}
var f = fn();
f()
</script>
2.3 闭包案例
1. 循环注册点击事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-点击li输出当前li的索引号
// 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(i);
console.log(this.index);
}
}
// 2. 利用闭包的方式得到当前小li 的索引号
for (var i = 0; i < lis.length; i++) {
// 利用for循环创建了4个立即执行函数
// 立即执行函数也成为小闭包因为立即执行函数里面的任何一个函数都可以使用它的i这变量
(function(i) {
// console.log(i);
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
</script>
</body>
</html>
2. 循环中的 setTimeout()。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<ul class="nav">
<li>榴莲</li>
<li>臭豆腐</li>
<li>鲱鱼罐头</li>
<li>大猪蹄子</li>
</ul>
<script>
// 闭包应用-3秒钟之后,打印所有li元素的内容
var lis = document.querySelector('.nav').querySelectorAll('li');
for (var i = 0; i < lis.length; i++) {
(function(i) {
setTimeout(function() {
console.log(lis[i].innerHTML);
}, 3000)
})(i);
}
</script>
</body>
</html>
2.4闭包造成内存泄漏
因为闭包就是能够访问外部函数变量的一个函数,而函数是必须保存在内存中的对象,所以位于函数执行上下文中的所有变量也需要保存在内存中,这样就不会被回收,如果一旦循环引用或创建闭包,就会占据大量内存,可能会引起内存泄漏)
如何避免闭包引起的内存泄漏
1.在退出函数之前,将不使用的局部变量全部删除。可以使变量赋值为null;(示例如下)
window.onload = function(){
var el = document.getElementById("id");
el.onclick = function(){
alert(el.id);
}
}
解决方法为
window.onload = function(){
var el = document.getElementById("id");
var id = el.id; //解除循环引用
el.onclick = function(){
alert(id);
}
el = null; // 将闭包引用的外部函数中活动对象清除
}
2.通过解除对匿名函数的引用,可以将匿名函数占用的内存安全释放。
function outerFun(outerArg){
return function(){
console.log('这里是内部匿名函数')
console.log('可以访问包含函数的变量',outerArg)
}
}
var create = outerFun("hi") //创建一个函数,是outerFun中返回的匿名函数
create() // 调用函数,是调用匿名函数
create = null // 释放对匿名函数的引用
3.利用匿名函数(立即执行函数),制造私有作用域(块级作用域),这样匿名函数执行完之后可以将引用的活动对象销毁。()()立即执行函数,也称为小闭包。
// 匿名函数 模仿 块级作用域
(function(){
// 此处为块级作用域
// 这里面的变量都会在函数执行完之后释放内存
})()
比如:
function outputNumbers(count){
// 不用return function,不能引用内部的匿名函数
// 匿名函数立即执行
(function(){
for(var i = 0; i < count; i++){
console.log(i)
}
})()
console.log(i) // 出错,因为函数执行完后i变量已销毁
}
闭包总结
闭包是什么?
闭包是一个函数 (能够访问其他函数内部变量的函数)
2. 闭包的作用是什么?
延伸变量的作用范围