当前位置: 首页 > news >正文

长沙网站建设王道下拉惠/高端企业网站建设

长沙网站建设王道下拉惠,高端企业网站建设,app开发公司加盟,聊城做网站建设的公司JavaScript深入理解——变量对象(Variable object) 概要 我们总是会在程序中定义一些函数和变量,之后会使用这些函数和变量来构建我们的系统。 然而,对于解释器来说,它又是如何以及从哪里找到这些数据的(函数,变量)?当…

JavaScript深入理解——变量对象(Variable object)

概要

我们总是会在程序中定义一些函数和变量,之后会使用这些函数和变量来构建我们的系统。
然而,对于解释器来说,它又是如何以及从哪里找到这些数据的(函数,变量)?当引用一个对象的时候,在解释器内部又发生了什么? 我们大多数人都知道,变量和执行上下文是密切相关的:

var a = 10; // 全局上下文中的变量(function () {var b = 20; // 函数上下文中的局部变量
})();alert(a); // 10
alert(b); // "b" is not defined
复制代码

不仅如此,许多程序员也都知道,ECMAScript标准中指出独立的作用域只有通过“函数代码”(可执行代码类型中的一种)才能创建出来。比方说,与C/C++不同的是,在ECMAScript中for循环的代码块是无法创建本地上下文的:

for (var k in {a: 1, b: 2}) {alert(k);
}alert(k); // 尽管循环已经结束,但是变量“k”仍然在作用域中
复制代码

下面就来详细介绍下,当声明变量和函数的时候,究竟发生了什么。

数据声明

既然变量和执行上下文有关,那它就该知道数据存储在哪里以及如何获取。这种机制就称作变量对象:

A variable object (in abbreviated form — VO) is a special object related with an execution context and which stores:

  • variables (var, VariableDeclaration);
  • function declarations (FunctionDeclaration, in abbreviated form FD);
  • and function formal parameters declared in the context.
