var、let、const与闭包、垃圾回收

前言

这期简单讲一下 var、let、const,由于内容很少,多加了一个闭包,与他们之间的一些故事,也更方便我们理解他们,在实战中尽量避免一些错误

var、let、const

var:

这个声明原意是 variable 可变的意思,也就是我们声明一个变量是用到的,后续我们可以用来修改变量内容

但 var 出现了变量提升,也就是 var 声明的变量会提升到整个函数作用域、一些场景会成为全局作用域,因此会出现一些副作用

副作用就是作用域范围内,声明位置之前,声明之后,不同块都会使用到同一个对象,尤其是在循环、闭包使用过程中,会出现与预期不相符的情况,这也算是不小的问题了

ps:但其设计时不知道是为了解决一些特殊场景,还是设计缺陷(其中有人说是为了一些场景下的通信),但这类情况不应该出现,因为其他编程语言设计时,根本没有按照这个特性设计,仍然可以轻松代替 var 变量提升带来的效果,且没有那么多弊端,更容易带来预期相符的效果,因此后面也出现了 let

let

let 的出现绝非偶然,个人感觉其完全可以代替 var 出现的,但是由于遗留代码需要兼容,不能大刀阔斧的改动,因此出现了let

let 也是声明一个变量的意思,与 var 有所不同:

  • let 舍弃了 var 带来的变量提升效果
  • 其作用域也是在自己的块作用域,且声明前的位置会出现暂时性死区,提前访问会报错,
  • 在自己作用域,自己的优先级最高,作用域外的同名变量优先级会小一些
  • 符合声明方可用的情况,也达到了早期其他编程语言声明变量的效果

ps: 个人感觉唯一的缺陷是没有代替 var,var 的语义就很明确,相比较之下 swift看着更好一些,但早期 swift的强硬改动,使使用者项目大范围报错这种情况也是一个痛,但后续编程语言更加规范健壮(但swift没js开发应用范围广,不能算swift不行😂),两者都算是福祸相依

const

const 其相比较 let,是一个常量,也就是声明后,其指向的内容指针不可再更改了的意思,如果强行更改会直接提示错误

当然 const 也只是约束到了其指向的内容地址不被更换,但如果是一个对象,对象内部修改则就没有办法了,这点是需要注意的

闭包

闭包(Closure)是编程语言中的一个重要概念,是指一个函数及其相关的引用环境组合而成的实体,能够访问其外部作用域的变量,即使在外部函数已经返回的情况下。

‌闭包的关键特性‌

  • ‌访问外部变量‌:闭包允许内部函数访问其外部函数的局部变量,即使外部函数已结束执行。‌‌
  • ‌封装与持久化‌:通过闭包,可以封装私有变量或实现数据的持久化存储,避免全局变量的污染。‌‌
  • ‌作用域链‌:闭包通过作用域链机制,使得函数能够"记住"其定义时的环境状态。‌‌

通过上面定义和概念,我们大概了解了,闭包实际上就是一个内部函数引用了外部作用域的变量,使用时他们整体形成了一个封闭的场景,这也是为了保证内部函数在执行时,仍然能够正常执行里面的内容

假设不形成闭包

疑问:有人可能会问,即使这样,为何要打包环境,内部函数的参数正常访问参数也行吧?

  1. 那样问题就来了,js垃圾回收机制会直接回收用不到的对象
  2. 当一个函数执行完毕后,栈内的临时变量就成了无主之物,会被 js垃圾回收机制回收,当我们内部函数返回给外部使用的时候,此时内部函数场景执行完毕,其临时变量都都可能会被回收
  3. 外部执行到返回的内部函数时,此时内部临时变量指向的其场景内临时变量,全部变成了 undefined 未定义了(其他语言可能直接崩溃了),因为其会成为危险的野指针
  4. 正是因为此特性,需要形成闭包,他会把内部函数使用到的变量全部打包到一起(临时变量也会因为被持有无法释放),因此形成一个新的结构,这个结构就是闭包
js 复制代码
function getName() {
    let name = 'dog'
    return () => {
        console.log(name)
    }
}

//为了保证 printName 正常执行,返回的内部函数会形成闭包,以保证正常执行
const printName = getName()
printName()

形成闭包后的新问题(内存泄漏)

经过场面步骤,闭包形成后,临时变量会无法被垃圾回收机制正常回收(因此会被认为出现了内存泄漏),如果想提前回收的话,那么可以和返回的内部函数一样,多返回一个清理临时变量的一个方法,这样可以提前处理掉临时变量,就可以保证其能被正常回收了

