热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

以及其任何超类对此上下文都是未知的_JSExecutionContext—执行上下文

你说我坑你,不是我内心复杂,只是你不够了解我——JS张北草原天路2017.6说明本文根据多篇中外博客文章,加上个人理解整合而成。因为曾尝试

你说我坑你,不是我内心复杂,只是你不够了解我 —— JS

b30ebe55efa823b4a9a735caad5a09b1.png

张北草原天路 2017.6

说明

本文根据多篇中外博客文章,加上个人理解整合而成。

因为曾尝试翻译过这些文章,发现并不是很好理解,所以希望换个自己的方式去表达原文的部分观点。如果表达的不准确,还望谅解。

本文写于 15 年,那个时候对 JS 还不是很了解,有计划从编译的角度再写一次。

名词约定

  • Execution Context(EC) 执行上下文
  • Executable Code 可执行代码
  • Execution Context Stack(ECS) 执行上下文栈
  • Variable Objec(VO) 变量对象
  • Activation Object(AO) 活动对象
  • Scope 作用域
  • Scope Chain 作用域链
  • Arguments Object 参数对象
  • Global Code 全局代码
  • Function Code 函数代码
  • Eval Code

Execution Context

每次当解释器转到不同的可执行代码的时候,就会进入一个执行上下文 EC。可以简单的理解执行上下文就是代码的执行环境或者作用域。EC 是个抽象的概念,ECMA-262 使用 EC 和Executable Code 区分。

Executable Code

可以简单的理解:可执行代码就是 JS 中合法的代码,可以被 JS 解释器执行的代码。

可执行代码的分类:

  1. Global code 全局代码

可以理解为是 JS 解释器为 JS 程序提供的默认全局代码,例如 function Object(), eval() 这些内建的函数代码,window 等全局对象等等。(注意:只包含函数定义代码,但是不包括函数体中的代码,请看 Function code 做的解释)。

2. Eval code

使用 eval 函数执行的代码。

可执行代码的概念与抽象的执行上下文的概念是相关的。在某些时刻,可执行代码与执行上下文是等价的。

在执行上下文提到,当程序执行转移到不同的可执行代码的时候,就会根据当前的可执行代码的类型新建一个对应的执行环境。

根据可执行代码的类型,我自己也给执行上下文分类:

  1. Global Execution Context 全局执行上下文 代码的默认运行环境,程序代码一旦被载入,最先进入的执行环境。只有一个全局执行上下文。
  2. Function Execution Context 函数执行上下文 每当调用一个函数,也就是执行函数体中的代码会新建一个函数执行上下文环境。
  3. Eval Execution Context Eval 执行上下文

当使用 eval 函数执行代码的时候,会新建一个 eval 执行上下文。

为了方便理解,来看一张图:

7419c875bbd26025ce520b342d9cf150.png

此图表示一个完整的JS程序。

一共用 4 个执行上下文。紫色的代表全局的上下文;绿色代表 person 函数内的上下文;蓝色以及橙色代表 person 函数内的另外两个函数的上下文。

只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。也就是说,我们可以在 person 的上下文中访问到全局上下 文中的 sayHello 变量,当然在函 firstName 或者 lastName 中同样可以访问到该变量。

函数上下文的个数是没有任何限制的,每到调用执行一个函数时,解释器就会自动新建出一个函数上下文,换句话说,就是新建一个局部作用域,可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。

在上述例子的,内部的函数可以访问到外部上下文中的声明的变量,反之则行不通。那么,这到底是什么原因呢?解释器内部是如何处理的呢?请往下看。

Execution Context Stack

一系列的上下文组成了上下文栈。这个栈和数据结构中的栈类似——先进后出(如果学过操作系统线程和栈帧这些概念,那理解起来非常容易——推荐《深入理解操作系统》)。 JS 程序只有一个线程,这意味着 JS 程序同一时间只能做一件事情(关于异步编程,以后再细细道来),知道这个很重要。

把执行上下文栈可视化大概是下图这个样子:

d07d68ff66c590dd7bf3c0390d74f227.png

栈顶是当前活动的执行上下文,也就是程序正在栈顶那个执行环境运行。栈底是全局执行上下文,因为程序一运行立即入栈的就是全局执行上下文,我们写的 JS 代码都是在全局执行上下文环境运行的。

通过出栈和入栈,切换当前程序代码的执行环境。

类似于原型链,上下文也有父执行上下文,子执行上下文。子执行上下文可以访问父执行上下文,但父执行上下文不能访问子执行上下文。上下文之间使用 scope 链接起来,我们把它称为 Scope Chain 作用域链。

举个栗子:

(function foo(i) {if (i === 3) {return;} else {foo(++i);}
}(0));

上述 foo 被声明后,通过 () 运算符强制直接运行了。函数代码就是调用了其自身 3 次,每次是局部变量 i 增加 1。每次 foo 函数被自身调用时,就会有一个新的执行上下文被创建。每当一个上下文执行完毕,该上下文就被弹出堆栈,回到上一个上下文,直到再次回到全局上下文。

