JS引擎动态解析JavaScript的过程
1.语法检查阶段
①词法分析
②语法分析
2.运行阶段
①预解析阶段
②执行阶段
本文重点总结运行阶段JS引擎的工作机制
一、预解析阶段
首先创建执行上下文或者说创建执行上下文环境。创建的执行上下文包括:变量对象、作用域链、this三大部分
1.变量对象VO(variable object):包括变量声明、函数声明、arguments。下文中活动对象AO(action)也是变量对象的一种,只是相对于函数而言的,就是说调用函数之后,函数自己的变量对象就为活动对象。
2.作用域链(scope chain):变量对象以及所有父级作用域
3.this值:this值在进入上下文阶段就确定了,一旦进入执行阶段,this值就不会变了
变量对象 | |
作用域链 | |
this值 |
之后会对变量对象VO/活动对象AO的一些属性填充值。此时上下文环境如下
属性 | 属性的值 | |
形参 | 实参的值,未传递实参则为undefined | |
变量对象 | 函数声明 | 该函数的值 |
变量声明 | undifined,注意!函数表达式在这里也理解为变量声明 | |
作用域链 | ||
this值 |
以上所说的创建的执行上下文环境,是一个总结性的表述。
具体的是,JavaScript在执行一个代码段之前,会创建执行上下文。其中,这个代码段还分三种情况:全局代码、函数体、eval代码。其中eval代码不常用而且用后会有不必要的风险,本文不赘述。
对于全局代码,也就是说在全局环境下的代码,执行上下文环境中有
变量声明、函数表达式 | undefined |
函数声明 | 确定的值 |
this | window |
对于函数体,也就是说在函数中,在创建的执行上下文环境中,除了以上数据(不包括this),以上数据由函数的[[scope]]属性复制而来,还会有参数、arguments等数据,
[[scope]] | 除了this |
this | 确定的值 |
形参 | 确定的值 |
arguments | 确定的值 |
在函数中,作用域链是个指针,通过[[scope]]属性指向全局对象,由另一个链条指向内部变量,也就是下文说的活动对象
二、执行代码阶段
术语:在这里,为了区分,我们把函数内部定义的变量群体称为活动对象,把函数外部定义的变量群体称为变量对象。在上面我们把所有变量群体都称为变量对象
在进入执行代码阶段,预解析阶段所创建的三大部分的内容都可能会改变。当JavaScript引擎一行一行的执行代码时,会把遇到的三大部分的值重新赋予执行上下文环境,之前的值就会被覆盖
变量对象
对变量对象而言,假如变量是定义在函数内部的,而函数没有被调用,那他的值永远都是undefined
而函数被调用的情况下,这时候就会产生一个新的执行上下文环境,也产生了相应的作用域链,随之也有了执行上下文栈的概念。此时JS引擎会把产生的新的执行上下文环境压到之前的全局代码(相对而言,可以理解为父代码)所创建的执行上下文环境之上,像栈一样,当函数调用完成后,这个上下文环境便会消除,它里面的活动对象也被消除,此时全局(父)的执行上下文环境便又登上舞台。处于活动状态的执行上下文环境只能有一个,即栈最上方的一个。
作用域链
我们知道,在JavaScript中只有函数才能规定作用域。每个函数,在创建的时候(注意是创建而非执行),都有一个[[scope]]属性(一个指针),它包含了该函数作用域上层的所有变量对象,在执行阶段调用函数的时候,进入新的执行上下文环境,通过复制函数的[[scope]]属性中的对象,在加上函数本身的活动对象,便构建起执行环境的作用域链,该函数要访问栈下面他的父作用域的变量对象,就要通过这个[[scope]]属性。
补充一点,
在上面我们说到,每个函数在创建的时候,都有一个[[scope]]属性(一个指针),所以虽然是在调用阶段才复制[[scope]]属性创建作用域链,但其实作用域链中的变量对象在创建的时候就已经被决定了(调用的时候决定的是变量对象的值),也就是说,函数的作用域,是由创建时候的位置决定的,而不是由调用时候的位置决定的
具体代码如下
var a = 100; function f1() { //他的父作用域为全局,无论在哪里被调用 console.log(a); } function f2() { var a = 200; f1(); //调用 } f2(); //输出100,创建的位置决定了他的父作用域为全局,所以输出100
this值
重点谈下this值在不同环境下代表的对象
1.构造函数
如果把函数作为构造函数使用,那么构造函数中的this就代表他new出来的对象
2.函数作为对象的一个属性
那么函数中的this就指向该对象,这是比较常见的
3.函数用call、apply调用
那么函数中的this就指向传入这两种方法参数中的对象
4.普通函数
在全局环境下,this就指向window
5.DOM事件
在DOM事件中,this值就指向触发事件的DOM对象
以上只是最普通的JS引擎在运行阶段的解析机制,对于闭包,这些方面又有不同。
在闭包中,变量对象部分,还存在着对全局变量(严格来说是父级变量)的引用,而这些引用是通过作用域链存在的,所以在闭包中的函数,哪怕父级函数在执行完毕后,其活动对象也不会销毁,作用域链也依然存在,但是只有被闭包引用的活动对象不被销毁,其余的都会被内存回收。关于this的值,在闭包中,当函数作为对象的方法被调用时,若函数中有匿名函数,由于匿名函数的执行环境具有全局性,所以其this对象通常指向window。