在这篇文章中,将比较深入地阐述下执行上下文 – JavaScript中最基础也是最重要的一个概念。相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的。

什么是执行上下文
Javascript中代码的运行环境分为以下三种:
全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
函数级别的代码 – 当执行一个函数时,运行函数体中的代码。
Eval的代码 – 在Eval函数内运行的代码。
在网上可以找到很多阐述作用域的资源,为了使该文便于大家理解,我们可以将“执行上下文”看做当前代码的运行环境或者作用域。下面我们来看一个示例,其中包括了全局以及函数级别的执行上下文:
上图中,一共用4个执行上下文。紫色的代表全局的上下文;绿色代表person函数内的上下文;蓝色以及橙色代表person函数内的另外两个函数的上下文。注意,不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。也就是说,我们可以在person的上下文中访问到全局上下文中的sayHello变量,当然在函数firstName或者lastName中同样可以访问到该变量。
至于函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出一个函数上下文,换句话说,就是新建一个局部作用域,可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。在上述例子的,内部的函数可以访问到外部上下文中的声明的变量,反之则行不通。那么,这到底是什么原因呢?引擎内部是如何处理的呢?
执行上下文堆栈
在浏览器中,javascript引擎的工作方式是单线程的。也就是说,某一时刻只有唯一的一个事件是被激活处理的,其它的事件被放入队列中,等待被处理。下面的示例图描述了这样的一个堆栈:
我们已经知道,当javascript代码文件被浏览器载入后,默认最先进入的是一个全局的执行上下文。当在全局上下文中调用执行一个函数时,程序流就进入该被调用函数内,此时引擎就会为该函数创建一个新的执行上下文,并且将其压入到执行上下文堆栈的顶部。浏览器总是执行当前在堆栈顶部的上下文,一旦执行完毕,该上下文就会从堆栈顶部被弹出,然后,进入其下的上下文执行代码。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。请看下面一个例子:
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
上述foo被声明后,通过()运算符强制直接运行了。函数代码就是调用了其自身3次,每次是局部变量i增加1。每次foo函数被自身调用时,就会有一个新的执行上下文被创建。每当一个上下文执行完毕,该上上下文就被弹出堆栈,回到上一个上下文,直到再次回到全局上下文。真个过程抽象如下图:
由此可见 ,对于执行上下文这个抽象的概念,可以归纳为以下几点:
单线程
同步执行
唯一的一个全局上下文
函数的执行上下文的个数没有限制
每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。
执行上下文的建立过程
我们现在已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:
建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)
建立变量,函数,arguments对象,参数
建立作用域链
确定this的值
代码执行阶段:
变量赋值,函数引用,执行其它代码
实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:
(executionContextObj = {
variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },
this: {}
}
建立阶段以及代码执行阶段的详细分析
确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。
上述第一个阶段的具体过程如下:
找到当前上下文中的调用函数的代码
在执行被调用的函数体中的代码以前,开始创建执行上下文
进入第一个阶段-建立阶段:
建立variableObject对象:
建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
检查当前上下文中的函数声明:
每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用
如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。
初始化作用域链
确定上下文中this的指向对象
代码执行阶段:
执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。
下面来看个具体的代码示例:
function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
}
foo(22);
在调用foo(22)的时候,建立阶段如下:
fooExecutionContext = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
scopeChain: { ... },
this: { ... }
}
由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:
fooExecutionContext = {
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
scopeChain: { ... },
this: { ... }
}
我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。
局部变量作用域提升的缘由
在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。
看下面一段代码:
(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello';
}
}());
上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。
为什么我们可以在声明foo变量以前就可以访问到foo呢?
因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。
为什么bar是undefined呢?
因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。
好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!
以上这篇对于Javascript 执行上下文的全面了解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
# javascript执行上下文
# 简单了解JavaScript中的执行上下文和堆栈
# JavaScript 中的执行上下文和执行栈实例讲解
# js 执行上下文和作用域的相关总结
# Javascript执行上下文顺序的深入讲解
# 通过实例了解JS执行上下文运行原理
# JS ES6中setTimeout函数的执行上下文示例
# 图文详解Javascript中的上下文和作用域
# JavaScript实现显示函数调用堆栈的方法
# JavaScript调用堆栈及setTimeout使用方法深入剖析
# 详解JavaScript中的执行上下文及调用堆栈
# 就会
# 一个函数
# 运行环境
# 第一个
# 我们可以
# 就可以
# 弹出
# 是一个
# 有个
# 才会
# 给大家
# 建立一个
# 值为
# 到该
# 单线程
# 的是
# 都是
# 运算符
# 这是
# 也就是说
相关文章:
建站主机与虚拟主机有何区别?如何选择最优方案?
建站之星如何实现五合一智能建站与营销推广?
Android自定义listview布局实现上拉加载下拉刷新功能
,怎么用自己头像做动态表情包?
建站主机如何选?性能与价格怎样平衡?
建站之星备案是否影响网站上线时间?
单页制作网站有哪些,朋友给我发了一个单页网站,我应该怎么修改才能把他变成自己的呢,请求高手指点迷津?
如何在Golang中实现微服务服务拆分_Golang微服务拆分与接口管理方法
深圳网站制作平台,深圳市做网站好的公司有哪些?
建站之星下载版如何获取与安装?
北京制作网站的公司排名,北京三快科技有限公司是做什么?北京三快科技?
建站之星如何快速生成多端适配网站?
建站主机如何安装配置?新手必看操作指南
如何通过山东自助建站平台快速注册域名?
javascript中对象的定义、使用以及对象和原型链操作小结
做企业网站制作流程,企业网站制作基本流程有哪些?
定制建站如何定义?其核心优势是什么?
,巨量百应是干嘛的?
西安专业网站制作公司有哪些,陕西省建行官方网站?
建站之星如何通过成品分离优化网站效率?
制作网站的过程怎么写,用凡科建站如何制作自己的网站?
建站之星如何助力企业快速打造五合一网站?
学校建站服务器如何选型才能满足性能需求?
建站之星×万网:智能建站系统+自助建站平台一键生成
建站10G流量真的够用吗?如何应对访问高峰?
高防服务器如何保障网站安全无虞?
如何选择高性价比服务器搭建个人网站?
网站制作专业公司有哪些,如何制作一个企业网站,建设网站的基本步骤有哪些?
如何选择网络建站服务器?高效建站必看指南
如何在腾讯云免费申请建站?
如何通过西部建站助手安装IIS服务器?
免费制作海报的网站,哪位做平面的朋友告诉我用什么软件做海报比较好?ps还是cd还是ai这几个软件我都会些我是做网页的?
建站主机功能解析:服务器选择与快速搭建指南
营销式网站制作方案,销售哪个网站招聘效果最好?
建站VPS选购需注意哪些关键参数?
济南专业网站制作公司,济南信息工程学校怎么样?
如何选择PHP开源工具快速搭建网站?
如何在沈阳梯子盘古建站优化SEO排名与功能模块?
湖北网站制作公司有哪些,湖北清能集团官网?
如何快速辨别茅台真假?关键步骤解析
如何通过FTP空间快速搭建安全高效网站?
常州企业网站制作公司,全国继续教育网怎么登录?
网站制作培训多少钱一个月,网站优化seo培训课程有哪些?
建站之星后台搭建步骤解析:模板选择与产品管理实操指南
如何用免费手机建站系统零基础打造专业网站?
常州自助建站工具推荐:低成本搭建与模板选择技巧
魔毅自助建站系统:模板定制与SEO优化一键生成指南
免费ppt制作网站,有没有值得推荐的免费PPT网站?
香港服务器网站生成指南:免费资源整合与高速稳定配置方案
企业宣传片制作网站有哪些,传媒公司怎么找企业宣传片项目?
*请认真填写需求信息,我们会在24小时内与您取得联系。