js运行步骤

  • 语法解析(检查有无语法错误)
  • 预编译
  • 解释运行(将 js 翻译成计算机识别的语言(0、1组成),翻译一行执行一行)

预编译

【全局】:

  • 创建 GO( Grobal Object ) 对象
  • 找变量声明
  • 找函数声明

【函数】:

  • 创建 AO( Activation Object ) 对象(执行上下文);
  • 找形参和变量声明,将形参和变量名作为 AO 对象的属性名,值为 undefined(有重复的名称只写一个即可);
  • 将形参与实参值统一(用实参的值替换 undefined);
  • 在函数体中找函数声明,将函数名添加到 AO 对象的属性中,值为函数体(如属性名重复,则覆盖前面的)。

【执行上下文/执行环境】组成:

  • 变量对象(Variable object,VO) :包含变量的对象,无法访问。
  • 作用域链(Scope chain):作用域即变量对象,作用域链是一个由变量对象组成的带头结点的单向链表,其主要作用就是用来进行变量查找;而[[Scope]]属性是一个指向这个链表头节点的指针
  • this:指向一个环境对象

【参考】https://www.jianshu.com/p/76ed896bbf91

执行环境(执行上下文 execution context)

定义

有时也称环境,执行环境定义了变量或函数有权访问的其他数据 ,决定了它们各自的行为。而每个执行环境都有一个与之相关的变量对象,环境中定义的所有变量和函数都保存在这个对象中。

执行过程

当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境,从此刻开始,函数的每次调用都会创建一个新的执行环境。

当javascript代码被浏览器载入后,默认最先进入的是一个全局执行环境。

当在全局执行环境中调用执行一个函数时,程序流就进入该被调用函数内,此时JS引擎就会为该函数创建一个新的执行环境,并且将其压入到执行环境堆栈的顶部。浏览器总是执行当前在堆栈顶部的执行环境,一旦执行完毕,该执行环境就会从堆栈顶部被弹出,然后,进入其下的执行环境执行代码。这样,堆栈中的执行环境就会被依次执行并且弹出堆栈,直到回到全局执行环境。

执行环境完成可以分为创建执行两个阶段

1、在创建阶段,解析器首先会创建一个`变量对象`【variable object】(函数中称为`活动对象`【activation object】),它由定义在执行环境中的变量、函数声明、和参数组成。在这个阶段,作用域链会被初始化,this的值也会被最终确定。
2、在执行阶段,代码被解释执行。
具体过程:每次调用函数,都会创建新的执行上下文。在JavaScript解释器内部,每次调用执行上下文,分为两个阶段:
2.1 创建阶段【若是函数,当函数被调用,但未执行任何其内部代码之前】
在进入执行上下文阶段,只会将有 var,function修饰的变量或方法添加到变量对象中。
2.1.1 创建作用域链(Scope Chain)
2.1.2 创建变量对象(变量,函数和参数)
2.1.3 确定this的指向
2.2 激活/代码执行阶段:
2.2.1 变量赋值
2.2.2 函数引用,
2.2.3 解释/执行其他代码

变量对象组成

  • 函数的所有形参 (如果是函数上下文)

    由名称和对应值组成的一个变量对象的属性被创建

    没有实参,属性值设为 undefined

  • 函数声明

    由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建

    如果变量对象已经存在相同名称的属性,则完全替换这个属性

  • 变量声明

    由名称和对应值(undefined)组成一个变量对象的属性被创建;

    如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

可以将每个执行上下文抽象为一个对象并有三个属性:

executionContextObj = {
scopeChain: { /* 变量对象(variableObject)+ 所有父执行上下文的变量对象*/ },
variableObject: { /*函数 arguments/参数,内部变量和函数声明 */ },
this: {}
}

解释器执行代码的伪逻辑(函数)

