执行上下文

函数表达式和函数声明

  1. 1.
  2. console.log(a); // ReferenceError: a is not defined
  3. // ReferenceError(引用错误)对象表明一个不存在的变量被引用。
  4. 2.
  5. console.log(a); // undefined
  6. var a;
  7. 3.
  8. console.log(a); // undefined
  9. var a = 10;
  10. 4.
  11. var a = 10;
  12. console.log(a); //

在一段js代码拿过来真正一句一句运行之前,浏览器已经做了一些“准备工作”,其中就包括对变量的声明,而不是赋值。变量赋值是在赋值语句执行的时候进行的。可用下图模拟:第一句报错,a未定义,很正常。第二句、第三句输出都是undefined,说明浏览器在执行console.log(a)时,已经知道了a是undefined,但却不知道a是10(第三句中)。

接下来的这段代码需要注意代码注释中的两个名词——“函数表达式”和“函数声明”。虽然两者都很常用,但是这两者在“准备工作”时,却是两种待遇。

“准备工作”

  1. 1.
  2. console.log(a); // ReferenceError: a is not defined
  3. // ReferenceError(引用错误)对象表明一个不存在的变量被引用。
  4. 2.
  5. console.log(a); // undefined
  6. var a;
  7. 3.
  8. console.log(a); // undefined
  9. var a = 10;
  10. 4.
  11. var a = 10;
  12. console.log(a); //

在“准备工作”中,对待函数表达式就像对待“ var a = 10 ”这样的变量一样,只是声明。看以上代码。“函数声明”时我们看到了第二种情况,而“函数表达式”时我们看到了第一种情况。

而对待函数声明时,却把函数整个赋值了。

总结一下,在“准备工作”中完成了哪些工作:

  1. 变量、函数表达式——变量声明,默认赋值为undefined;
  2. this——赋值;
  3. 函数声明——把函数整个赋值;

这三种数据的准备情况我们称之为“执行上下文”或者“执行上下文环境”。

  1. function fn(x) {
  2. console.log(arguments); //Arguments { 0: 10, 等 2 项… }
  3. console.log(x); //
  4. }
  5. fn(10);

以上代码展示了在函数体的语句执行之前,arguments变量和函数的参数都已经被赋值。从这里可以看出,函数每被调用一次,都会产生一个新的执行上下文环境。因为不同的调用可能就会有不同的参数。

另外一点不同在于,函数在定义的时候(不是调用的时候),就已经确定了函数体内部自由变量的作用域。用一个例子说明一下:

  1. var a = 10;
  2.  
  3. function fn() {
  4. console.log(a); //a是自由变量
  5. //函数创建时,就确定了a要取值的作用域
  6. }
  7.  
  8. function bar() {
  9. var a = 20;
  10. fn(); //打印10不是20
  11. }
  12. bar(fn); //

执行上下文栈给执行上下文环境下一个通俗的定义——在执行代码之前,把将要用到的所有的变量都事先拿出来,有的直接赋值了,有的先用undefined占个空。

执行全局代码时,会产生一个执行上下文环境,每次调用函数都又会产生执行上下文环境。当函数调用完成时,这个上下文环境以及其中的数据都会被消除,再重新回到全局上下文环境。处于活动状态的执行上下文环境只有一个。

其实这是一个压栈出栈的过程——执行上下文栈。

  1. 1 var a = 10, //1.进入全局上下文环境
  2. 2 fn,
  3. 3 bar = function(x) {
  4. 4 var b = 5;
  5. 5 fn(x + b); //3.进入fn函数上下文环境
  6. 6 };
  7. 7
  8. 8 fn = function(y) {
  9. 9 var c = 5;
  10. 10 console.log(y + c);
  11. 11 }
  12. 12
  13. 13 bar(10); //2.进入bar函数上下文环境

在执行代码之前,首先将创建全局上下文环境。

全局 上下文环境
a undefined
fn undefined
bar undefined
this window

然后是代码执行。代码执行到第12行之前,上下文环境中的变量都在执行过程中被赋值。

全局 上下文环境
a 10
fn function
bar function
this window

执行到第13行,调用bar函数。

跳转到bar函数内部,执行函数体语句之前,会创建一个新的执行上下文环境。
全局 | 上下文环境
—|—
b | undefined
x | 10
arguments | [10]
this | window

并将这个执行上下文环境压栈,设置为活动状态。


执行到第5行,又调用了fn函数。进入fn函数,在执行函数体语句之前,会创建fn函数的执行上下文环境,并压栈,设置为活动状态。

待第5行执行完毕,即fn函数执行完毕后,此次调用fn所生成的上下文环境出栈,并且被销毁(已经用完了,就要及时销毁,释放内存)。

同理,待第13行执行完毕,即bar函数执行完毕后,调用bar函数所生成的上下文环境出栈,并且被销毁(已经用完了,就要及时销毁,释放内存)。

好了,给大家介绍了一段简短代码的执行上下文环境的变化过程,一个完整的闭环。其中上下文环境的变量赋值过程我省略了许多,因为那些并不难,一看就知道。

