Skip to main content

浏览器中JavaScript的执行机制

执行上下文

变量提升

js中,定义一个变量要分成两部分来看:变量声明和变量赋值(变量初始化)

函数声明则只有声明过程,没有赋值过程。

所谓变量提升,就是把变量和函数的声明提升至代码开头,但并不是实际的代码位置变了,而是js代码要运行需要经过编译和执行两步,变量提升发生在编译阶段,将变量和函数的声明放进了内存中。

js代码的执行

  1. 编译阶段

编译阶段生成两部分内容:执行上下文(execution context)和可执行代码

执行上下文,就是一段代码的运行环境。

编译阶段,在这段代码的执行上下文中存在一个变量环境对象,该对象中保存了变量提升的内容,即变量和函数的声明

  1. 执行阶段

js代码执行,从上向下一行一行执行,遇到变量和函数时,就去执行上下文的变量环境中查找

执行上下文分类

  • 全局执行上下文

  • 函数执行上下文

  • eval执行上下文

调用栈

调用栈,用来管理函数调用的顺序,栈中存放的就是各个执行上下文

帮助理解JS代码的执行顺序

作用域

作用域就是变量和函数的可访问范围

Q: 块级作用域中var和let、const的区别。

let,const声明的变量在编译极端,会被放入执行上下文中的词法环境中,如果有块级作用域,且块级作用域中存在let、const声明,就会把这些声明以栈的形式再放入词法环境中

代码执行时,遇到变量,就会先从当前代码的执行上下文的词法环境中,栈顶开始向下查找,词法环境查找完毕,就会找变量环境中的变量声明。

作用域链和闭包

理解作用域链,要先理解js的作用域是词法作用域,即变量定义的位置决定了它的可访问性,而不是它执行的位置。

作用域链的查找顺序就是,该变量在当前作用域中没有找到时,就是会去它定义时所有在位置的作用域中查找。

var name = 'jack'
function hello() {
  console.log(name) // name在hello函数的作用域中没找到定义,就去全局作用域中找,因为hello函数定义在全局作用域中
}
hello()

闭包,内部函数访问外部变量的一环,属于作用域的一环

闭包中变量定义时所在的函数执行完,其执行上下文就出栈了,由于有其他函数引用着这些变量,因此这边变量不会销毁,依然存在于堆中,其他函数直接从堆中引用这些变量

当内部函数执行时,内部的变量现在当前函数的作用域中查找,没有找到,就去闭包中查找,最后去全局作用域中查找

this

this和作用域链没有关系,记住这一点。作用域链是静态的,在编译过程中就确定了。this是动态的,执行过程中才确定,跟函数调用的方式或位置有关。

this也存在与执行上下文中,即代码执行时,遇到this,也要去执行上下文中找this,看只想哪里

使用call,apply,bind方法改变this指向

通过对象调用函数,可以改变this指向

通过构造函数改变this执行,这时this指向的时生成的实例对象。

箭头函数中的this会继承父函数的this,并不会改变this的指向。箭头函数没有自己的执行上下文

扩展: AST抽象语法树