变量对象+作用域链+闭包

下文根据汤姆大叔的深入javascript系列文章删改,如果想深入理解请阅读汤姆大叔的系列文章。
http://www.cnblogs.com/TomXu/...

变量对象

初步介绍


变量对象(缩写为VO)是一个与执行上下文相关的特殊对象,它存储着在上下文中声明的以下内容:
    变量 (var, 变量声明);
    函数声明 (FunctionDeclaration, 缩写为FD);
    函数的形参

我们可以用普通的ECMAScript对象来表示一个变量对象:

VO = {};

VO是执行上下文的属性(property),所以:

activeExecutionContext = {
  VO: {
    // 上下文数据(var, FD, function arguments)
  }
};

只有全局上下文的变量对象允许通过VO的属性名称来间接访问(因为在全局上下文里,全局对象自身就是变量对象),在其它上下文中是不能直接访问VO对象的,因为它只是内部机制的一个实现。

全局上下文中的变量对象

只有全局上下文的变量对象允许通过VO的属性名称来间接访问

在全局上下文中,有

VO(globalContext) === global;

因为我们在全局上下文中声明的变量等都是存在全局的变量对象中,而在全局上下文中的全局变量对象又是全局对象本身。所以我们可以通过VO的属性名称间接访问

var a = new String(‘test‘);

alert(a); // 直接访问,在VO(globalContext)里找到:"test"

alert(window[‘a‘]); // 间接通过global访问:global === VO(globalContext): "test"
alert(a === this.a); // true

var aKey = ‘a‘;
alert(window[aKey]); // 间接通过动态属性名称访问:"test"

函数上下文中的变量对象

在函数执行上下文中,VO是不能直接访问的,此时由活动对象(activation object,缩写为AO)扮演VO的角色。

VO(functionContext) === AO;

在理解函数上下文中的变量对象时,我们通过处理上下文代码的2个阶段来进行理解

1.进入执行上下文
2.执行代码

进入执行上下文

进入执行上文文的时候,也即是代码执行之前,此时VO包含了下列属性

函数形参
函数声明
变量声明

其中,函数声明的等级最高,然后是函数形参,最后才是变量声明。越高等级的声明可以覆盖低等级的声明。

执行代码

这个周期内,AO/VO已经拥有了属性(不过,并不是所有的属性都有值,大部分属性的值还是系统默认的初始值undefined )。这个时候会进行赋值操作以及执行代码。

alert(x); // function

var x = 10;
alert(x); // 10

x = 20;

function x() {};

alert(x); // 20

在进入上下文阶段,由于函数具有最高的级别,所以第一次alert(x)输出的是函数。之后进行变量赋值,分别alert 10 20。

function bar (x){
    alert(x);
    var x = 2;
}
bar(3); //3

由于形参声明比变量声明级别高,所以alert(3),因为在进入执行上下文时变量无法覆盖形参声明,所以输出的是3而不是undefined。

不使用var可以声明一个全局变量,这句话是错误的。

alert(a); // undefined
alert(b); // "b" 没有声明,报错

b = 10;
var a = 20;

作用域链

函数上下文的作用域链在函数调用时创建的,包含活动对象和这个函数内部的[[scope]]属性。函数上下文包括以下内容:

activeExecutionContext = {
    VO: {...}, // or AO
    this: thisValue,
    Scope: [ // Scope chain
      // 所有变量对象的列表
      // for identifiers lookup
    ]
};

其scope定义如下:

Scope = AO + [[Scope]]

[[scope]]是所有父变量对象的层级链,处于当前函数上下文之上,在函数创建时存于其中。

注意这重要的一点--[[scope]]在函数创建时被存储--静态(不变的),永远永远,直至函数销毁。即:函数可以永不调用,但[[scope]]属性已经写入,并存储在函数对象中。

另外一个需要考虑的是--与作用域链对比,[[scope]]是函数的一个属性而不是上下文。

因此我个人的理解是作用域链应该是函数本身的活动对象+父级的变量对象。其中函数本身的活动对象总是排在第一位,在寻找标识符的时候,如果在当前活动对象找不到,那么会遍历作用域链上的父级变量对象。其中[[scope]]在函数创建时被存储,与函数共存亡。

var x = 10;

function foo() {
  alert(x);
}

(function () {
  var x = 20;
  foo(); // 10, but not 20
})();

说明函数的作用域链在函数创建的时候就已经定义好了,是静态的,不因为调用的时候而改变。

闭包

作用域链的加深理解

var firstClosure;
var secondClosure;

function foo() {

  var x = 1;

  firstClosure = function () { return ++x; };
  secondClosure = function () { return --x; };

  x = 2; // 影响 AO["x"], 在2个闭包公有的[[Scope]]中

  alert(firstClosure()); // 3, 通过第一个闭包的[[Scope]]
}

foo();

alert(firstClosure()); // 4
alert(secondClosure()); // 3

firstClosure和secondClosure两个函数创建的时候,内部的变量x都是从父级函数foo的变量对象x中引用,所以其实两个函数都是共享一个作用域,因此导致x变量共通了。

经典闭包

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = function () {
    alert(k);
  };
}

data[0](); // 3, 而不是0
data[1](); // 3, 而不是1
data[2](); // 3, 而不是2

