前言
闭包已经是老生常谈了,面试中也经常遇到,一个JS基础扎实的前端必须掌握闭包
记录前端闭包的学习
学习已完成
- 1.作用域
- 2.作用域本质
- 3.闭包
1.作用域
作用域就是变量的可用范围,作用域的目的是防止不同范围的变量之间相互干扰
全局作用域与局部作用域
var a = 1; // 全局作用域 全局变量
function fn(){
var b = 2; // 局部作用域 局部变量
}
1)全局作用域
全局作用域可反复使用保存在 window,但会全局污染
2)局部作用域
局部变量不会被污染,但却无法反复使用
函数作用域:保存在函数内的范围成为函数作用域,里面的变量称为局部变量
局部变量有两种
- 直接在
function
的{}
内定义声明的 - 形参变量
function fn(n) {
n = 1 // 局部变量
var a = 2; // 局部变量
}
只有function的{}形成作用域才有局部变量,对象的{}与对象中的属性 均不是局部变量,都阻挡不了内部的变量超出{}的范围
// 局部变量 a/n
function fn(n) {
n = 1 // 局部变量
var a = 1; // 局部变量
}
fn()
console.log('a: ', a); // a is not defined 变量c只存在于局部,所以error
// 非局部变量 b/c/d
{
var b = 2
}
console.log("b", b); // b=2 说明{} 并不能锁住局部作用域
if (false) {
var c = 3
}
console.log('c: ', c); // c=undefined 因为c被提到全局了,在全局没有赋值,所以c为undefined
var arr = [1]
for (var i = 0; i < arr.length; i++) {
var d = 5
}
console.log("d", d) // d=5 说明for循环{} 并不能锁住局部作用域,其他循环亦是如此
2.作用域本质
全局作用域是一个名为window的对象,所有全局变量和全局函数都是window对象的成员
函数作用域其实是JS引擎在调用函数时才临时创建的一个作用域对象。其中保存函数的局部变量。当函数调用完,函数作用域对象就会被释放了。
JS中函数作用域对象,还有个别名——活动的对象(Actived Object)简称AO。所以,局部变量不可重用。
3.闭包
闭包一种既重用变量又保护变量不被污染的一种编程方法。
使用场景:希望保存一个可反复使用又不被外界污染的专属局部变量时,用闭包 闭包有3个特性:
①函数嵌套函数
②函数内部可以引用函数外部的参数和变量
③参数和变量不会被垃圾回收机制回收
1)使用闭包的步骤举例
// 写一个花钱的函数
// 第1步: 用外层函数包裹要保护的变量和内层函数
function bag() {
var total = 1000;
//第2步: 返回内层函数对象
return function(money) {
total -= money
console.log(`花了${money}还剩${total}元`)
}
}
//第3步: 调用外层函数,用变量接住内层函数对象
var pay = bag(); // pay接住的即是bag()返回出来的内层函数对象
pay(100); // 900
total = 0; // 无法修改局部变量
pay(100); // 800
2)本质
闭包的本质就是外层函数调用后,外层函数的作用域对象,被返回的内层函数的作用域链引用着无法释放就形成了闭包对象
作用域链:每个函数在定义时,就已经规划好了自己专属的一个查找变量的路线图,称为作用域链。函数会从自身的局部作用域一直往上走查找要用的变量,一直到全局作用域,一个函数可用的所有作用域串联起来,就行成了当前函数的作用域链。
步骤1)中的例子就形成了作用域链,分别为内层函数自己的作用域对象 -> 外层函数的作用域对象 -> 全局作用域对象
3)再举例定时器问题
for(var i = 0; i < 3; i++) {
(function(a){
setTimeout(() => {
console.log(a)
}, a * 1000)
})(i)
}
使用 IIFE(立即执行函数)每一轮循环生成一个新的函数作用域,令setTimeout回调可以将新的作用域封闭在每一轮循环内部,每一轮循环内部都会保存一个外部的i变量进行访问。
整个立即执行函数就是闭包,立即执行函数执行结束时,变量a被定时器setTimeout一直引用着无法释放。
4)再举例过滤一个数组
// 过滤一个数组取得 5-10之间的数字
var arr = [1, 5, 10, 15, 20]
// 直接写法
var arrNew = arr.filter(function (z) {
return 5 <= z && 10 >= z
})
console.log('arrNew: ', arrNew);
// 用闭包封装复用
function between(x, y) { // 外层函数的作用域对象里的x,y不断的被内层函数的作用域链引用着
return function (z) {
return x <= z && y >= z // 内层函数作用域引用着外层作用域的值x,y
}
}
console.log(arr.filter(between(5, 10))) // [5, 10]
5)闭包的缺点
无法回收闭包中引用变量,容易造成内存泄漏,所以要即时释放不用的闭包
总结
两句话概括闭包:
- 有权访问另一个函数作用域中变量的函数。
- 外层函数调用后,外层函数的作用域对象,被返回的内层函数的作用域链引用着无法释放就形成了闭包对象。
最后
以上的方式总结只是自己学习总结,有其他方式欢迎各位大佬评论
渣渣一个,欢迎各路大神多多指正,不求赞,只求监督指正( ̄. ̄)
有关该文章经常被面试问到的可以帮忙留下言,小弟也能补充完善完善一起交流学习,感谢各位大佬(~ ̄▽ ̄)~