js 复制代码
function getName() {
    let name = 'dog'
    return {
        printName() {
            console.log(name)
        },
        clearName() {
            name = null
        }
    }
}

//为了保证 printName 正常执行,返回的内部函数会形成闭包,以保证正常执行
const printObj = getName()
printObj.printName()
printObj.clearName()

内存泄露与垃圾回收

当然上面场景不是说真的内存泄露了,是否内存泄露要从所在的场景角度看,不是说使用了闭包就会产生内存泄露

  • 如果使用的闭包场景是在一个全局区内执行,那么如果不主动清理,里面的 name 垃圾回收是真的不会回收
  • 如果是在一个页面,此时该页面引用了此闭包的话,那么随着页面的释放,里面的闭包也会随之释放
  • 如果闭包是指一个函数内执行,那么这个函数执行完毕后,其也会被随之释放,因为没人持有闭包了

垃圾回收是回收没有被根节点直接或者间接持有的对象,这点需要注意,一旦切断联系则成了无根之萍,就是垃圾回收机制回收的对象

所以也不要别人忽悠,闭包该怎么使用就怎么使用,不需要提前释放,那就不需要管(当然,如果临时变量太多,等不到页面销毁,那么就需要手动切断联系了)

闭包与临时变量的关系

前面说了,闭包是内部函数引用了临时变量形成的结构,那么闭包是怎么持有临时变量的呢,实际上就是持有了临时变量的引用,注意不是直接深拷贝了该对象,否则后续的案例则会陷入泥潭

闭包与 let、var 循环的一个案例

可以分别看下面的三个循环执行情况,这也是对闭包引用临时变量的一个考验

1.正常使用 let 声明 index

js 复制代码
for (let index = 0; index < 5; index++) {
    setTimeout(() => {
        console.log(index)
    }, 1000);
}

2.使用 var 声明 index

js 复制代码
for (var index = 0; index < 5; index++) {
    setTimeout(() => {
        console.log(index)
    }, 1000);
}

3.使用 let 在外部声明 index

js 复制代码
let index = 0
for (; index < 5; index++) {
    setTimeout(() => {
        console.log(index)
    }, 1000);
}

答案如下:

  1. 1s 之后一口气执行5个 setTimeout的回调,换行打印 1 2 3 4 5
  2. 1s 之后一口气执行5个 setTimeout的回调,换行打印 5 5 5 5 5
  3. 1s 之后一口气执行5个 setTimeout的回调,换行打印 5 5 5 5 5

上面就是考察了闭包持有的临时变量引用,以及for声明的变量所在作用域

1.for中使用 let 声明的变量,使用时相当于每个index变量都是在{}单独的一个作用域,并不是同一个引用,所以每个闭包引用的都是不同的 index

2.var 声明的变量获得变量提升,里面所有的 index使用的都是同一个引用index,所以当闭包执行时会显示同一个值

3.let 放到 for 外面,里面所有的 index 使用的都是外部同一个引用index,闭包引用的自然是同一个 index,所以当闭包执行时会显示同一个值

最后

这里除了了解 var、let、const,还了解了一些垃圾回收、闭包,与for 循环下的闭包延迟执行的表现,加深一下理解

  • 同时也能理解到 for 循环早期使用 var 出现的变量提升,在闭包延迟执行的情况下与预期不符的情况
  • 同时也了解到 es6 引出的 let,能让 for循环在每个新的作用域中声明一个新的索引,避免循环中使用同一个索引,出现的与预期不符的情况
相关推荐
Entropy-Lee22 分钟前
JavaScript 语句和函数
开发语言·前端·javascript
Wcowin1 小时前
MkDocs文档日期插件【推荐】
前端·mkdocs
xw52 小时前
免费的个人网站托管-Cloudflare
服务器·前端
网安Ruler2 小时前
Web开发-PHP应用&Cookie脆弱&Session固定&Token唯一&身份验证&数据库通讯
前端·数据库·网络安全·php·渗透·红队
!win !2 小时前
免费的个人网站托管-Cloudflare
服务器·前端·开发工具
饺子不放糖2 小时前
基于BroadcastChannel的前端多标签页同步方案:让用户体验更一致
前端
饺子不放糖2 小时前
前端性能优化实战:从页面加载到交互响应的全链路优化
前端
Jackson__2 小时前
使用 ICE PKG 开发并发布支持多场景引用的 NPM 包
前端
饺子不放糖2 小时前
前端错误监控与异常处理:构建健壮的Web应用
前端
cos2 小时前
FE Bits 前端周周谈 Vol.1|Hello World、TanStack DB 首个 Beta 版发布
前端·javascript·css