举个例子,可以用ECMAScript的对象来表示变量对象:
VO={}
复制代码
VO同时也是有一个执行上下文的属性:
activeExecutionContext = {VO: {// 上下文中的数据 (变量声明(var), 函数声明(FD), 函数形参(function arguments))}
};
复制代码
对变量的间接引用(通过VO的属性名)只允许发生在全局上下文中的变量对象上(全局对象本身就是变量对象,这部分会在后续作相应的介绍)。 对于其他的上下文而言,是无法直接引用VO的,因为VO是实现层的。

声明新的变量和函数的过程其实就是在VO中创建新的和变量以及函数名对应的属性和属性值的过程。

如下所示:

var a = 10;function test(x) {var b = 20;
};test(30);
复制代码
上述代码对应的变量对象则如下所示:
// 全局上下文中的变量对象
VO(globalContext) = {a: 10,test: 
};// “test”函数上下文中的变量对象
VO(test functionContext) = {x: 30,b: 20
};
复制代码
但是,在实现层(标准中定义的),变量对象只是一个抽象的概念。在实际执行上下文中,VO可能完全不叫VO,并且初始的结构也可能完全不同。

不同执行上下文中的变量对象

变量对象上的一些操作(比如:变量的初始化)和行为对于所有的执行上下文类型来说都一样的,从这一点来说,将变量对象表示成抽象的概念更加合适。函数上下文还能定义额外的与变量对象相关的信息
AbstractVO (generic behavior of the variable instantiation process)║╠══> GlobalContextVO║        (VO === this === global)║╚══> FunctionContextVO(VO === AO,  object and  are added)
复制代码
接下来对这块内容进行详细介绍

全局上下文中的变量对象

首先,有必要对全局对象(Global object)作个定义。

全局对象是一个在进入任何执行上下文前就创建出来的对象;此对象以单例形式存在;它的属性在程序任何地方都可以直接访问,其生命周期随着程序的结束而终止。

全局对象在创建的时候,诸如Math,String,Date,parseInt等等属性也会被初始化,同时,其中一些对象会指向全局对象本身--比如,DOM中,全局对象上的window属性就指向了全局对象(但是,并非所有的实现都是如此):

global = {Math: <...>,String: <...>......window: global
};</...></...>
复制代码
在引用全局对象的属性时,前缀通常可以省略,因为全局对象是不能通过名字直接访问的。然而,通过全局对象上的this值,以及通过如DOM中的window对象这样递归引用的方式都可以访问到全局对象:
String(10); // 等同于 global.String(10);// 带前缀
window.a = 10; // === global.window.a = 10 === global.a = 10;
this.b = 20; // global.b = 20;
复制代码
回到全局上下文的变量对象上--这里变量对象是全局对象本身:
VO(globalContext) === glbal;
复制代码
准确地理解这个事实是非常必要的:正是由于这个原因,当在全局上下文中声明一个变量时,可以通过全局对象上的属性来间接的引用该变量(比方说,当变量名提前未知的情况下)
var a = new String('test');alert(a); // directly, is found in VO(globalContext): "test"alert(window['a']); // indirectly via global === VO(globalContext): "test"
alert(a === this.a); // truevar aKey = 'a';
alert(window[aKey]); // indirectly, with dynamic property name: "test"
复制代码

函数上下文中的变量对象

在函数的执行上下文中,VO是不能直接访问的。它主要扮演被称作活跃对象(activation object) 简称AO的角色

活跃对象会在进入函数上下文的时候创建出来,初始化的时候会创建一个arguments属性, 其值就是Arguments对象:

AO = {arguments:
}
复制代码

Arguments对象是活跃对象上的属性,它包含了如下属性:

  • callee -- 对当前函数的引用
  • length -- 实参的个数
  • properties-indexes(数字,转换成字符串)其值是函数参数的值(参数列表中,从左到右)。properties-indexes的个数 == arguments.length; arguments对象的properties-indexes的值和当前实际传参的形参是共享的。

如下所示:

function foo(x, y, z) {// 定义的函数参数(x,y,z)的个数alert(foo.length); // 3// 实际传递的参数个数alert(arguments.length); // 2// 引用函数自身alert(arguments.callee === foo); // true// 参数互相共享alert(x === arguments[0]); // truealert(x); // 10arguments[0] = 20;alert(x); // 20x = 30;alert(arguments[0]); // 30// 然而,对于没有传递的参数z,// 相关的arguments对象的index-property是不共享的z = 40;alert(arguments[2]); // undefinedarguments[2] = 50;alert(z); // 40}foo(10, 20);
复制代码

上述例子,在当前的Google Chrome浏览器中有个bug——参数z和arguments[2]也是互相共享的。

处理上下文代码的几个阶段

至此,也就到了本文最核心的部分了。处理执行上下文代码分为两个阶段:
  1. 进入执行上下文
  2. 执行代码

对变量对象的修改和这两个阶段密切相关。

要注意的是,这两个处理阶段是通用的行为,与上下文类型无关(不管是全局上下文还是函数上下文都是一致的)。

进入执行上下文

一旦进入执行上下文(在执行代码之前), VO就会被一些属性填充(在此前已经描述过了):
  • 函数的形参(当进入函数执行上下文时)
    --变量对象的一个属性,其属性名就是形参的名字,其值就是实参的值;对于没有传递的参数,其值为undefined
  • 函数声明(FunctionDeclaration, FD)
    --变量对象的一个属性,其属性名和值都是函数对象创建出来的;如果变量对象已经包含了相同名字的属性,则替换它的值
  • 变量声明(var, VariableDeclaration)
    --变量对象的一个属性,其属性名即为变量名,其值为undefined;如果变量名和已经声明的函数名或者参数名相同,则不会影响已经存在的属性。

看下面这个?:

function test(a, b) {var c = 10;function d() {}var e = function _e() {};(function x() {});
}test(10); // call
复制代码

当以10位参数进入"test"函数上下文的时候,对应的AO如下所示:

AO(test) = {a: 10,b: undefined,c: undefined,d: e: undefined
};
复制代码

注意了,上面的AO并不包含函数"x"。这是因为这里的"x"并不是函数声明而是函数表达式(FunctionExpression 简称FE), 函数表达式不会对VO造成影响。尽管函数"_e"也是函数表达式,然而,正如我们看到的,由于它被赋值给了变量"e",因此它可以通过"e"来访问到。关于函数声明和函数表达式会在别的文章做具体介绍。 至此,处理上下文代码的第一阶段介绍完了,接下来介绍第二阶段--执行代码阶段。

执行代码

此时, AO/VO的属性已经填充好了。(尽管大部分属性都还没有赋予真正的值,都是初始化时候的undefined值)。 继续以上一例子为例,到了执行代码阶段,AO/VO就会修改成如下形式:
AO['c'] = 10;
AO['e'] = <指向函数表达式"_e">;
复制代码

再次注意到,这里函数表达式"_e"仍在内存中,这是因为它被保存在声明的变量"e"中,而同样是函数表达式的"x"却不在AO/VO中:如果尝试在定义前或者定义后调用"x"函数,这时会发生"x未定义"的错误。未保存的函数表达式只有在定义或者递归时才能调用。

如下是更加典型的例子:

alert(x); // functionvar x = 10;
alert(x); // 10x = 20;function x() {};alert(x); // 20
复制代码

上述例子中,为何“x”打印出来是函数呢?为何在声明前就可以访问到?又为何不是10或者20呢?原因在于,根据规则——在进入上下文的时候,VO会被填充函数声明; 同一阶段,还有变量声明“x”,但是,正如此前提到的,变量声明是在函数声明和函数形参之后,并且,变量声明不会对已经存在的同样名字的函数声明和函数形参发生冲突, 因此,在进入上下文的阶段,VO填充为如下形式:

VO = {};VO['x'] = // 发现var x = 10;
// 如果函数“x”还未定义
// 则 "x" 为undefined, 但是,在我们的例子中
// 变量声明并不会影响同名的函数值VO['x'] = 
复制代码

随后,在执行代码阶段,VO被修改为如下所示:

VO['x'] = 10;
VO['x'] = 20;
复制代码

如下例子再次看到在进入上下文阶段,变量存储在VO中(因此,尽管else的代码块永远都不会执行到,而“b”却仍然在VO中):

if (true) {var a = 1;
} else {var b = 2;
}alert(a); // 1
alert(b); // undefined, but not "b is not defined"
复制代码

关于变量

大多数讲JavaScript的文章甚至是JavaScript的书通常都会这么说:“声明全局变量的方式有两种,一种是使用var关键字(在全局上下文中),另外一种是不用var关键字(在任何位置)”。 而这样的描述是错误的。要记住的是: 使用var关键字是声明变量的唯一方式

如下赋值语句:

a = 10;
复制代码

仅仅是在全局对象上创建了新的属性(而不是变量)。“不是变量”并不意味着它无法改变,它是ECMAScript中变量的概念(它之后可以变为全局对象的属性,因为VO(globalContext) === global,还记得吧?)

不同点如下所示:

alert(a); // undefined
alert(b); // "b" is not definedb = 10;
var a = 20;
复制代码

接下来还是要谈到VO和在不同阶段对VO的修改(进入上下文阶段和执行代码阶段): 进入上下文:

VO = {a: undefined
};
复制代码

我们看到,这个阶段并没有任何“b”,因为它不是变量,“b”在执行代码阶段才出现。(但是,在我们这个例子中也不会出现,因为在“b”出现前就发生了错误)

将上述代码稍作改动:

alert(a); // undefined, we know whyb = 10;
alert(b); // 10, created at code execution 执行时创建var a = 20;
alert(a); // 20, modified at code execution 修改时创建
复制代码

这里关于变量还有非常重要的一点:与简单属性不同的是,变量是不能删除的{DontDelete},这意味着要想通过delete操作符来删除一个变量是不可能的。

a = 10;
alert(window.a); // 10alert(delete a); // truealert(window.a); // undefinedvar b = 20;
alert(window.b); // 20alert(delete b); // falsealert(window.b); // still 20
复制代码

但是,这里有个例外,就是“eval”执行上下文中,是可以删除变量的:

eval('var a = 10;');
alert(window.a); // 10alert(delete a); // truealert(window.a); // undefined
复制代码

利用某些debug工具,在终端测试过这些例子的童鞋要注意了:其中Firebug也是使用了eval来执行终端的代码。因此,这个时候var也是可以删除的。

实现层的特性:__parent__属性

正如此前介绍的,标准情况下,是无法直接访问活跃对象的。然而,在某些实现中,比如知名的SpiderMonkey和Rhino,函数有个特殊的属性__parent__, 该属性是对该函数创建所在的活跃对象的引用(或者全局变量对象)。

如下所示(SpiderMonkey,Rhino)

var global = this;
var a = 10;function foo() {}alert(foo.__parent__); // globalvar VO = foo.__parent__;alert(VO.a); // 10
alert(VO === global); // true
复制代码

上述例子中,可以看到函数foo是在全局上下文中创建的,相应的,它的__parent__属性设置为全局上下文的变量对象,比如说:全局对象。

然而,在SpiderMonkey中以相同的方式获取活跃对象是不可能的:不同的版本表现都不同,内部函数的__parent__属性会返回null或者全局对象。 在Rhino中,以相同的方式获取活跃对象是允许的

如下所示(Rhino):

var global = this;
var x = 10;(function foo() {var y = 20;// the activation object of the "foo" contextvar AO = (function () {}).__parent__;print(AO.y); // 20// __parent__ of the current activation// object is already the global object,// i.e. the special chain of variable objects is formed,// so-called, a scope chainprint(AO.__parent__ === global); // trueprint(AO.__parent__.x); // 10})();
复制代码

总结

本文,我们介绍了与执行上下文相关的对象。希望,本文能够对大家有所帮助,同时也希望本文能够起到解惑的作用。

原文地址

译文地址

重学系列传送门

重学JavaScript深入理解系列(一)
重学JavaScript深入理解系列(三)
重学JavaScript深入理解系列(四)
重学JavaScript深入理解系列(五)
重学JavaScript深入理解系列(六)

转载于:https://juejin.im/post/5cdbc4b0f265da038412b26d

http://www.lbrq.cn/news/1579303.html

相关文章:

  • 濮阳做网站推广/杭州seo关键词优化公司
  • 企业网站备案时间/网络营销方式有哪些?
  • 做网站的结论/seo网站推广软件 快排
  • 网上给别人做设计的网站/网站建设报价单
  • 白城市住房建设局网站/营销策划师
  • 程序员做交友网站/商城小程序开发哪家好
  • 河南省汝州市文明建设网站/南宁seo排名外包
  • 松江泖港网站建设/百度关键词代做排名
  • 应用制作下载/怀来网站seo
  • 安徽省建设行业质量与安全协会网站/怎么开一个网站平台
  • 网站服务器多少钱一月/网络营销的优缺点
  • 企业网站设计经典案例/seo实战培训王乃用
  • 教育类网站如何做/合肥seo网站管理
  • 免费b2b网站大全免费黄页/seo课程总结
  • 专业的网站建设公/长沙县网络营销咨询
  • 一站式网站建设与运营/30个免费货源网站
  • 如何建微信商城网站/广州婚恋网站排名
  • 建站域名/营销型网站分析
  • 网站的系统建设方式/网站关键词优化培训
  • 网站淘宝客怎么做的/网上售卖平台有哪些
  • wordpress媒体库删除/网络优化是干什么的
  • 网站 做百度推广有没有效果怎么样/品牌宣传方式
  • 深圳做律师网站公司/深圳网站优化平台
  • 拼多多卖网站建设/googleplay官方下载
  • 国内漂亮网站欣赏/营销软文范例大全300
  • 西安网站注册/企业网站seo排名
  • wordpress新打开空白页/举例说明seo
  • 营销型网站建设论文/软考培训机构哪家好一点
  • 如何做自己网站/微博推广效果怎么样
  • 承接网站建设 优帮云/电商营销推广方案
  • 【项目设计】高并发内存池
  • Ceph存储池参数中pg_num和pgp_num的关系
  • 【openEuler构建测试环境或部署嵌入式系统】openEuler生态扩容新路径:内网穿透工具cpolar助力多场景落地
  • “一车一码一池一充”:GB 17761-2024新国标下电动自行车的安全革命
  • 【深度学习3】向量化(Vectorization)
  • Day 9-2: Transformer翻译实例演示 - 翻译的基础设施