在这篇文章中,将深刻研究JavaScript最基本的部分之一,即执行上下文。在这篇文章的最后,你应该更清楚地理解解释器要作什么,为何在声明一些函数/变量以前可使用它们,以及它们的值是如何肯定的。web
当JavaScript代码运行时,执行代码的环境是至关重要的。通常有如下三种状况:浏览器
把执行上下文看做是当前代码正在执行的环境/做用域函数
// global context
var sayHello = 'sayHello'
function person() {
var first = 'webb'
var last = 'wang'
function firstName() {
return first
}
function lastName() {
return last
}
console.log(sayHello + firstName() + '' + lastName())
}
复制代码
以上代码没什么特别的地方,它包括1个全局上下文和3个不一样的函数上下文,全局上下文能够被程序中的其它任何上下文访问。ui
你能够有任意数量的函数上下文,每一个函数被调用的时候都会建立一个新的上下文。每一个下文都有一个不能被外部函数直接访问到的内部变量的私有做用域。在上面代码的例子中,一个函数能够访问当前上下文外部声明的变量,可是一个外部上下文不能够访问函数内部声明的变量。this
浏览器中的JavaScript解释器是做为一个单线程实现的,这实际上意味着,在浏览器中,一次只能发生一件事,其余操做或事件将排队在所谓的执行堆栈中。spa
当浏览器开始执行脚本时,首先会默认进入全局执行上下文,若是在全局代码中调用了函数,程序会按照顺序进入被调用函数,建立一个新的执行上下文,并推入到执行栈的栈顶。线程
若是你在当前执行的函数中,调用了另外的函数,代码的执行流将会进入函数内部,并建立一个新的执行上下文推入到执行栈顶。浏览器老是会先执行栈顶的代码,而且一旦函数完成执行当前执行上下文,他就会从栈顶弹出,将控制权返回到当前堆栈中的上下文。指针
关于执行堆栈有如下关键点code
如今咱们知道每当有函数被调用时,都会建立一个新的执行上下文。在js内部,每一个执行上文建立都要经历下面2个阶段对象
1.建立阶段(函数被调用,但尚未执行内部代码)
2.代码执行阶段
能够将每一个执行上下文概念上表示为一个具备3个属性的对象:
executionContextObj = {
'scopeChain': { /* variableObject + all parent execution context's variableObject */ },
'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },
'this': {}
}
复制代码
当函数被调用时,在建立阶段解释器会建立包含有函数内部变量,参数的一个变量对象
你能够在网上找到许多用JavaScript定义术语提高的资源,解释变量和函数声明被提高到函数做用域的顶部。可是,没有人详细解释为何会发生这种状况,并且有了解释器如何建立激活对象的新知识,就很容易理解为何会发生这种状况。如下面的代码为例:
(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello';
}
}());
复制代码
若是咱们遵循建立阶段,咱们就知道在代码执行阶段以前已经建立了变量。所以,当函数流开始执行时,foo已经在活动对象中定义。
尽管foo声明了两次,但从建立阶段咱们就知道函数是在变量以前在变量对象上建立的,若是变量对象上的属性名已经存在,那么咱们只需绕过。 所以,首先在变量对象上建立对函数foo()的引用,当解释器到达var foo时,咱们已经看到了属性名foo的存在,因此代码什么也不作,继续执行
bar其实是一个具备函数赋值的变量,咱们知道这些变量是在建立阶段建立的,可是它们是用undefined值初始化的。
但愿如今你已经很好地理解了JavaScript解释器是如何执行代码的。理解执行上下文和堆栈可让你了解代码没有按照预期执行的缘由