先从一个简单的例子出发(先不涉及异步),看看本身是否大体了解浏览器的执行机制:html
console.log(a);
var a=1;
function foo(a){
console.log(a);
var a=2;
console.log(a);
}
foo(a);
复制代码
undefined 1 2git
若是你的预测结果不同,那你能够看看下面几个常见的误区:github
答:变量提高只提高变量的声明,并不进行赋值。其中变量提高发生在预编译阶段,此时a=undefined,预编译结束后代码以下web
//函数声明和变量声明进行提高,且函数声明优先级更高
function foo(a){
console.log(a);
var a=2;
console.log(a);
}
var a;
console.log(a);
a=1;
foo(a);
复制代码
很明显第一个结果为undefined。数组
变量声明在顺序上跟在函数声明和形式参数声明以后,同时,若是变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。例子中的var a=2,能够拆分为var a;a=2;其中a=2是对参数a进行赋值。浏览器
首先你须要理解以下几个概念:安全
JavaScript 中的变量分为基本类型和引用类型。其中,基本类型存在于栈中,引用类型存在于堆中。在js的执行阶段,当执行到a=2这样的赋值语句时,js引擎线程会先判断2是基本类型仍是引用类型,若是它是基本类型,则直接对执行栈中的AO进行赋值a=2(AO会在下面的执行上下文中讲到),如果引用类型,则在堆中存入2,而后用2在堆中的地址对AO进行赋值。bash
js的执行环境分为三种:异步
js每进入一个执行环境就会建立一个执行上下文,并将它放入执行栈中。执行上下文会在下文讲到。函数
js是一门单线程语言,但并不意味着参与js执行过程的线程就只有一个。一个有四个线程参与该过程: JS引擎线程、事件触发线程、定时器触发线程、HTTP异步请求线程。其中,只有JS引擎线程在执行JS脚本程序,其余三个线程只负责将知足触发条件的处理函数推动事件队列,等待JS引擎线程执行。
举一个简单的例子来讲:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
复制代码
JS引擎主线程按代码顺序执行,当执行到console.log('script start');,JS引擎主线程认为该任务是同步任务,因此马上执行输出script start,而后继续向下执行;
JS引擎主线程执行到setTimeout(function() { console.log('setTimeout'); }, 0);,JS引擎主线程认为setTimeout是异步任务API,则向浏览器内核进程申请开启定时器线程进行计时和控制该setTimeout任务。因为W3C在HTML标准中规定setTimeout低于4ms的时间间隔算为4ms,那么当计时到4ms时,定时器线程就把该回调处理函数推动任务队列中等待主线程执行,而后JS引擎主线程继续向下执行;
JS引擎主线程执行到console.log('script end');,JS引擎主线程认为该任务是同步任务,因此马上执行输出script end;
JS引擎主线程上的任务执行完毕(输出script start和script end)后,主线程空闲,则开始读取任务队列中的事件任务,将该任务队里的事件任务推动主线程中,按任务队列顺序执行,最终输出setTimeout,因此输出的结果顺序为script start script end setTimeout;
若是还不清楚,能够看看下图:
首先,这是一个浏览器环境,其中主线程操做堆和执行栈,而RunTime中存在着许多web API,当主线程读取到setTimeOut等API时,它会交给其余线程来处理(setTimeOut则是定时器触发线程),定时器触发线程会先将setTimeOut中的回调函数存放在event table中,当知足触发条件时(如上面的4ms),就将回调函数推入事件队列(callback queue)中,等待主线程空闲(执行栈中为空),回调函数则被推入执行栈中进行执行。
执行上下文可理解为当前的执行环境,与该运行环境相对应。js引擎每进入一个环境就会建立相应的执行上下文,建立执行上下文的过程当中,主要作了如下三件事件,如图:
其中,变量对象VO(Variable object)用于存放声明后的变量、函数和形参。咱们举一个例子来讲:
var a = 10;
function test(x) {
var b = 20;
};
test(30);
复制代码
对应的变量对象是:
// 全局上下文的变量对象
VO(global) = {
a: undefined,
test: <reference to function>
//<reference to function>是test函数位于堆中的地址
};
// test函数上下文的变量对象
VO(test) = {
arguments: {
x:undefined,
length:1
},
b: undefined
};
复制代码
当预编译结束,js进入解释执行阶段时,VO就会转化为AO(Active object),也就是活动对象。AO中变量和参数的值再也不是undefined,它们的值会随着js的逐步执行而发生变化。
做用域链用于代表上下文的执行顺序。上例中的做用域链为:
scopeChain: [VO(test), AO(global)],
复制代码
this指向当前做用域。这里不作过多分析。
js的执行分为三个阶段:
语法分析阶段: script标签加载即开始语法分析,分析整个标签内的语法错误。无错误即进入预编译阶段。
预编译阶段: 每进入一个新环境,就进行一次预编译,同时建立一个执行上下文(包含VO对象、做用域链、this,忘了是什么就往回看看)并放入执行栈中。此时,当前环境会进行必定的函数提高和变量提高,注意:函数提高优先于变量提高。环境中没有新的函数声明则进入解释执行阶段。
解释执行阶段: 将当前执行上下文中的VO->AO, 此后,js引擎在当前环境从上到下、从左到右执行代码,不断改变AO中的变量等内容。当前上下文执行完毕则出栈,执行下一个上下文。
一样,咱们举一个例子进行分析:
var a = 1;
function bar() {
var b = 2;
function foo() {
var c = 3;
}
foo();
console.log(b)
}
function coo() {
alert("hello")
}
bar()
coo()
复制代码
咱们解释一下以上过程:
至此,你已基本理解了js的执行机制。