解释跟上面类似。function在创建的时候,内部的变量k通过访问作用域链即是父级的变量对象k拿到,而当函数被调用的时候,for循环早已执行完毕,此时的K是3,所以三个函数调用的时候输出的值都为3。

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = (function _helper(x) {
    return function () {
      alert(x);
    };
  })(k); // 传入"k"值
}

// 现在结果是正确的了
data[0](); // 0
data[1](); // 1
data[2](); // 2

创建了一个匿名函数,通过把k变量作为参数传进去,这样在执行function的时候,由于内部的形参能够访问到k变量,所以无需到父级作用域链上进行寻找,因此最后输出达到预期目的。

闭包的理论定义

这里说明一下,开发人员经常错误将闭包简化理解成从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。

ECMAScript中,闭包指的是:

1.从理论角度:所有的函数。因为它们都在创建的时候就将上层上下文的数据保存起来了。哪怕是简单的全局变量也是如此,因为函数中访问全局变量就相当于是在访问自由变量,这个时候使用最外层的作用域。

2.从实践角度:以下函数才算是闭包:
    1.即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
    2.在代码中引用了自由变量

原文地址:https://www.cnblogs.com/jlfw/p/12536459.html

时间: 2024-11-08 17:44:10

变量对象+作用域链+闭包的相关文章

[从jQuery看JavaScript]-变量与作用域链

jQuery片段: [javascript] view plaincopy var // Will speed up references to window, and allows munging its name. window = this, // Will speed up references to undefined, and allows munging its name. undefined, // Map over jQuery in case of overwrite _jQ

JavaScript this 局部变量全局变量 作用域 作用域链 闭包

从阮老师博客的一道测试题说起: 代码段一: var name = "The Window"; var object = { name : "My Object", getNameFunc : function(){ return function(){ return this.name; }; } }; alert(object.getNameFunc()()); 代码段二: var name = "The Window"; var object

作用域 作用域链 闭包 思想 JS/C++比较

首先,我说的比较是指JS中这种思想/实现方式与C++编译原理中思想/实现方式的比较 参考链接:(比较易懂的介绍,我主要写个人理解) 作用域链: http://www.cnblogs.com/dolphinX/p/3280876.html 闭包:http://kb.cnblogs.com/page/110782/ 个人理解: 作用域链: 在JS中,function也是一种object的实例. 作用域的概念必须已经知晓. 作用域链:用于标识符解析:确定数据的存储位置以及数据作用域(数据访问).(应该

执行环境 作用域 作用域链 闭包的理解

1.首先 当一个变量或者函数被声明的时候 它的执行环境便被确认 , 执行环境定义了变量和函数有权访问的其他数据,决定了他们各自的行为, 而作用域就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期 每次进入一个新的执行环境,都会创建一个用于搜索变量和函数的作用域链.作用域链是函数被创建的作用域中对象的集合.作用域链可以保证对执行环境有权访问的所有变量和函数的有序访问. 作用域链的最前端始终是当前执行的代码所在环境的变量对象(如果该环境是函数,则将其活动对象作为变量对象),下一个变量对象

javascript-词法作用域规则-作用域链-闭包运用学习心得

虽然在平时貌似,很习以为常的一些用法但是真要弄清这几个概念的时候,确实费了很大功夫,现在虽然不能说明白但总算有了一些心得.好吧下面直接开始 注本文(*)为相关链接 例子1.1 词法作用域规则:函数的嵌套关系是定义时决定的,而非调用时决定的,即词法作用域,即嵌套关系是由词法分析时确定的,而运行时决定. (*)http://blog.csdn.net/zzulp/article/details/8144520 (*)http://www.cnblogs.com/lhb25/archive/2011/

变量对象、作用域链和This

变量对象 作用域链 This 整理自:https://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html 系列文章中变量对象,作用域链和this三篇文章 原文地址:https://www.cnblogs.com/shaunyang/p/10348640.html

javascript 执行环境,变量对象,作用域链

前言 这几天在看<javascript高级程序设计>,看到执行环境和作用域链的时候,就有些模糊了.书中还是讲的不够具体. 通过上网查资料,特来总结,以备回顾和修正. 要讲的依次为: EC(执行环境或者执行上下文,Execution Context) ECS(执行环境栈Execution Context Stack) VO(变量对象,Variable Object)|AO(活动对象,Active Object) scope chain(作用域链)和[[scope]]属性 EC 每当控制器到达EC

执行环境、变量对象和作用域链

执行环境又称执行上下文,英文缩写是EC(Execution Context),每当执行流转到可执行代码时,即会进入一个执行环境.在JavaScript中,执行环境分三种: 全局执行环境 — 这个是最外围的代码执行环境,一旦代码被载入,引擎最先进入的就是这个环境.在浏览器中,全局环境就是window对象,一次所有全局属性和函数都是作为window对象的属性和方法创建的.全局执行环境直到应用程序退出时才会被销毁. 函数执行环境 — 当执行一个函数时,JavaScript引擎进入执行环境.某个执行环境

js中的作用域和作用域链

作用域1. 全局作用域2. 函数作用域这里扯出来下js的函数声明和变量声明提升,直接来两段代码 if (!a in window) { var a = 1; } console.log(a) //undefined 嗯,为什么呢?因为var声明的变量会变量声明提升,所以相当于执行if判断的时候a变量已经声明过了,而此时a是一个全局变量既是window对象的一个属性,所以这里压根没有进if判断,所以这里打印出来的是undefined再来一段坑坑的代码 (function (){ var a = b