作用域

基础认识

“javascript没有块级作用域”。所谓“块”,就是大括号“{}”中间的语句。

比如一个if语句

  1. var i = 10;
  2. if (i > 1) {
  3. var name = "yzh";
  4. }
  5. console.log(name); //yzh
  1. for (var i = 0; i < 10; i++) {
  2.  
  3. }
  4. console.log(i); //

for语句

我们在编写代码的时候,不要在“块”里面声明变量,要在代码的一开始就声明好了。以避免发生歧义

  1. var i;
  2. for (i = 0; i < 10; i++) {
  3.  
  4. }
  5. console.log(i);

我们在声明变量时,全局代码要在代码前端声明,函数中要在函数体一开始就声明好。除了这两个地方,其他地方都不要出现变量声明。而且建议用“单var”形式你光知道“javascript没有块级作用域”是完全不够的,你需要知道的是——javascript除了全局作用域之外,只有函数可以创建的作用域。

概念

如上图,全局代码和fn、bar两个函数都会形成一个作用域。而且,作用域有上下级的关系,上下级关系的确定就看函数是在哪个作用域下创建的。例如,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突

例如以上代码中,三个作用域下都声明了“a”这个变量,但是他们不会有冲突。各自的作用域下,用各自的“a”。

作用域和上下文环境

如上图,我们在上文中已经介绍了,除了全局作用域之外

每个函数都会创建自己的作用域,作用域在函数定义时就已经确定了。而不是在函数调用时确定。

下面我们将按照程序执行的顺序,一步一步把各个上下文环境加上

第一步,在加载程序时,已经确定了全局上下文环境,并随着程序的执行而对变量就行赋值。

第二步,程序执行到第27行,调用fn(10),此时生成此次调用fn函数时的上下文环境,压栈,并将此上下文环境设置为活动状态。

第三步,执行到第23行时,调用bar(100),生成此次调用的上下文环境,压栈,并设置为活动状态。

第四步,执行完第23行,bar(100)调用完成。则bar(100)上下文环境被销毁。接着执行第24行,调用bar(200),则又生成bar(200)的上下文环境,压栈,设置为活动状态。

第五步,执行完第24行,则bar(200)调用结束,其上下文环境被销毁。此时会回到fn(10)上下文环境,变为活动状态。

第六步,执行完第27行代码,fn(10)执行完成之后,fn(10)上下文环境被销毁,全局上下文环境又回到活动状态。

最后我们可以把以上这几个图片连接起来看看。

作用域只是一个“地盘”,一个抽象的概念,其中没有变量。要通过作用域对应的执行上下文环境来获取变量的值。

同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。所以,作用域中变量的值是在执行过程中产生的确定的,而作用域却是在函数创建时就确定了。

如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中寻找变量的值。

自由变量

在A作用域中使用的变量x,却没有在A作用域中声明(即在其他作用域中声明的),对于A作用域来说,x就是一个自由变量。
例:

  1. var x = 50;
  2.  
  3. function fn() {
  4. var b = 20;
  5. console.log(x + b);
  6. }
  7. fn(); //
  1. var x = 50;
  2.  
  3. function fn() {
  4. console.log(x);
  5. }
  6.  
  7. function show(f) {
  8. var x = 20;
  9. (function() {
  10. f(); //50 不是20
  11. })();
  12. }
  13. show(fn); //50 不是20

在调用fn()函数时,函数体中第6行。取b的值就直接可以在fn作用域中取,因为b就是在这里定义的。而取x的值时,就需要到另一个作用域中取。到哪个作用域中取呢?
有人说过要到父作用域中取,其实有时候这种解释会产生歧义
例如:

不要在用以上说法了。相比而言,用这句话描述会更加贴切——要到创建这个函数的那个作用域中取值——是“创建”,而不是“调用”,切记切记——其实这就是所谓的“静态作用域”。

对于本文第一段代码,在fn函数中,取自由变量x的值时,要到哪个作用域中取?——要到创建fn函数的那个作用域中取——无论fn函数将在哪里调用

上面描述的只是跨一步作用域去寻找。

如果跨了一步,还没找到呢?——接着跨!——一直跨到全局作用域为止。要是在全局作用域中都没有找到,那就是真的没有了。

这个一步一步“跨”的路线,我们称之为——作用域链。

全局环境 changeColor()的局部环境 swapColors()的局部环境
变量color 变量anotherColor 变量tempColor
函数changeColor() 函数swapColors()

以上代码共涉及3个执行环境:全局环境、changeColor()的局部环境和swapColors()的局部环境。

内部环境可以通过作用域链访问所有的外部环境,但外部环境不能访问内部环境中的任何变量和函数。

每个环境都可以向上搜索作用域链,以查询变量和函数名;但任何环境都不能通过向下搜索作用域链而进入另一个执行环境