62fa192d4cf4897a31a8b08d51602a1e.gif

对2和3做个总结:

1.单线程
2.同步执行
3.只有一个全局执行上下文
4.无限制个函数执行上下文

Execution Context深入

Execution Context以函数上下文为例

每次函数调用都会生成一个新的函数执行上下文。这个生成过程可以分解为两个步骤:

  1. Creation Stage 创建阶段(函数被调用,但函数执行之前)

A.创建 Scope Chain 作用域链
B.创建变量,函数和参数
C.确定 this 的值

  1. Activation / Code Execution Stage 代码执行阶段

A.解释器执行代码,变量赋值。
可以把函数执行上下文想象成一个拥有三个属性的对象:
executionContextObj = {scopeChain: { / variableObject + all parent execution context's variableObject / },variableObject: { / function arguments / parameters, inner variable and function declarations / },this: {}
}

函数执行上下文创建详解

(1)找到调用函数的入口
(2)在执行函数代码之前,创建一个执行上下文
(3)进入 creation stage 上下文创建阶段
1. 初始化 Scope Chain 作用域链
2. 创建 variable object 变量对象
2.1 创建 arguments object 对象,使用调用函数传入的实参赋值。2.2 从上往下扫描函数体代码中的函数声明;
A.对于每找到的一个函数声明,在 VO 中创建创建一个使用函数名为名的属性,并赋值(这个阶段,函数定义就被载入内存,所以函数声明在这个阶段可以赋值。注意区别函数声明和函数表达式的不同)
B.如果函数名已经存在了,那就会发生覆盖。也就是,如果有重名函数,后面的会覆盖前面的。
2.3 从上到下扫描函数体代码中的变量声明
A.每找到的一个变量声明,以变量名为属性名在 VO 创建一个属性,并赋值 undefined。
B.如果在 VO 中发生重名,解释器会跳过去,接着扫描。
3. 确定 this 的值。(4)Activation / Code Execution 代码执行阶段
在创建的上下文中从上到下逐行执行函数体代码,并给变量赋值

举个栗子:

function foo(i) {var a = 'hello';var b = function privateB() { };function c() { }
}foo(22);

当执行 foo(22) 的时候,creation state 阶段的执行上下文大概是这个样子:

fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: { 0: 22, length: 1 },i: 22, c: pointer to function c(),a: undefined,b: undefined},this: { ... }
}

当函数执行完成,execution stage 大概是这样:

fooExecutionContext = {scopeChain: { ... },variableObject: {arguments: { 0: 22, length: 1 },i: 22,c: pointer to function c(),a: 'hello',b: pointer to function privateB()},this: { ... }
}

总结 —— 函数提升

(function() {console.log(typeof foo);// function pointerconsole.log(typeof bar);// undefinedvar foo = 'hello',bar = function() { return 'world'; };function foo() {return 'hello';}
}());

  1. 为什么可以在声明 foo 之前可以使用 foo?

在 execution stage 阶段之前的 creation stage 阶段,解释器已经在 VO 创建了所有的变量,所以在代码执行的时候,foo 已经有值了。

  1. foo 被声明了两次,为什么 foo 的值是 function,而不是 undefined 或者 string?

尽管 foo 被声明了两次,但是在 creation stage 阶段,函数声明优先于变量声明被创建。并且,变量声明不会覆盖函数声明在 VO 的属性。 需要注意的是 console.log(typeof foo) 是 undefined.但是执行 var foo = 'hello' 后,foo 的值就是 ’hello’。

  1. 为什么 bar 是 undefined

bar 是一个变量,它的值是一个函数表达式。在 creation stage 阶段,只是在 VO 中创建了 bar 这个属性,并没有赋值。

思考

  1. 什么是执行上下文,什么是执行上下文栈?
  2. 执行上下文的分类?
  3. 执行上下文被创建的过程?

参考

  • What is the Execution Context & Stack in Javascript
  • 深入理解Javascript之执行上下文
  • ECMA-262-3 in detail. Chapter 1. Execution Contexts
  • http://bclary.com/2004/11/07/#a-10
  • Understanding Execution Context and Execution Stack in Javascript



推荐阅读
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • springmvc学习笔记(十):控制器业务方法中通过注解实现封装Javabean接收表单提交的数据
    本文介绍了在springmvc学习笔记系列的第十篇中,控制器的业务方法中如何通过注解实现封装Javabean来接收表单提交的数据。同时还讨论了当有多个注册表单且字段完全相同时,如何将其交给同一个控制器处理。 ... [详细]
  • Html5-Canvas实现简易的抽奖转盘效果
    本文介绍了如何使用Html5和Canvas标签来实现简易的抽奖转盘效果,同时使用了jQueryRotate.js旋转插件。文章中给出了主要的html和css代码,并展示了实现的基本效果。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 标题: ... [详细]
author-avatar
sir栖云_888
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有