javascript核心(二)

一、variable object

variable object以下简称为VO,VO是执行上下文相关的范围内的值。VO存储了在上下文内已经定义的变量和函数定义(function declarations)。注意函数的表达(function expressions)并不在VO中。VO是一个抽象的概念,在不同类型的上下文中,表现为使用不同的对象。例如:在全局上下文中VO是全局对象自身(global object itself)(这就是为什么我们能够通过全局对象的属性名字直接访问全局属性)

考虑下下面这个全局执行上下文的例子:

var foo = 10;
 
function bar() {} // function declaration, FD
(function baz() {}); // function expression, FE
 
console.log(
  this.foo == foo, // true
  window.bar == bar // true
);
 
console.log(baz); // ReferenceError, "baz" is not defined

全局上下文的VO拥有下面的属性:

我们再次看到像brz这种函数表达式不会包含在VO中的,这就是为什么当我们尝试在外面访问函数baz会有ReferenceError
注意,ECMA相对于其他语言(C、C++),只有函数创建一个新的范围。函数范围内定义的变量和内部函数在外部是不可见的,所以不会污染全局变量对象(global variable object)。

使用eval我们也会进入一个新的执行上下文,但是eval使用的是全局变量对象或者是调用者的VO。

函数和函数的VO是什么?在函数的上下文中,VO会被表示为AO(activation object)。

二、activation object

当调用者调用一个函数时,AO(activation object)会被创建。AO里面会有形参和arguments 对象(arguments object是一个形参的map,里面存放属性的下标和值)。接下来,在函数的上下文中AO被当成一个VO来使用(也就是说函数的VO和普通的VO一致,只不过除了变量和函数定义之外,他还存储了形参以及arguments 对象

function foo(x, y) {
  var z = 30;
  function bar() {} // FD
  (function baz() {}); // FE
}
 
foo(10, 20);


上面的那段代码在函数上下文中VO为:

同样函数表达baz不被包含在AO/VO中。

接下来,我们进入下一部分。大家都知道,在ECMAScript中我们可以使用内部函数,在内部函数中我们可以访问父函数或者全局上下文中的变量。就像我们把VO作为上下文中的Scope Object,和原型链相类似的还有一个范围链(scope chain)。

三、Scope chain

A scope chain 出现在代码上下文中的标示符所组成的对象列表(a list of objects that are searched for identifiers appear in the code of the context.)。

规则和原型链基本类似:如果一个变量在当前范围(VO/AO)内未找到,则继续搜索父亲的VO,直到顶端。

考虑到上下文,标示符(identifiers)是指:变量的名字、函数的定义、形参等等。当一个函数引用到代码里面的一个标示符,而这个标示符不是local 变量(或者local 函数、形参),这种变量被称之为free variable。为了寻找到这些free variables,scope chain就会被使用到。

一般情况下:scope chain是一个所有父亲VO以及函数自身的VO/AO对象列表。当然,scope chain还包含一些其他的,例如在代码执行中动态添加的内容(with-objects 、catch-clauses)

当查找一个标示符,scope chain从AO开始,一直查找到最顶端。

var x = 10;
 
(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;
    // "x" and "y" are "free variables"
    // and are found in the next (after
    // bar's activation object) object
    // of the bar's scope chain
    console.log(x + y + z);
  })();
})();

我们假设通过隐式属性__parent__ 来访问scope chain中的下一个对象(在ES5中为outer link)。通过下图来表示出上面代码(父亲的VO被保存在函数的[[Scope]] 属性中)。

在代码的执行过程中,scope chain会随着with语句和catch子句对象而增加。既然这些对象也是简单的对象,他们也有原型链。这就导致了scope会查找2个分支:(1)scope chain被搜索 然后(2)从每一个scope chain的link进入link的原型链(on every scope chain’s link — into the depth of the link’s prototype chain )。

Object.prototype.x = 10;
 
var w = 20;
var y = 30;
 
// in SpiderMonkey global object
// i.e. variable object of the global
// context inherits from "Object.prototype",
// so we may refer "not defined global
// variable x", which is found in
// the prototype chain
 
console.log(x); // 10
 
(function foo() {
 
  // "foo" local variables
  var w = 40;
  var x = 100;
 
  // "x" is found in the
  // "Object.prototype", because
  // {z: 50} inherits from it
 
  with ({z: 50}) {
    console.log(w, x, y , z); // 40, 10, 30, 50
  }
 
  // after "with" object is removed
  // from the scope chain, "x" is
  // again found in the AO of "foo" context;
  // variable "w" is also local
  console.log(x, w); // 100, 40
 
  // and that's how we may refer
  // shadowed global "w" variable in
  // the browser host environment
  console.log(window.w); // 20
 
})();

我们会得到下面的结构(在使用__parent__link之前,首先会考虑__proto__  chain)

注意不是所有全局对象的实现都会从Object.prototype继承。上图的例子可以用SpiderMonkey测试。

在所有的父亲对象都存在的情况下,从内部函数得到外部的数据并没有什么特别,我们只需要查询scope chain即可。可是,就像我们上面提到的,当上下文结束之后,所有的状态和自身都会被销毁。同时内部函数可能会从外部函数中被返回。再者,这个返回的函数在之后可能会在另一个上下文中被激活。但是这时候那些free variable已经消失不见了,由此引出另一个问题闭包,闭包直接就涉及到scope chain这个概念。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值