深入理解javascript作用域系列第五篇
前面的话
对于执行环境(execution context)和作用域(scope)并不容易区分,甚至很多人认为它们就是一回事,只是高程和犀牛书关于作用域的两种不同翻译而已。但实际上,它们并不相同,却相互纠缠在一起。本文先用一张图开宗明义,然后进行术语的简单解释,最后根据图示内容进行详细说明
图示
概念
【作用域】
作用域是一套规则,用于确定在何处以及如何查找标识符。关于LHS查询和RHS查询详见作用域系列第一篇内部原理。
作用域分为词法作用域和动态作用域。javascript使用词法作用域,简单地说,词法作用域就是定义在词法阶段的作用域,是由写代码时将变量和函数写在哪里来决定的。于是词法作用域也可以描述为程序源代码中定义变量和函数的区域
作用域分为全局作用域和函数作用域,函数作用域可以互相嵌套
在下面的例子中,存在着全局作用域,fn作用域和bar作用域,它们相互嵌套

【作用域链和自由变量】
各个作用域的嵌套关系组成了一条作用域链。例子中bar函数保存的作用域链是bar -> fn -> 全局,fn函数保存的作用域链是fn -> 全局
使用作用域链主要是进行标识符的查询,标识符解析就是沿着作用域链一级一级地搜索标识符的过程,而作用域链就是要保证对变量和函数的有序访问
【1】如果自身作用域中声明了该变量,则无需使用作用域链
在下面的例子中,如果要在bar函数中查询变量a,则直接使用LHS查询,赋值为100即可

var a = 1;
var b = 2;
function fn(x){
var a = 10;
function bar(x){
var a = 100;
b = x + a;
return b;
}
bar(20);
bar(200);
}
fn(0);

【2】如果自身作用域中未声明该变量,则需要使用作用域链进行查找
这时,就引出了另一个概念——自由变量。在当前作用域中存在但未在当前作用域中声明的变量叫自由变量
在下面的例子中,如果要在bar函数中查询变量b,由于b并没有在当前作用域中声明,所以b是自由变量。bar函数的作用域链是bar -> fn -> 全局。到上一级fn作用域中查找b没有找到,继续到再上一级全局作用域中查找b,找到了b

var a = 1;
var b = 2;
function fn(x){
var a = 10;
function bar(x){
var a = 100;
b = x + a;
return b;
}
bar(20);
bar(200);
}
fn(0);

[注意]如果标识符没有找到,则需要分为RHS和LHS查询进行分析,若进行的是LHS查询,则在全局环境中声明该变量,若是严格模式下的LHS查询,则抛出ReferenceError(引用错误)异常;若进行的是RHS查询,则抛出ReferenceError(引用错误)异常。详细情况移步至此
【执行环境】
执行环境(execution context),有时也称为执行上下文、执行上下文环境或环境,定义了变量或函数有权访问的其他数据。每个执行环境都有一个与之关联的变量对象(variable object),环境中定义的所有变量和函数都保存在这个对象中

这是例子中的代码执行到第15行时fn(0)函数的执行环境,执行环境里面保存了fn()函数作用域内所有的变量和函数的值

【执行流】
代码的执行顺序叫做执行流,程序源代码并不是按照代码的书写顺序一行一行往下执行,而是和函数的调用顺序有关
例子中的执行流是第1行 -> 第2行 -> 第4行 -> 第15行 -> 第5行 -> 第7行 -> 第12行 -> 第8行 -> 第9行 -> 第10行 -> 第11行 -> 第13行 -> 第8行 -> 第9行 -> 第10行 -> 第11行 -> 第14行

[注意]在程序代码执行之前存在着编译和声明提升(hoisting)的过程,本例中假设代码是已经经过声明提升过程之后的代码
【执行环境栈】
执行环境栈类似于作用域链,有序地保存着当前程序中存在的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回给之前的执行环境。javascript程序中的执行流正是由这个机制控制着
在例子中,当执行流进入bar(20)函数时,当前程序的执行环境栈如下图所示,其中黄色的bar(20)执行环境表示当前程序正处此执行环境中

当bar(20)函数执行完成后,当前程序的执行环境栈如下图所示,bar(20)函数的执行环境被销毁,等待垃圾回收,控制权交还给黄色背景的fn(0)执行环境

说明
下面按照代码执行流的顺序对该图示进行详细说明

【1】代码执行流进入全局执行环境,并对全局执行环境中的代码进入声明提升(hoisting)












总结
【1】javascript使用的是词法作用域。对于函数来说,词法作用域是在函数定义时就已经确定了,与函数是否被调用无关。通过作用域,可以知道作用域范围内的变量和函数有哪些,却不知道变量的值是什么。所以作用域是静态的
[注意]通过eval()函数和with语句可以对作用域进行动态修改
【2】对于函数来说,执行环境是在函数调用时确定的,执行环境包含作用域内所有变量和函数的值。在同一作用域下,不同的调用(如传递不同的参数)会产生不同的执行环境,从而产生不同的变量的值。所以执行环境是动态的
深入理解javascript作用域系列第五篇的更多相关文章
- 深入理解javascript作用域系列第五篇——一张图理解执行环境和作用域
× 目录 [1]图示 [2]概念 [3]说明[4]总结 前面的话 对于执行环境(execution context)和作用域(scope)并不容易区分,甚至很多人认为它们就是一回事,只是高程和犀牛书关 ...
- 深入理解javascript作用域系列第四篇——块作用域
× 目录 [1]let [2]const [3]try 前面的话 尽管函数作用域是最常见的作用域单元,也是现行大多数javascript最普遍的设计方法,但其他类型的作用域单元也是存在的,并且通过使用 ...
- 深入理解javascript作用域系列第三篇——声明提升(hoisting)
× 目录 [1]变量 [2]函数 [3]优先 前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javasc ...
- 深入理解javascript作用域系列第三篇
前面的话 一般认为,javascript代码在执行时是由上到下一行一行执行的.但实际上这并不完全正确,主要是因为声明提升的存在.本文是深入理解javascript作用域系列第三篇——声明提升(hois ...
- 深入理解javascript作用域系列第四篇
前面的话 尽管函数作用域是最常见的作用域单元,也是现行大多数javascript最普遍的设计方法,但其他类型的作用域单元也是存在的,并且通过使用其他类型的作用域单元甚至可以实现维护起来更加优秀.简洁的 ...
- 深入理解javascript作用域系列第一篇——内部原理
× 目录 [1]编译 [2]执行 [3]查询[4]嵌套[5]异常[6]原理 前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域 ...
- 深入理解javascript作用域系列第一篇
前面的话 javascript拥有一套设计良好的规则来存储变量,并且之后可以方便地找到这些变量,这套规则被称为作用域.作用域貌似简单,实则复杂,由于作用域与this机制非常容易混淆,使得理解作用域的原 ...
- 深入理解javascript作用域系列第二篇——词法作用域和动态作用域
× 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...
- 深入理解javascript作用域系列第二篇
前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作 ...
随机推荐
- 如何在AngularJS渲染后再加载JS
http://www.itnose.net/detail/6100484.html app.directive('repeatDone', function () { return function ...
- [php]手动搭建php开发环境(排错)
前提:针对自己的系统下载相应的php.apache.mysql,安装完毕后按照以下去配置httpd.conf和php.ini 本人用的是php5.6.4和apache2.4.4 一.Apache : ...
- 【CodeForces】585 E. Present for Vitalik the Philatelist
[题目]E. Present for Vitalik the Philatelist [题意]给定n个数字,定义一种合法方案为选择一个数字Aa,选择另外一些数字Abi,令g=gcd(Ab1...Abx ...
- css各种姿势的水平居中
首先是最常用的,利用margin属性的auto来进行水平居中 margin: 0 auto; 其中0是指上下的margin,auto是左右的margin,margin这个属性的简写是按顺时针走的,也就 ...
- 记一次诡异的bug调试——————关于JDK1.7和JDK1.8中HashSet的hash(key)算法的区别
现象: 测试提了一个bug,我完全复现不了,但是最吊诡的是在其他人的机器上都可以复现.起初以为是SVN合并后出现的冲突,后来经过对比法排查: step 1: 我本地开两个jetty,一个跑合并之前的版 ...
- Sublime之插件的安装(一)
由于最近刚换了一个工作,所以决定重新申请一个blog,把工作当中遇到的一些问题记录下来,方便自己下次忘记,也希望能与一起需要的小伙伴一起共勉. 如果有不同的观点或者是不同的看法,大家都可以畅谈,我一直 ...
- CAD启动提示"是否关闭命令行"不管点击什么,都会闪退的解决办法
AutoCAD splash screen starts up and then closes AutoCAD splash screen starts up and then closes (S ...
- VMware 克隆多台Linux机器并配置IP的方法
我们首先要知道 VMware 三种网络模式的区别. ①.Bridged(桥接模式):就是将主机网卡与虚拟机虚拟的网卡利用虚拟网桥进行通信.在桥接的作用下,类似于把物理主机虚拟为一个交换机,所有桥接设置 ...
- spring boot 自定义属性覆盖application文件属性
参考 Spring boot源码分析-ApplicationListener应用环境: https://blog.csdn.net/jamet/article/details/78042486 加载a ...
- Python3 反射及常用的方法
反射就是通过字符串映射或修改程序运行时的状态.属性.方法 有四个常用方法: hasattr(obj,name_str) 判断一个obj对象是否有对应name_str的方法 getattr(obj,na ...