最后这个例子是从书上找到的,比较经典和简单。

  1. var color = "blue";
  2.  
  3. function changeColor() {
  4. var anotherColor = "red";
  5.  
  6. function swapColors() {
  7. var tempColor = anotherColor;
  8. anotherColor = color;
  9. color = tempColor;
  10.  
  11. //这里可以访问color,anotherColor和tempColor
  12. }
  13.  
  14. //这里可以访问color和anotherColor,但不能访问tempColor
  15. swapColors();
  16. }
  17.  
  18. changeColor(); //注释后alert显示为blue
  19.  
  20. //这里只能访问color
  21. alert("Color is now " + color); //red

关于js作用域问题详解的更多相关文章

  1. ES6,ES2105核心功能一览,js新特性详解

    ES6,ES2105核心功能一览,js新特性详解 过去几年 JavaScript 发生了很大的变化.ES6(ECMAScript 6.ES2105)是 JavaScript 语言的新标准,2015 年 ...

  2. Vue.js 数据绑定语法详解

    Vue.js 数据绑定语法详解 一.总结 一句话总结:Vue.js 的模板是基于 DOM 实现的.这意味着所有的 Vue.js 模板都是可解析的有效的 HTML,且通过一些特殊的特性做了增强.Vue ...

  3. JS变量对象详解

    JS变量对象详解 开年之后工作热情一直不是很高,这几天一直处于消极怠工状态.早上不想起床,起床了不想上班.明明放假之前工作热情还一直很高,一直心心念念的想把小程序项目怼出来,结果休假回来之后画风完全不 ...

  4. 《Node.js开发实战详解》学习笔记

    <Node.js开发实战详解>学习笔记 ——持续更新中 一.NodeJS设计模式 1 . 单例模式 顾名思义,单例就是保证一个类只有一个实例,实现的方法是,先判断实例是否存在,如果存在则直 ...

  5. Js apply 方法 详解

    Js apply方法详解 我在一开始看到JavaScript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这 ...

  6. Js apply()使用详解

    Js apply方法详解 我在一开始看到javascript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这 ...

  7. Js apply方法详解,及其apply()方法的妙用

    Js apply方法详解 我在一开始看到javascript的函数apply和call时,非常的模糊,看也看不懂,最近在网上看到一些文章对apply方法和call的一些示例,总算是看的有点眉目了,在这 ...

  8. “全栈2019”Java第一百零五章:匿名内部类覆盖作用域成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

  9. “全栈2019”Java第九十八章:局部内部类访问作用域成员详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...

随机推荐

  1. 小白使用Web Deploy在vs2015中发布到iis遇到的问题及操作流程

    整体流程详细参照:http://www.cnblogs.com/potential/p/3751426.html 问题1.未能连接到远程计算机,请确保在远程计算机上安装了 Web Deploy 并启动 ...

  2. POJ3463【次短路】

    转自:http://www.cnblogs.com/jackge/archive/2013/04/29/3051273.html 算法:最短路和次短路.Dijkstra算法.采用邻接表建图. 总结:不 ...

  3. CodeForces Canada Cup 2016【A,B,C,D】

    CodeForces 725A: 思路就是如果"最左"不是'>'这个了,那么这个右边的一定不可能到达左边了: 同理最右: CodeForces 725B: 有两个空姐,一个从 ...

  4. 基础篇-psql帮助命令

    \? psql命令帮助 \h sql语句帮助 \?常用命令 1.默认  \d 后面不跟参数,则显示当前数据库所有的表 2. \d  表名  ,则显示这个表的定义 3.\d 索引名  ,显示索引的信息 ...

  5. C#连接Sybase数据库,Anywhere 8

    数据库版本是Adaptive Server Anywhere 8 1.添加引用,程序集 iAnywhere.Data.AsaClient.dll文件在数据库的安装目录下,例如:C:\Program F ...

  6. element-ui + el-dialog + Vue.component 注册的富文本控件 第二次及以后打开dialog出现问题解决方法

    自定控件 添加属性  v-if="dialogVisible" <el-dialog title="" :visible.sync="dialo ...

  7. 【Codeforces1111D_CF1111D】Destroy the Colony(退背包_组合数学)

    题目: Codeforces1111D 翻译: [已提交至洛谷CF1111D] 有一个恶棍的聚居地由几个排成一排的洞穴组成,每一个洞穴恰好住着一个恶棍. 每种聚居地的分配方案可以记作一个长为偶数的字符 ...

  8. 为什么会出现lvs+nginx

    一.ngix(应用层 网络七层负载均衡) 1.异步转发,请求数据和相应数据都要经过ngix,ngix和客户端建立连接 2.轮询所有的tomcat服务器,保证请求成功或者最后一台tomcat服务器也请求 ...

  9. hibernate Day2 案例代码

    1.编写实体类Person package com.icss.pojo; public class Person { private int uid; private String uname; pr ...

  10. [github][https模式下提交记住密码]

    git版本 1.7.9以后 1.  开启 git config --global credential.helper cache 2. 设置时间 git config credential.helpe ...