1、查找调用函数的代码。
2、执行函数代码之前,先创建执行上下文。
3、进入创建阶段:
3.1 初始化作用域链:
3.2 创建变量对象:
3.2.1 创建arguments对象,检查上下文,初始化参数名称和值并创建引用的复制。
3.2.2 扫描上下文的函数声明:
为发现的每一个函数,在变量对象上创建一个属性——确切的说是函数的名字——其有一个指向函数在内存中的引用。
如果函数的名字已经存在,引用指针将被重写。
3.2.3 扫描上下文的变量声明:
为发现的每个变量声明,在变量对象上创建一个属性——就是变量的名字,并且将变量的值初始化为undefined
如果变量的名字已经在变量对象里存在,将不会进行任何操作并继续扫描。
3.3 求出上下文内部“this”的值。
4、激活/代码执行阶段:
在当前上下文上运行/解释函数代码,并随着代码一行行执行指派变量的值。

demo:

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

1、创建阶段:foo(22)函数调用时

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

2、执行阶段:执行流进入函数并且激活/代码执行阶段

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

注意:

  1. 单线程
  2. 同步执行
  3. 唯一的全局执行环境
  4. 局部执行环境的个数没有限制
  5. 每次某个函数被调用,就会有个新的局部执行环境为其创建,即使是多次调用的自身函数(即一个函数被调用多次,也会创建多个不同的局部执行环境)。

执行环境的分类

  1. Global Code,即全局的、不在任何函数里面的代码,例如:一个js文件、嵌入在HTML页面中的js代码等。
  2. Function Code,即用户自定义函数中的函数体JS代码。
  3. Eval Code,即使用eval()函数动态执行的JS代码。【不推荐,可忽略】



    全局环境:JavaScript代码运行起来会首先进入该环境

    函数环境:当函数被调用执行时,会进入当前函数中执行代码

    eval(不建议使用,可忽略)

解释:

  1. 全局执行环境

    在浏览器中,其指window对象,是JS代码开始运行时的默认环境。

    全局执行环境的变量对象始终都是作用域链中的最后一个对象。
  2. 函数执行环境

    当某个函数被调用时,会先创建一个执行环境及相应的作用域链。然后使用arguments和其他命名参数的值来初始化执行环境的变量对象。

执行上下文(execution context)属性:

  • 变量对象(variableObject)
  • 作用域链(scope chain)
  • this

【注】:

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。

全局上下文中的变量对象就是全局对象

在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。


AO & VO 区别与联系

活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。

活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

AO = VO + function parameters + arguments
AO 还包含函数的 parameters,以及 arguments 这个特殊对象

未进入执行阶段之前,变量对象(VO)中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)被激活转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。

它们其实都是同一个对象,只是处于执行上下文的不同生命周期


作用域链创建

词法作用域(lexical scoping)是指,函数在执行时,使用的是它被定义时的作用域,而不是这个函数被调用时的作用域
函数的作用域在函数定义的时候就决定了。
这是因为函数有一个内部属性 [[scope]],当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
当函数激活时,进入函数上下文,创建 VO/AO 后,就会将活动对象添加到作用链的前端。至此,作用域链创建完毕。

demo:

		function foo() {
function bar() {
...
}
}
// 函数创建时,各自的[[scope]]为: foo.[[scope]] = [
globalContext.VO
]; bar.[[scope]] = [
fooContext.AO,
globalContext.VO
];

函数执行上下文中作用域链和变量对象的创建过程总结

		var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();

执行过程如下:

		// 1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
]; // 2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
]; // 3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
} // 4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
} // 5.第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
} // 6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
} // 7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];

闭包(可以用执行上下文中来解释)

  • 即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
  • 在代码中引用了自由变量

内部函数引用了外部函数的变量,在外部函数上下文被销毁后,其中的变量仍然可以被其内部函数引用

因为:

fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}

对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

**说明:

个人总结,笔记,有误请指出,谢谢指教~

ps: 文章格式暂时没时间处理,之后优化【捂脸】

**

参考:

1、http://www.cnblogs.com/neusc/p/5771150.html

2、https://yanhaijing.com/javascript/2014/04/29/what-is-the-execution-context-in-javascript/

3、https://github.com/mqyqingfeng/Blog/issues/8

4、https://github.com/mqyqingfeng/Blog/issues/6

js - 基础 之 预编译总结的更多相关文章

  1. js中的预编译

    预编译 js执行顺序: 词法/语法分析 预编译 解释执行 js中存在预编译 function demo() { console.log('I am demo'); } demo(); //I am d ...

  2. JS作用域和预编译(转载 学习中。。。)

    JS在页面加载过程中顺序执行.但是分块预编译.执行. JS在执行前会进行类似”预编译”的操作,而且先预声明变量再预定义函数. 此时注意,是声明,不是定义,如:var a = 1; 在预编译中,只是执行 ...

  3. js复习,预编译

    注意:函数声明整体提升.变量 声明提升 1.imply global 暗示全局变量:即任何变量,如果变量未声明就赋值,此变量就为全局对象所有 ==>  eg: a = 122;==>  e ...

  4. gulp最佳实践(包含js,css,html预编译,合并,压缩,浏览器自动刷新)

    gulp是基于流的自动化构建工具官方网址:http://www.gulpjs.com.cn/ 一.安装需要的模块 1.新建package.json,输入下面的内容 { "name" ...

  5. JS基础语法---预解析

    预解析:就是在解析代码之前   1. 预解析做什么事? 把变量的声明提前了----提前到当前所在的作用域的最上面 函数的声明也会被提前---提前到当前所在的作用域的最上面   举例: function ...

  6. Rails : css或js文件无法成功预编译或调用jquery类插件时预编译问题

    调用bootstrap css框架时,将bootstrap文件夹放入 vendor/assets/下 bootstrap文件结构如下:    [shenma@localhost demo]$ ls v ...

  7. Javascript的"预编译"思考

    今天工作需要,搜索下JS面试题,看到一个题目,大约是这样的 <script> var x = 1, y = z = 0; function add(n) { n = n+1;  } y = ...

  8. 通过预编译头文件来提高C++ Builder的编译速度

    C++ Builder是最快的C++编译器之一,从编译速度来说也可以说是最快的win32C++编译器了.除了速度之外,C++builder的性能也在其它C++编译器的之上,但许多Delphi程序员仍受 ...

  9. Javascript - 预编译与函数词法作用域

    预编译与函数词法作用域(Precompiled & Scoped) 预编译 Javascript脚本的宿主在执行代码之前对脚本做了预编译处理,比如浏览器对Js进行了预编译,编译器会扫描所有的声 ...

随机推荐

  1. [Flask]jinja2模板-宏的使用

    定义宏: macros.html <!DOCTYPE html> <html lang="en"> <head> <meta charse ...

  2. OpenStack 启动虚拟机 Booting from Hard Disk

    问题 OpenStack 启动虚拟机 Booting from Hard Disk-GRUB 环境 OpenStack RUNNING IN vSphere 6.0.0 VM 开启了 CPU 虚拟化支 ...

  3. 去除雨滴的滤镜 Derain in FFmpeg

    Remove the rain in the input image/video by applying the derain methods based on convolutional neura ...

  4. JS实战篇

    实现选项卡的选择: 效果图如下: 代码如下: <!DOCTYPE html> <html> <head> <meta charset="UTF-8& ...

  5. IDEA和VS快捷键对比

    IDEA和Visual Studio快捷键对比 VS     F5 F9            resume programe 恢复程序     Alt+F10       show executio ...

  6. linux中权限对文件和目录的影响?

    #########  rwx 权限对文件和目录的含义  ############ 代表字符          权限                  对文件的含义               对 目录 ...

  7. 2031 HDOJ 进制转换

    Problem Description 输入一个十进制数N,将它转换成R进制数输出.   Input 输入数据包含多个测试实例,每个测试实例包含两个整数N(32位整数)和R(2<=R<=1 ...

  8. 解决ajax跨越问题

    解决方案: ajax跨域访问是一个老问题了,解决方法很多,比较常用的是JSONP方法,JSONP方法是一种非官方方法,而且这种方法只支持GET方式,不如POST方式安全.   如果跨域使用POST方式 ...

  9. JavaScript中:地址引用的特性,导致静态初始值被修改

    问题分类 JavaScript,值引用,地址引用 问题描述 开发过程中,服务端将静态配置数据从mysql数据库中读取到内存中,方便调用. 在实现流派功能时,需从数据库中读取流派种类数据到内存中,由于其 ...

  10. 【转帖】AMD:未向合资企业THATIC发放后续芯片设计授权

    AMD:未向合资企业THATIC发放后续芯片设计授权 https://www.cnbeta.com/articles/tech/854193.htm 海光和兆芯的CPU 都不靠谱啊. 在台北电脑展(C ...