函数的执行环境和变量的作用域
要理解闭包,首先要理解JavaScript函数的执行环境
和变量的作用域
。
执行环境(Execution Context,也称为"执行上下文")是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其它数据,决定了各自的行为。当JavaScript代码执行的时候,会进入不同的执行环境,这些不同的执行环境就构成了执行环境栈。
let name = "qianshu";
function foo() {
let year = 2020;
function bar() {
let month = 12;
console.log(month); //这里能读取到外部变量name、year
}
bar();
}
foo();
变量的作用域分为两种:全局变量和局部变量。
let name = 'qianshu'; //全局变量
function foo(){
let year =2020; //局部变量
}
Foo();
console.log(year) //ReferenceError: year is not defined. 外部函数不能读取到内部变量
什么是闭包
在红宝书中,闭包是这样定义的。闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
首先我们先来看一段代码。
function outer() {
let year =2020
function insider() {
console.log(`欢迎来到${year}年`);
};
return insider;
};
let a = outer()
a() //欢迎来到2020年
上面这段代码inside( )函数就是一个简单的闭包函数。为了获取到内部数据(变量/函数),inside( )函数作为一个返回值,让outer( )函数读取到它内部变量。
那么,我的理解是,闭包就是能够读取其他函数内部变量的函数。A( insider )函数嵌套在B( outer )函数内,B函数使用了A函数的内部变量,且A函数返回B函数,这就是闭包。
要使用闭包,只需要简单地将一个函数定义在另一个函数内部,并将它暴露
出来。要暴露一个函数,可以将它返回或者传给其他函数。
内部函数将能够访问到外部函数作用域中的变量,即使外部函数已经执行完毕。
闭包的作用
闭包的作用
-
使用函数内部的变量在函数执行完后,仍然存活在内存中,延伸了函数环境生命周期
父级变量被子函数调用,则父级作用域中的变量都会被保存在内存中
-
让函数外部可以操作到函数内部的数据
//延伸函数环境的生命周期例子
function foo() {
let n = 1;
return function sum() {
console.log(++n);
};
}
let a = foo();
a(); // 2
a(); //3
//没有闭包的例子
function hd() {
let n = 1;
return function sum() {
console.log(++n);
};
}
hd()(); //2
hd()(); //2
//让函数外部可以操作到函数内部的数据的例子
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗
闭包常见应用场景
获取区间数值
let arr = [1, 2, 3, 15, 54, 31, 65, 10];
function between(a, b) {
return function (v) {
return v >= a && v <= b;
};
}
let data = arr.filter(between(2, 20));
console.log(data); //[2,3,15,10]
防抖
事件相应函数在一段时间后才执行,如果在这段时间内再次调用,则重新计算执行时间;当预定时间内没有再次调用,则执行doSomeThing。(现实生活中的例子:公交车司机开门让乘客上车,要等所有乘客都上车后才关门,而不是每上一位乘客执行一次开关门的操作)
function debounce(func, wait, immedicate) {
let timeoutm,result;
return function () {
//改变执行函数内部的this的指向
let context = this;
//event 指向问题
let args = arguments;
if (timeout) clearTimeout(timeout);
if (immedicate) {
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
//立即执行
if (callNow) result = func.apply(context, args);
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
return result;
};
}
//测试代码
let count = 0;
let container = document.querySelector("#container");
function doSomeThing(e) {
container.innerHTML = count++;
}
container.onmousemove = debounce(doSomeThing, 300, true);
节流
节流,就是指连续触发事件但是在 n 秒中只执行一次函数。
function throttle(func, wait) {
let context, args, timeout;
let old = 0; //时间戳
let later = function () {
old = new Date().valueOf();
timeout = null;
func.apply(context, args);
};
return function () {
context = this;
args = arguments;
//获取当前时间戳
let now = new Date().valueOf();
if (now - old > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
func.apply(context, args);
old = now;
} else if (!timeout) {
timeout = setTimeout(later, wait);
}
};
}
//测试代码
let count = 0;
let container = document.querySelector("#container");
function doSomeThing(e) {
container.innerHTML = count++;
}
container.onmousemove = throttle(doSomeThing, 300, true);
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
<body>
<div desc="foo">123</div>
<div desc="bar">345</div>
</body>
<script>
//内存泄漏
let divs = document.querySelectorAll("div");
divs.forEach(function (item) {
let desc = item.getAttribute("desc");
item.addEventListener("click", function () {
console.log(desc);
console.log(item);
});
item = null; //退出函数之前,将不使用的局部变量删除
});
</script>
参考链接
-
学习JavaScript闭包(Closure),阮一峰
-
闭包与作用域,后盾人教程
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!