前言:闭包(Closure)是JavaScript 的一个难点,在面试中经常被问到,而且很多高级应用都要依靠它,所以我想整理一篇关于”闭包“的学习笔记,希望对掘友们起到帮助。
要理解闭包,首先要理解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),这时一定要小心,不要随便改变父函数内部变量的值。
123
345
如果大家想学习前端方面的技术,我把我多年的经验分享给大家,还有一些学习资料,分享Q群:1046097531