程序随笔

JS引擎运行机制之环境

2019-07-02  本文已影响0人  76053c611046

概念

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的执行环境
概念:函数调用栈

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

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

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

function foo () { 
    function bar () {        
      return 'I am bar';
    }
    return bar();
}
foo();
函数调用栈
1.创建执行上下文

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

1.1 创建变量对象

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)函数天生的。原型链对象在作用域链中没有找到变量对时,那么就会通过原型链来查找。

执行上下文的数量限制

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

总结

引用链接:

上一篇 下一篇

猜你喜欢

热点阅读