要知道你写的代码接下来是交给谁的,先要明白解释型语言和编译型语言。html
解释型语言:这种类型的编程语言,会将代码一句一句直接运行,不须要像编译语言(Compiled language)同样,通过编译器先行编译为机器码,以后再运行。这种编程语言须要利用解释器,在运行期,动态将代码逐句解释(interpret)为机器码,或是已经预先编译为机器码的的子程序,以后再运行。html5
编译型语言:是一种以编译器来实现的编程语言。它不像解释型语言同样,由解释器将代码一句一句运行,而是以编译器,先将代码编译为机器码,再加以运行。理论上,任何编程语言均可以是编译式,或直译式的。它们之间的区别,仅与程序的应用有关。算法
那么,JavaScript就是典型的解释型语言,那么要运行JavaScript程序就必需要有响应的执行环境,也就是要经过JavaScript引擎解析执行JS代码。JavaScript引擎的基本工做是把开发人员写的JavaScript代码转换成高效、优化的代码,这样就能够经过浏览器进行解释甚至嵌入到应用中。好比著名的V8
引擎。chrome
JavaScript的解析过程分为两个阶段:预编译期(预处理)
与执行期
。在预编译期,JavaScript解释器完成对JavaScript代码的预处理,转换为字节码。执行期间,JavaScript解释器把字节码转换成二进制码,按照顺序执行。编程
正常的编译型语言编译期,其过程可分为6步:词法分析、语法分析、语义分析、源代码优化、代码生成、目标代码优化。对于JavaScript来讲,经过词法分析和语法分析获得语法树后,就会进入到执行期,执行代码。浏览器
词法分析:在词法分析阶段,JavaScript解释器先把代码的字符流转换为记号流,例如:bash
a = (b -c)
复制代码
转换为记号流:数据结构
NAME "a"
EQUALS
OPEN_PARENTHESIS
NAME "b"
MINUS
NAME "c"
CLOSE_PARENTHESIS
SEMICOLON
复制代码
词法分析阶段能够实现的是:一、去掉注释,生成文档;二、记录错误信息;三、完成预处理多线程
语法分析阶段就是把词法分析阶段产生的记号,生成语法树,即把从程序中收集的信息存储到数据结构中,数据结构在此处为两种:一、符号表:记录变量、函数、类;二、语法树:程序结构的树形表示,将此树形结构生成中间代码。例如:闭包
if(typeof a == "undefined" ) {
a = 0
} else {
a = a
}
alert(a)
复制代码
生成的语法树为:
当构建语法树的过程当中,没法构造,则报出语法错误,并结束整个代码块的解析。 词法分析和语法分析阶段是交错进行的,每取一个词法记号,就送入语法分析器进行分析。 词法、语法分析是有规则的,其中ECMAScript262这份文档,就是对JavaScript这门语言定义了一整套完整的标准。语法分析就依靠这套标准,固然也有不按照标准来实现的,好比IE的JS引擎。这也是为何JavaScript会有兼容性的问题。
通过编译阶段的准备,代码在内存中已经构建成语法树,JavaScript引擎会根据这个语法树结构边解释边执行。解释过程当中,引擎严格按照做用域机制执行。JavaScript采用的词法做用域,简单说就是变量和函数的做用域在定义时决定,取决于源代码结构。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar();
复制代码
就像这段代码,并不会像动态做用域同样,输出2
. 引擎解释执行每一个函数时,先建立一个执行环境,在这个环境中建立一个调用对象,这个对象内存储着当前域中全部局部变量、参数、嵌套函数、引用函数和父级列表。调用对象声明周期与函数一致,当函数调用完毕且没有外部引用的状况下,被垃圾回收机制回收。
同时解释器经过做用域链把多个嵌套的做用域串在一块儿,并借助这个链,由内而外查找变量值,直到全局对象,若是没有找到,返回"undefined"。做用域链,是由当前环境与上层环境的一系列变量对象组成,它保证了当前执行环境对符合访问权限的变量和函数的有序访问。
在执行环境建立的过程当中,会有一个特殊的状况——闭包。它由两部分组成。执行上下文(代号A),以及在该执行上下文中建立的函数(代号B)。当B执行时,若是访问了A中变量对象中的值,那么闭包就会产生。在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。
function A() {
var a = 20;
var b = 30;
function B() {
return a + b;
}
return B;
}
var B = A();
B();
复制代码
首先有执行上下文A,在A中定义了函数B,而经过对外返回B的方式让B得以执行。当B执行时,访问了A内部的变量a,b。所以这个时候闭包产生。JavaScript拥有自动的垃圾回收机制,关于垃圾回收机制,有一个重要的行为,那就是,当一个值,在内存中失去引用时,垃圾回收机制会根据特殊的算法找到它,并将其回收,释放内存。正常来说,当A执行完毕后,生命周期结束,A函数的执行上下文就会失去引用。其占用的内存空间很快就会被垃圾回收器释放。但是B函数的存在,会阻止这一过程,使B函数常驻内存。
JavaScript的单线程,与它的用途有关。做为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操做DOM。这决定了它只能是单线程,不然会带来很复杂的同步问题。好比,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另外一个线程删除了这个节点,这时浏览器应该以哪一个线程为准? 因此,为了不复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,未来也不会改变。 为了利用多核CPU的计算能力,HTML5提出WebWorker标准,容许JavaScript脚本建立多个线程,可是子线程彻底受主线程控制,且不得操做DOM。因此,这个新标准并无改变JavaScript单线程的本质。单个线程使得运行代码很容易,由于你没必要处理在多线程环境中出现的复杂场景——例如死锁。可是在一个线程上运行也很是有限制。因为JavaScript、只有一个调用堆栈,当某段代码运行变慢时会发生什么?
既然是单线程的,在某个特定的时刻只有特定的代码可以被执行,并阻塞其它的代码。而浏览器是事件驱动的(Event driven),浏览器中不少行为是异步的,会建立事件并放入执行队列中。JavaScript引擎是单线程处理它的任务队列。当异步事件发生时,如mouse click, a timer firing, or an XMLHttpRequest completing(鼠标点击事件发生、定时器触发事件发生、XMLHttpRequest完成回调触发等),将他们放入执行队列,等待当前代码执行完成再从执行队列按序拿出事件执行。Event Loop只作一件事情,负责监听Call Stack和Callback Queue。当Call Stack里面的调用栈运行完变成空了,Event Loop就把Callback Queue里面的第一条事件(其实就是回调函数)放到调用栈中并执行它,后续不断循环执行这个操做。
也就是说JS只有一个调用栈。调用栈是一种数据结构,它记录了咱们在程序中的位置。若是咱们运行到一个函数,它就会将其放置到栈顶。当从这个函数返回的时候,就会将这个函数从栈顶弹出,这就是调用栈作的做用。栈内的任务队列又分为macro-task
(宏任务)与micro-task
(微任务),在最新标准中,它们被分别称为task与jobs。
macro-task
大概包括:script(总体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。micro-task
大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)。setTimeout(function() {
console.log('xxxx'); // 这段代码才是进入任务队列的任务
})
// setTimeout做为一个任务分发器,这个函数会当即执行,而它所要分发的任务,也就是它的第一个参数,才是延迟执行
复制代码
垃圾回收机制有好多种,这里简单说下标记清除算法
为了决定一个对象是否被须要,这个算法用于肯定是否能够找到某个对象。 其包含如下步骤。