JS 执行机制

523 阅读4分钟

js 代码在执行时, js 引擎会先进行编译处理,并创建执行上下文;编译完成之后进入执行阶段。

变量提升

在编译阶段,会优先对变量和函数的声明进行处理,存储到执行上下文中,等待执行阶段调用。

同名的函数和变量,在声明提升时,函数声明优先于变量声明,展示为函数;在变量赋值之后,展示为变量值。

showName()

var showName = function() {
    console.log(1);
}

function showName() {
    console.log(2);
}

showName();

// 2
// 1

变量提升的问题

  1. 容易被误覆盖掉。
  2. 本应被销毁的值没有被销毁。例如 for 循环中的声明变量 i

解决方案

使用 let / const 声明


执行上下文

执行上下文是执行代码时的运行环境信息。调用一个函数时,就会进入这个函数的执行上下文,确定函数在执行期间用到的变量,this,对象以及函数等。

  • 在执行全局代码时,会编译全局代码并创建全局执行上下文。在整个生命周期内,全局执行上下文有且只有一个
  • 在执行函数时,会编译函数并创建函数执行上下文。::函数只有被调用的时候才会被编译::。
  • 使用 eveal 时,也会创建执行上下文

对执行上下文的管理通过调用栈来完成。

  • 栈是一种后进先出的数据结构,在其他编程语言中,执行过程也是通过栈来管理函数间的调用关系的。
  • 在代码中可以通过 console.trace 输出当前函数的调用关系。

入栈的执行上下文超过一定数目的时候就会触发栈溢出的错误。


作用域

作用域控制着变量和函数的可见性和生命周期。ES6 的作用域分为 3 种:

  • 全局作用域
  • 函数作用域
  • 块级作用域

js 是如何做到即支持块级作用域,又支持变量提升的?

  1. 在执行上下文中,有着 变量环境 和 词法环境的区分
    1. var 声明的变量,会被存放到 变量环境 中
    2. let/const 声明的变量,会被存放到 词法环境中
  2. 词法环境中,每个块级作用域也是独立存在的。通过调用栈的方式进行管理

作用域链

词法作用域

词法作用域是静态作用域,是由代码中函数声明的位置来决定的,通过它就能够预测代码在执行过程中如何查找标识符。

作用域链是由词法作用域决定的。

闭包

根据词法作用域的规则,内部函数 getName 和 setName 总是可以访问它们的外部函数 foo 中的变量。

function foo() {
    var myName = 'b';
    let test1 = 1;
    var innerBar = {
        getName:function(){
            console.log(test1)
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName('a')
bar.getName()
console.log(bar.getName())

当调用 bar.getName 时,函数foo 已经执行结束了,但是依旧可以使用 foo 中的变量。

当通过调用外部函数返回一个内部函数后,外部函数已经执行结束,但是内部函数中引用的外部函数变量依旧保存在内存中,我们就把这些变量的集合称为闭包。

闭包的回收

  • 如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭时。
  • 如果引用闭包的函数是个局部变量,等函数销毁后,在下次 js 引擎执行垃圾回收时,判断闭包这个内容如果不再被使用,那么 js 的垃圾回收器就会回收这块内存

this

在对象内部的方法中使用自有的属性是一个很普遍的需求,这就得通过 this 机制来实现

作用域链和 this 是两种不同的机制。this 跟调用的方式有关,作用域链跟函数声明所在的位置有关

设置函数上下文中this

  1. 通过 call / bind / apply 方法设置
  2. 通过修改调用方法设置
    1. 在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window
    2. 通过对象来调用其内部的方法,该方法的执行上下文中的 this 指向对象本身。
  3. 通过构造函数中设置。

this 中的缺陷

嵌套函数中的 this 不会从外层函数中继承

    ```javascript
    var myObj = {
      name : "name", 
      showThis: function(){
        console.log(this)
        function bar(){console.log(this)}
        bar()
      }
    }
    myObj.showThis()
    ```

   1. 通过声明中间变量来保存 this 改为作用域链的方式调用外层函数中的this2. 使用箭头函数来解决这个问题, 箭头函数没有自己的执行上下文,会继承调用函数中的 this

总结

执行上下文中包含

  • 变量环境
  • 词法环境
  • 作用域链
  • this

这些都是在编译阶段完成后就确认的了。