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

御弟哥哥 等级 623 0 0

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


上下文栈(Execution Context Stack)

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

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

举个例子:

function fun2() {
    var b = 222;
}
function fun1() {
    var a = 111;
    fun2();
}
fun1();
c = 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执行函数之前,会先扫描一遍代码,将变量、函数声明都放到变量对象中。当执行函数时如果遇到一个变量、函数名,就会到活动对象中去找,发现有对应的属性名,就可以从变量对象中取出它的属性值来使用,而不用等到那一句声明语句之后!
例子:

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

为什么js不是块级作用域

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


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

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

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

var a = 111;
console.log(global.a);  // 打印undefined 
收藏
评论区

相关推荐

项目实战之---AES 加密
ajax/index.js import axiosApi from '../js/fetch'; import { baseUrl, headerParams } from '../js/baseUrl'; // import引用AES源码js import CryptoJS from 'cryptojs/cryptojs'; console.lo
前端 - 常见的异常捕获方法
前端异常捕获在ES3之前js代码执行的过程中,一旦出现错误,整个js代码都会停止执行,这样就显的代码非常的不健壮。从ES3开始,js也提供了类似的异常处理机制,从而让js代码变的更健壮,程序执行的过程中出现了异常,也可以让程序具有了一部分的异常恢复能力。js异常的特点是,出现不会导致JS引擎崩溃,最多只会终止当前执行的任务。回归正题,我们该如何在程序异常发生
CSS禁用鼠标拖拽选中内容
chrome  -webkit-user-select:none firxfox  -moz-user-select:none IE需要使用JS的onSelected事件了。 JS代码             dom.style.MozUserSelect = 'none';//fixrox禁止选择的JS脚本             dom.st
JS
[在线预览](https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fjsfiddle.net%2F1010543618%2Ffyf913t0%2F) 使用《Web API 接口》的《MutationObserver》 [MutationObserver](https://www.oschina.
JS导出页面为PDF文件,该如何操作?来看一眼就明白啦!
废话不多说,直接上代码。 1.资源文件或依赖 <script type="text/javascript" src="https://my.oschina.net//u/4265132/blog/4054317/js/canvg2.js"></script><script type="text/javascript" src="https://m
1、webpack入门例子。
在下面的例子中,简单使用webpack打包一个js。并且把这个js引用的其他js、json数据一起打包进去。 官网:[http://webpack.github.io/](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fwebpack.github.io%2F) 英文文档:  [http
Echarts全国地图二级钻取
<html> <head> <script src="js/jquery-1.9.1.min.js"></script> <script src="js/echarts-all-3.js"></script> <script src="js/china.js"></script> </h
JQValidate使用说明
JQuery Validate使用总结: 一、导入js库 <script src="../js/jquery.js" type="text/javascript"></script> <script src="../js/jquery.validate.js" type="text/javascript"></script> 二、默认校验规则
JS前端图形化插件之利器Gojs组件(php中文网)
JS前端图形化插件之利器Gojs组件(php中文网) ========================== 一、总结 ---- ### 一句话总结:php中文网我可以好好走一波 二、JS前端图形化插件之利器Gojs组件 -------------------- 参考: JS前端图形化插件之利器Gojs组件-js教程-PHP中文网 http://
JavaScaript学习笔记第(一)
js由三部分组成,分别是ECMAScript、DOM、BOM 其中ECMAScript规定了js的语法 js是一门解释型语言、脚本语言、动态类型语言、基于对象语言 书写js代码和CSS一样,有三个书写的地方,第一个是使用<script>标签,再<sccript>标签中书写js代码,标签一般都在body标签中的末尾,第二个地方是书写再结构里,html标签
JavaScript 两个叹号含义
先起个例子吧~  这个用的是谋智的js引擎monkey spider  darion.yaphet@localhost.localdomain:/home/darion.yaphet> js               js> var i; js> print(i) undefined js>  js> va
JavaScript八张思维导图
**![](https://oscimg.oschina.net/oscnet/17104907-a0f0-4b40-ac9d-9c40d9b13157.jpg)** **目录** * JS基本概念 * JS操作符 * JS基本语句 * JS数组用法 * Date用法 * JS
Javascript 是如何体现继承的 ?
js继承的概念 js里常用的如下两种继承方式: 原型链继承(对象间的继承) 类式继承(构造函数间的继承)   由于js不像java那样是真正面向对象的语言,js是基于对象的,它没有类的概念。所以,要想实现继承,可以用js的原型prototype机制或者用apply和call方法去实现 在面向对象的语言中,我们使用类来创建一个自定义对象
React.createClass 、React.createElement、Component
react里面有几个需要区别开的函数 React.createClass 、React.createElement、Component 首选看一下在浏览器的下面写法: <div id="app"> </div> <script src="../js/react.js"></script> <scr
Springmvc异步上传文件
<script src="js/jquery.js" type="text/javascript"></script><script src="js/jquery.ext.js" type="text/javascript"></script><script src="js/jquery.form.js" type="text/javascript"