彻底理解js的执行上下文,以及变量对象

在js中,执行上下文(Execution Context)是非常重要的一种对象,它保存着函数执行所需的重要信息,其中有三个属性:变量对象(variable object)作用域链(scope chain)this指针(this value),它们影响着变量的解析变量作用域函数this的指向


上下文栈(Execution Context Stack)

js执行的时候会维护一个上下文栈,每次开始执行一个函数之前,js都要创建一个上下文对象,并将其压入上下文栈中。因此,**当前正在执行的函数的上下文(简称当前上下文)**总是在栈顶,这个函数一执行完,上下文就会从栈中弹出。

代码开始运行时,栈顶会先放入一个全局上下文,全局上下文同样有变量对象,作用域链和this指针。

举个例子:

1function fun2() { 2 var b = 222; 3} 4function fun1() { 5 var a = 111; 6 fun2(); 7} 8fun1(); 9c = 333;
  1. 开始执行时:
    栈顶:全局上下文
  2. 代码执行到fun1();时,创建fun1的上下文,压入栈。这时栈变成:
    栈顶:fun1上下文 / 全局上下文
  3. 代码执行到fun2();时,创建fun2的上下文,压入栈。这时栈变成:
    栈顶:fun2上下文 / fun1上下文 / 全局上下文
  4. fun2执行完毕,fun2上下文弹出:
    栈顶:fun1上下文 / 全局上下文
    (这时执行权回到fun1内,栈顶也恰好回到了fun1上下文。可见这种机制能保证正在执行的函数的上下文总是在栈顶)
  5. fun1执行完毕,fun1上下文弹出,执行权回到全局区域:
    栈顶:全局上下文
  6. 所有代码都执行完毕,全局上下文弹出,栈为空。

执行上下文.变量对象(Variable Object)

我们已经说过,每次**执行(注意是执行而不是声明!)**一个函数之前,执行引擎都会创建一个上下文对象。创建上下文对象的时候,就会创建它的一个重要属性:变量对象。
创建变量对象的过程是这样:

  1. 建立arguments对象:属性名是'0'、'1'、'2'.....,属性值就是实际传入的参数。此外arguments.length是实际参数的个数
  2. 找到这个将要执行的函数内的所有函数声明,储存在变量对象中,属性名就是函数名,属性值就是函数的引用(所在的内存地址)。如果有多个同名的函数声明,后出现的函数覆盖前面的属性值。
  3. 找到这个将要执行的函数内的所有变量声明,储存在变量对象中,属性名就是变量名,属性值是undefined

还有一个概念叫做活动对象(activation object),活动对象其实和变量对象是同一个东西在不同时期的两种叫法。函数还未开始执行(创建上下文的期间)时叫变量对象,函数开始执行以后就叫活动对象。


变量对象让js有变量提升的特性

原来,在js执行函数之前,会先扫描一遍代码,将变量、函数声明都放到变量对象中。当执行函数时如果遇到一个变量、函数名,就会到活动对象中去找,发现有对应的属性名,就可以从变量对象中取出它的属性值来使用,而不用等到那一句声明语句之后!
例子:

1function fun1(var arg) { 2 // 创建变量对象:{arg:987, fun2:fun2的地址, a:undefinded} 3 console.log(a); // 打印undefinded,因为活动对象中有键值对:a:undefinded。 4 var a = 111; // 如果将这一语句删除,上一句会直接报错! 5 console.log(a); // 打印111,因为活动对象中有键值对:a:111 6 fun2(); // 打印in fun2! 因为活动对象中有键值对:fun2:某个内存地址 7 return; // 即使是在return之后的声明,也会被放入变量对象! 8 function fun2() { 9 console.log('in fun2!'); 10 } 11} 12fun1(987); 13// 输出为: 14// undefined 15// 111 16// in fun2!

为什么js不是块级作用域

这通过变量对象就可以解释了。因为只要在同一个函数中声明,所有变量都会保存在同一个变量对象中!所以在代码块中声明变量和在代码块外声明变量当然没有区别!


还记得我们刚才说“代码开始运行时,栈顶会先放入一个全局上下文”吗?在浏览器中,全局上下文的变量对象就是全局对象(就是window对象)!这就可以解释以下代码:

1// 执行函数之前发现a的声明,将其加入window对象,设为undefined 2var a = 111; // 在活动对象(也就是window)中将a赋值为111 3console.log(window.a); // 打印111
1window.a = 111; 2console.log(a); // 打印111,因为在活动对象(也就是window)中找到了a:111

经过测试,Node.js全局上下文的变量对象不是全局对象(global)。

1var a = 111; 2console.log(global.a); // 打印undefined
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )