美文网首页
js变量提升原理

js变量提升原理

作者: 剑老师 | 来源:发表于2023-05-10 16:59 被阅读0次

前端的小伙伴大概都知道,js中的var变量存在变量提升,在es6以后随着let变量的出现,变量提升问题得以解决。那么变量提升的原理是什么?es6又是怎么解决变量提升问题的?下面我们来共同探寻答案:

我们首先来了解几个概念,执行上下文、变量环境、词法环境。(本文不涉及闭包、this指向等问题)

执行上下文

当一段js代码被执行时,js引擎会先对其进行编译,并创建执行上下文。执行上下文分为三种:全局执行上下文、函数执行上下文、eval执行上下文

  1. 全局执行上下文
    js执行全局代码时,js引擎会创建一个全局的执行上下文,全局执行上下文在页面的生命周期内只有一份。即每个js文件,只有一个全局上下文。

  2. 函数执行上下文
    当执行一个js函数时,js引擎会创建一个函数执行上下文,当函数执行结束之后,函数的执行上下文会被销毁。一个函数被多次调用,会创建多个执行上下文。

  3. eval执行上下文
    使用eval函数执行一段js代码时,会创建一个eval的执行上下文。

js文件执行时,首先会创建全局执行上下文,并压入调用栈,当调用js函数时,会创建函数执行上下文,并压入调用栈。当函数执行完之后,函数执行上下文便会从栈中移出。如以下代码的执行:

var a = "123"
function func1() {
    var b = "123"
    console.log(b)
    func2()
}
funcgion func2() {
    const c = "456"
    console.log(c)
}
func1()
执行上下文

执行上下文中其实还包含了另外两个对象,一个变量环境对象和一个词法环境对象。那么接下来我们来看一下什么是变量环境和词法环境

变量环境

变量环境存在于执行上下文中,其本质是一个对象,变量环境中存储的是此作用域内定义的变量、函数信息等信息。如全局执行上下文中的变量环境存储的是全局的变量和函数信息。函数执行上下文中的变量环境则存放的是函数的参数、局部变量等信息。

其实,js的代码在执行前还有一个编译的过程,在编译过程中,var变量和function函数部分会被js引擎放入到变量环境中,并且变量会被默认设置为undefined。在执行阶段,js引擎会在变量环境中查找声明的变量和函数。这就是我们所说的“变量提升”,这也是为什么函数可以在函数的实现之前调用。

例:

console.log(a)
var a = "123"
function func1() {
    console.log(a)
}
func1()

以上代码的执行顺序是:

  • js引擎先进行编译,并把a变量和func1放入到变量环境中,并把a变量设置为undefined

  • 进入执行阶段,执行第一行代码console.log(a),此时从变量环境中取出a的值为undefined,所以打印结果为undefined

  • 执行第二行代码var a = "123",将变量环境中的a变量赋值为字符串123

  • 执行最后一行代码func1(),js引擎从变量环境中找出对应的func1,并执行里面的代码console.log(a),打印结果为123

所以以上代码输出结果为

undefined
123

虽然在a声明之前打印a变量,但是却并没有报错。

词法环境

ES6之前,js中只支持全局作用域和函数作用域,并不支持块级作用域。ES6之后,js引入了letconst关键字,从而解决了变量提升问题并使js支持了块级作用域。

其实说letconst没有变量提升并不准确,当js代码被编译时,letconst变量代码会被存放在词法环境中。此时letconst变量已经被提升了,但是只是创建被提升,初始化和赋值并没有被提升,如果在赋值之前去读写该变量,便会报错,这就是我们所说的“暂时性死区”。

那实现块级作用域的原理是什么呢?其实在词法环境中,维护了一个作用域栈,栈底是函数的最外层变量(letconst声明的变量),进入一个作用域块后,就会把该作用域中的变量入栈;当作用域中的代码执行完成之后,该作用域的信息就会从栈顶弹出。
我们举个以下例子来说明

例:

