深刻学习js系列是本身阶段性成长的见证,但愿经过文章的形式更加严谨、客观地梳理js的相关知识,也但愿可以帮助更多的前端开发的朋友解决问题,期待咱们的共同进步。前端
若是以为本系列不错,欢迎点赞、评论、转发,您的支持就是我坚持的最大动力。git
做为一个JavaScript的程序开发者,若是被问到JavaScript代码的执行顺序,你脑海中是否是有一个直观的印象 -- JavaScript 是顺序执行的,可事实真的是这样的吗?github
让咱们首先看两个小例子:面试
var foo = function () {
console.log('foo1');
}
foo(); // foo1
var foo = function () {
console.log('foo2');
}
foo(); // foo2
复制代码
function foo() {
console.log('foo1');
}
foo(); // foo2
function foo() {
console.log('foo2');
}
foo(); // foo2
复制代码
刷过面试题目的都知道:数组
JavaScript引擎并不是一行一行地分析和执行程序,而是一段一段地分析执行,当执行一段代码的时候,会进行一个准备工做。浏览器
好比咱们熟悉的JavaScript中的变量提高好比函数提高都是在这个准备阶段完成的。微信
本文咱们就来深刻的研究一下,这一段一段中的段是如何划分的呢?闭包
到底JavaScript引擎遇到一段怎样的代码才会作"准备工做"呢?为了解答这个问题咱们引入一个概念——执行上下文。ide
若是你作太小学的阅读理解,确定见到过这样的题目:联系上下文解释句子,这里的上下文指的多是这个句子所在的段落,也多是这个句子所在段落的临近段落。实际上,这里描述的是一个句子的语境和做用范围,联系类比到程序中咱们能够做以下定义:函数
执行上下文是当前JavaScript代码被解析和执行时所在环境的抽象概念。
执行上下文总共分为三种类型,有时候咱们也叫作可执行代码(executable code)
window
对象,this
指向这个全局对象。Eval
函数执行上下文: 指的是运行在eval
函数中的代码,不多用并且不建议使用。举个例子,当执行到一个函数的时候,就会进行准备工做,这里的"准备工做",让咱们用个更专业一点的说法,就叫作"执行上下文(execution context)"。
接下来问题来了,咱们写的函数多了去了,如何管理建立的那么多执行上下文呢?因此 JavaScript 引擎建立了执行上下文栈(Execution context stack )ECStack 来管理执行上下文。
这里咱们能够简单的认为 ECStack 是一个数组,相似这样:
ECStack = [];
复制代码
执行栈,也叫作调用栈,具备 LIFO(last in first out 后进先出) 结构,用于存储在代码执行期间建立的全部执行上下文。
- 首次运行JavaScript代码的时候,会建立一个全局执行的上下文并Push到当前的执行栈中,每当发生函数调用,引擎都会为该函数建立一个新的函数执行上下文并Push当前执行栈的栈顶。
- 当栈顶的函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文的控制权将移动到当前执行栈的下一个执行上下文。
让咱们看一段代码来理解这个过程:
var a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
复制代码
- 当上述代码在浏览器加载时,JavaScript引擎建立了一个全局执行上下文并把它压入(push) 当前的执行栈。当遇到 first() 函数调用时,JavaScript引擎为该函数建立一个新的执行上下文并把它压入当前执行栈的顶部。
- 当从 first() 函数内部调用 second() 函数时,JavaScript引擎为 second() 函数建立了一个新的执行上下文并把它压入当前执行栈的顶部,当 second() 函数执行完毕,它的执行上下文会从当前栈弹出(pop),而且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。
- 当 first() 执行完毕,它的执行上下文从栈中弹出,控制流程到达了全局执行上下文。一旦全部的代码执行完毕,JavaScript引擎从当前栈中移出全局执行上下文。
下面这张图,可以更加清晰的解释上面这个执行过程
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
checkscope();
复制代码
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f;
}
checkscope()();
复制代码
两段代码执行的结果同样,可是两段代码究竟有哪些不一样呢?
答案就是执行上下文栈的变化不同。
让咱们模拟第一段代码:
ECStack.push(<checkscope> functionContext);
ECStack.push(<f> functionContext);
ECStack.pop();
ECStack.pop();
复制代码
让咱们模拟第二段代码:
ECStack.push(<checkscope> functionContext);
ECStack.pop();
ECStack.push(<f> functionContext);
ECStack.pop();
复制代码
checkscope()();这里对于这个函数的执行作一些解释
// checkscope()() 就至关于
var f = checkscope();
f();
复制代码
checkscope 函数执行,函数执行完毕后,该函数返回一个函数名,就至关于:
ECStack.push(<checkscope> functionContext); ECStack.pop(); 复制代码
而后再执行的这个返回的函数,就至关于:
ECStack.push(<f> functionContext); ECStack.pop(); 复制代码
为了更详细讲解两个函数执行上的区别,咱们须要探究一下执行上下文到底包含了哪些内容,咱们须要更加深刻了解变量对象的相关内容。
欢迎添加个人我的微信讨论技术和个体成长。