浏览器中的JavaScript执行机制

167 阅读5分钟

变量提升:JavaScript是按照顺序执行的吗?

image.png

观察代码,并看到他的输出结果,我们发现:

  1. 在变量定义前使用,并没有报错,且值为undefined,不是定义的值;
  2. 在函数定义前使用,不会报错,且函数正常执行;

变量提升

所谓变量提升,是指在JavaScript代码执行过程中,JavaScript引擎把变量的声明部分和函数的声明部分提升到代码“开头”的行为。变量被提升后,会给变量设置默认值undefined。

JavaScript代码的执行流程

JavaScript是先编译,再执行的

实际上,变量和函数声明在代码的位置是不会变的,它是在编译阶段被JavaScript引擎放入内存中。在编译阶段,可以分为两部分:变量提升部分代码、执行部分代码

image.png

输入一段代码,经过编译后,会生成两部分内容:执行上下文和可执行代码。

执行上下文是JavaScript执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如this、变量、对象以及函数。

JavaScript引擎到了“可执行代码”阶段,就会按照顺序一行一行地执行。

当代码中出现相同的变量或函数时,在编译阶段,第二个函数会覆盖掉第一个,然后再执行阶段就会找到这个函数并执行,这个时候用的就是第二个函数。综上,一段代码如果定义了两个相同名字的函数,最终生效的是最后一个函数。

总结:

  1. Javascript代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为Javascript代码在执行之前需要先编译
  2. 在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为undefined;在代码执行阶段, Javascript引擎会从变量环境中去查找自定义的变量和函数。
  3. 如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。

调用栈:为什么JavaScript代码会出现栈溢出?

调用栈是用来管理函数调用关系的一种数据结构。如图,调用栈:

image.png

调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript引擎就会报错,这种错误就是栈溢出。常见于写递归的时候。

块级作用域:为什么要引入let和const?

作用域

作用域是指在程序中定义变量的区域,该位置决定了变量的生命周期。通俗地理解,作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期。

变量提升所带来的问题

  1. 变量容易在不被觉察的情况下被覆盖掉
  2. 本应销毁的变量没有被销毁

ES6通过引入let和const关键字,使JavaScript拥有了块级作用域。作用块内声明的变量不影响块外面的变量。

作用域链和闭包:相同变量,javaScript是如何选择的?

词法作用域

词法作用域就是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态的作用域,通过它就能预测代码在执行过程中如何查找标识符。词法作用域是代码阶段就决定好的,和函数是怎么调用的没有关系。

闭包

在JavaScript中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包

  1. 如果闭包一直使用,那么它可以作为全局变量而存在;
  2. 如果使用频率不高,而且占用内存比较大,那就作为局部变量;

this:从执行上下文来理解

this是和执行上下文绑定的。

全局执行上下文中的this

全局执行上下文中的this是指向window对象。

函数执行上下文中的this

如何设置函数执行上下文中的this呢?

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

this的设计缺陷以及对应方案

  1. 嵌套函数中的this不会从外层函数中继承
    • 声明self用来保存this
    • 使用ES6中的箭头函数
  2. 普通函数中的this默认指向全局对象window