function fun()
{
    let a = 1
    {
        let a = 2
        let b = 3
        console.log(a)
        console.log(b)
    }
    console.log(a)
    console.log(b)
}
fun()

如图:


fun函数执行状态
  1. fun函数被编译时,外层的a变量首先被创建,并存放至词法环境作用域栈中,此时函数内部的块级作用域中的变量不会被创建。
  2. 当函数执行至作用域块时,let alet b也被创建并入栈存放至栈顶。并将a赋值为2,将赋值为3
  3. 当执行至console.log(a)console.log(b)时,js引擎首先从栈顶找到ab的值并打印出23
  4. 当作用域块执行完成之后,作用域块中的变量信息从栈中弹出。
  5. 接着执行console.log(a)找到的是栈底的a变量,并打印出1。接着执行console.log(b),由于在词法环境和变量环境中都找不到b变量,所以便会报错b is not defined

如果同一个函数中不同作用域存在相同的变量(如上面例子的a),那么变量的查找顺序是怎样的呢?

  1. 首先在词法环境作用域栈的栈顶的变量信息中开始查找
  2. 如果找到该变量,则直接返回该变量在此作用域块中的值,如果没有找到则从栈顶往下依次查找。
  3. 如果从词法环境中的栈顶到栈底都没有找到,则从变量环境中查找。

总结:

讲到这里,我想应该可以回答一下文章开始所提的两个问题:

变量提升的原理是什么?
在js代码编译阶段,var变量和function函数会被js引擎放入到变量环境中,并且var变量会被默认设置为undefined。需要注意的是,var变量只有创建和初始化被提升,赋值并没有被提升;而function的创建、初始化和赋值均会被提升。所以在变量的声明之前,该变量的值是undefined,而函数则可以在声明之前正常调用。

letconst是怎么解决变量提升问题的?
在js代码编译阶段,letconst变量会被js引擎放入到词法环境中。与var一样,letconst变量也被提升了。但只是创建被提升,变量的初始化和赋值并未被提升,如果在赋值之前读写该变量,就会形成暂时性死区。

相关文章

  • 浏览器学习笔记-JS执行

    变量提升 变量提升原理浏览器对js是先编译后执行,在编译过程中,js中的变量声明会被提升到代码段落前面。函数声明和...

  • js 变量提升的原理

    众所周知,JavaScript 中存在变量提升的问题,在 ES6 引入的新关键词 let 可以很方便的解决这个问题...

  • 深入理解JavaScript之变量提升

    变量提升 原理:JS引擎的工作方式是先解析代码,获取所有被声明的变量;然后在运行。JS代码自上而下执行之前,浏览器...

  • 前端知识点收集

    JS原理 变量提升与函数提升/是什么,为什么,怎么办this的指向以及当return了一个对象之后跨域:JSONP...

  • JS中的提升

    JS中包含两种提升,变量提升和函数提升。 变量提升 变量提升只能是var或者function声明的变量或者函数,l...

  • 大前端—面试宝典—第一篇

    1. var的变量提升的底层原理是什么? JS引擎的工作方式是: 1) 先解析代码,获取所有被声明的变量; 2)然...

  • js底层原理一(变量提升、函数提升)

    前言: js是解释性语言,运行时才能解析出代码对错。 为什么可以变量函数提升? js代码是自上而下执行,但是在js...

  • 01-撩课大前端—面试宝典—第一篇

    1. var的变量提升的底层原理是什么? 2. JS如何计算浏览器的渲染时间? 3. JS的回收机制? 4. 垂直...

  • 撩课-Web大前端每天5道面试题-Day1

    1. var的变量提升的底层原理是什么? 2. JS如何计算浏览器的渲染时间? 3. JS的回收机制? 4. 垂直...

  • JavaScript 大纲

    js 语法基础 JavaScript 介绍 js 输出 注释 变量,变量声明提升,全局变量,常量 7 大数据类型 ...

网友评论

      本文标题:js变量提升原理

      本文链接:https://www.haomeiwen.com/subject/cafjsdtx.html