JS引擎运行机制之环境

概念

javaScript引擎就是根据ECMAScript定义的语言标准来动态执行JavaScript字符串

对于静态语言(如:C、C++、Java),处理上述这些事情的叫编译器Compiler。对于JavaScript这样的动态语言则被称为解释器Interpreter。不同的地方在于编译器是将源代码编译为另外一种代码(比如:机器码、或者字节码),而解释器是直接解析并将代码运行结果输出。

Chrome JS引擎V8是用C++编写的,为了提高浏览器执行JavaScript的性能,V8将JavaScript转为了更高效的机器码(JIT编译器:Just-In-Time compiler),而不只是使用解释器。

过程

解析JS的过程分为两个阶段: 语法检查阶段运行阶段

语法检查包含词法分析语法分析(注:这两个阶段是编译原理,所有语言都适用。eq:在浏览器工作原理)

运行阶段包含预编译执行代码

词法分析

JavaScript解释器先把JavaScript代码(字符串)的字符流按照ECMAScript标准转换为记号流

eq:

a = (b - c);

转换为记号流

NAME "a"
EQUALS
OPEN_PARENTHESIS
 NAME "b"
MINUS 
NAME "c"
CLOSE_PARENTHESIS
SEMICOLON

语法分析

JavaScript语法分析器在经过词法分析后,将记号流按照ECMAScript标准把词法分析所产生的记号生成语法树,又被成为"AST(语法抽象树)"。

分析该js脚本代码块的语法是否正确,如果出现不正确,则向外抛出一个语法错误(SyntaxError),停止该js代码块的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入预编译阶段

预编译

预编译包含两步: 创建执行上下文属性填充

概念:JS的执行环境
  • 全局环境(JS代码加载完毕后,进入代码预编译即进入全局环境)
  • 函数环境(函数调用执行时,进入该函数环境)
  • eval环境
概念:函数调用栈

函数调用栈就是使用栈存取的方式进行管理运行环境,特点是先进后出,后进先出。

每进入一个不同的运行环境都会创建一个相应的执行上下文(Execution Context),那么在一段JS程序中一般都会创建多个执行上下文,js引擎会以栈的方式对这些执行上下文进行处理,形成函数调用栈(call stack),栈底永远是全局执行上下文(Global Execution Context),栈顶则永远是当前执行上下文。

当浏览器第一次加载你的script的时候,它默认的进了全局执行环境。如果在你的全局代码中你调用了一个函数,那么顺序流就会进入到你调用的函数当中,创建一个新的执行环境并且把这个环境添加到执行栈的顶部

function foo () { 
    function bar () {        
      return 'I am bar';
    }
    return bar();
}
foo();
JS引擎运行机制之环境_第1张图片
函数调用栈
1.创建执行上下文

JS引擎将语法检查正确后生成的语法树复制到执行上下文中

  • 创建变量对象(Variable Object)
  • 建立作用域链(Scope Chain)
  • 确定this的指向
1.1 创建变量对象
  • 创建arguments对象,检查当前上下文中的参数,建立该对象的属性与属性值,仅在函数环境(非箭头函数)中进行,全局环境没有此过程
  • 检查当前上下文的函数声明,按代码顺序查找,将找到的函数提前声明,如果当前上下文的变量对象没有该函数名属性,则在该变量对象以函数名建立一个属性,属性值则为指向该函数所在堆内存地址的引用,如果存在,则会被新的引用覆盖。
  • 检查当前上下文的变量声明,按代码顺序查找,将找到的变量提前声明,如果当前上下文的变量对象没有该变量名属性,则在该变量对象以变量名建立一个属性,属性值为undefined;如果存在,则忽略该变量声明

PS:在全局环境中,window对象就是全局执行上下文的变量对象,所有的变量和函数都是window对象的属性方法。

函数声明提前和变量声明提升是在创建变量对象中进行的,且函数声明优先级高于变量声明。

1.2 建立作用域链

作用域链由当前执行环境的变量对象(未进入执行阶段前)与父级的一系列活动对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。

1.3 确定this指向

在全局环境下,全局执行上下文中变量对象的this属性指向为window;函数环境下的this指向却较为灵活,需根据执行环境和执行方法确定,

2.属性填充: 扫描上下文中声明的函数形参、函数以及变量,并依次填充变量对象的属性

执行代码

  1. 变量对象赋值
    • 变量赋值
    • 函数表达式赋值
  2. 调用函数
  3. 顺序执行其他代码

当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

函数的作用域是在函数创建即“预编译”阶段就已经就已经定义了,而在代码执行阶段则是将函数的作用域添加到作用域链上。

var aa="123";
console.log('bb',typeof bb, bb());
function bb(){
    console.log('cc', cc);
    console.log('aa', aa);
    return 'bb';
}
var cc="cc";
console.log('bb',typeof bb,  bb());

执行过程中当前函数的AO永远是在最前面的,保存在堆栈上,而每当函数激活的时候,这些AO都会压栈到该堆栈上,查询变量是先从栈顶开始查找,也就是说作用域链的栈顶永远是当前正在执行的代码所在环境的VO/AO(当函数调用结束后,则会从栈顶移除)。

PS:JavaScript解释器通过作用域链将不同执行位置上的变量对象串连成列表,并借助这个列表帮助JavaScript解释器检索变量的值。作用域链相当于一个索引表,并通过编号来存储它们的嵌套关系。当JavaScript解释器检索变量的值,会按着这个索引编号进行快速查找,直到找到全局对象为止,如果没有找到值,则传递一个特殊的 undefined值。

原型链查询

当创建函数时,同时也会创建原型链对象(prototype)函数天生的。原型链对象在作用域链中没有找到变量对时,那么就会通过原型链来查找。

执行上下文的数量限制

执行上下文可存在多个,虽然没有明确的数量限制,但如果超出栈分配的空间,会造成堆栈溢出。常见于递归调用,没有终止条件造成死循环的场景

总结

  • JavaScript是单线程
  • 栈顶的执行上下文处于执行中,其它需要排队
  • 全局上下文只有一个处于栈底,页面关闭时出栈
  • 函数执行上下文可存在多个,但应避免递归时堆栈溢出
  • 函数调用时就会创建新的上下文,即使调用自身,也会创建不同的执行上下文

引用链接:

  • JS引擎解析过程
  • 闭包
  • 词法分析和语法分析更多扩展
  • JS执行上下文

你可能感兴趣的:(JS引擎运行机制之环境)