javascript运行机制

来源:互联网 发布:命令行启动mysql 编辑:程序博客网 时间:2024/05/26 15:54

参考文章:
http://www.ruanyifeng.com/blog/2014/10/event-loop.html
http://www.cnblogs.com/bugda/p/6036282.html

一. javascript 单线程

作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?

为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。

二 javascript的运行机制

所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

具体来说,异步执行的运行机制如下。(同步执行也是如此,因为它可以被视为没有异步任务的异步执行。)
(1)所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
(2)主线程之外,还存在一个”任务队列”(task queue)。只要异步任务有了运行结果,就在”任务队列”之中放置一个事件。
(3)一旦”执行栈”中的所有同步任务执行完毕,系统就会读取”任务队列”,看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
(4)主线程不断重复上面的第三步。

下图就是主线程和任务队列的示意图。
这里写图片描述

只要主线程空了,就会去读取”任务队列”,这就是JavaScript的运行机制。这个过程会不断重复。

除了放置异步任务的事件,”任务队列”还可以放置定时事件,即指定某些代码在多少时间之后执行。这叫做”定时器”(timer)功能,也就是定时执行的代码。
定时器功能主要由setTimeout()和setInterval()这两个函数来完成,它们的内部运行机制完全一样,区别在于前者指定的代码是一次性执行,后者则为反复执行。以下主要讨论setTimeout()。

setTimeout(function(){console.log(1);}, 0);console.log(2);

上面代码的执行结果总是2,1,因为只有在执行完第二行之后,系统才会执行“任务队列”中的回调函数。

三 javascript 执行上下文与预解析

javascript是解释型语言,它不同于c#和java等编译型语言。对于传统编译型语言来说,编译步骤分为:词法分析、语法分析、语义检查、代码优化和字节生成;但对于解释型语言来说,通过词法分析和语法分析得到语法树后,就可以开始解释执行了。

javascript是由浏览器解释执行的脚本语言,不同于java c,需要先编译后运行,javascript 由浏览器js解释器进行解释执行,总的过程分为两大块,预编译期和执行期。js执行过程分为与编译期和执行期(以代码块为单位,边解释边执行),在预编译期,js解释器会对本代码段内所有的 声明的变量和方法进行处理,将变量和方法提到对应的作用域的最前面,该过程只是对变量进行声明,并不会进行初始化或者赋值(缺省值默认为undefined)。

js引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码时,会进行一个准备工作(确定执行上下文),即上边提到的预编译。

那么js引擎遇到一段怎样的代码时才会做准备工作呢?

其实很简单,javascript的可执行代码有三种:全局代码,函数代码和eval代码。例如,当执行到一个函数时,就会进行准备工作。

js引擎创建了执行上下文栈来管理执行上下文。

每个执行上下文,都有三个重要属性:
- 变量对象
- 作用域链
- this
这里重点说变量对象(Active Object:AO)。

1.全局上下文的变量对象就是全局对象。

2.函数上下文的变量对象会包括:

(1) 函数的所有形参
a. 名称和对应值(arguments)组成的一个变量对象的属性被创建
b.没有实参,属性值为undefined
(2)函数声明
a.由名称和对应值(函数对象(function-object))组成的一个变量对象的属性被创建
b.如果变量对象已经存在相同名称的属性,就完全替换这个属性
(3)变量声明
a.由名称和对应值(undefined)组成的一个变量对象的属性被创建。
b.如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
例子1:

function ff(a,b){    console.log(a);    console.log(b);    var b = 100;    function a(){    }    console.log(b);}ff(1,2);//function a(){}//2//100

例子2:

var a=100;function a(){  console.log(a);}a();

代码执行报错:a is not a function
问题来了,为什么会报这个错误呢? 这里涉及到函数和变量的预解析:

1)函数声明会置顶
2)变量声明也会置顶
3)函数声明比变量声明更置顶:(函数在变量上面)
4)变量和赋值语句一起书写,在js引擎解析时,会将其拆成声明和赋值2部分,声明置顶,赋值保留在原来位置
5)声明过的变量不会重复声明

上面代码等同于:

var a=function (){  console.log(a);}var a=100;a();

例子3:

function foo(a){    var b=2;    function c(){}    var d = function(){};    b=3;}foo(1);

进入执行上下文后,这时候的AO是:

AO={    arguments:{      0:1,      length:1    },    a:1    b:undefined,    c:reference to function c(){},    d:undefined}

代码执行

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
代码执行完,这时候的AO是:

AO = {    arguments:{       0:1,       length:1    },    a:1    b:3    c:reference to function c(){},    d:reference to FunctionExpression "d"}

3.关于变量对象,总结如下:

(1)全局上下文的变量对象初始化是全局对象;
(2)函数上下文的变量对象初始化只包括Arguments对象;
(3)在进入执行上下文时会给变量对象添加形参、函数声明、变量声明等初始的属性值。
(4)在代码执行阶段,会再次修改变量对象的属性值。