javascript系列之核心知识点(二)
变量对象 |
变量对象是一个与执行上下文相关联的容器。它是一个和上下文密切结合的特殊对象,含有定义在上下文中的变量和函数声明.注意,函数表达式(和函数声明不同的)不包含在变量对象中。
变量对象是一个抽象的概念。理论上讲,不同的上下文类型表示使用不同的对象。例如在全局上下文,变量对象就是全局上下文自身(这就是为什么我们可以通过全局对象的属性名获取全局变量的原因)
1 var foo = 10;
3 function bar() {} // 函数声明, FD
4 (function baz() {}); // 函数表达式, FE
6 console.log(
7 this.foo == foo, // true
8 window.bar == bar // true
9 );
11 console.log(baz); // ReferenceError, "baz" is not defined
这样全局上下文的变量对象(VO)将有以下属性:
图7全局变量对象
我们发现函数baz作为一个函数表达式不包含在变量对象中。这就是为什么当我们在函数外面获取它时有一个ReferenceError
注意这与其他语言(c/c++)不同的,在ECMAScript中仅仅只有函数产生一个新的作用域。定义在函数作用域中的变量和内部函数在函数外部是不能直接调用的,而且不会污染全局变量对象
使用eval后我们将会进入一个(eval's)执行上下文,然而,eval在全局变量对象中或者在调用者的变量对象中(例如eval被一个函数调用)使用,函数和他的变量对象是怎么的了?在函数上下文中变量对象表示为活动对象。
活动对象 |
当一个函数被一个调用者激活(调用),一个特殊的对象,所谓的活动对象就产生了。它包含了形参和特殊的arguments
对象(是形参的映射但有index-properties)。在函数上下文中,这个活动对象就当做变量对象来用。
函数的变量对象也是一个简单的变量对象,但它除了包含变量和函数声明,它还包含形参和arguments
对象,而且它叫做活动对象。看下面的例子:
1 function foo(x, y) {
2 var z = 30;
3 function bar() {} // FD
4 (function baz() {}); // FE
5 }
7 foo(10, 20);
我们就有foo函数上下文的活动对象(AO)
图8.活动对象
再一次看到函数表达式baz不包含在变量/活动对象中。这个主题所有巧妙情形(变量提升和函数声明)的完整描述在Chapter 2. Variable object.
我们进入到下一部分。众所周知,在ECMAScript中我们可以使用内部函数获取父函数的变量和全局上下文的变量。与上面讨论原型链一样,这就是所谓的作用域链。
作用域链 |
作用域链就是一个用来寻找出现在上下文代码中标示符的对象列表
规则是很简单的,和原型链相似:如果一个对象在他的作用域中(自己的变量/活动对象中)没找到,就进入到父变量对象中查找,一直下去。
在上下文中,标示符指的是:变量名,函数声明,形参等等。当一个函数在他的代码中用到变量不是局部变量(或者局部函数和形参),这样的变量叫自由变量,准确的寻找这些自由变量时,作用域就派上用场了。
一般情况下,作用域链就是所有父变量对象的列表,加上函数自身的变量/活动对象(在作用域链的最前面),然而作用域链也会包含一些其他的对象。例如,在上下文代码执行时动态添加到作用域链中的对象。比如with中的对象和catch从句。
当解析(查找)一个标示符时,作用域链从活动对象开始查找(如果在变量对象中没找到),逐渐追溯到作用域链的顶部,和原型链是一样的
var x=10;
(function foo(){
var y=20;
(function bar(){
var z=30;
//"x"和"y"是自由变量,在bar的作用域链中的下一个对象中寻找
console.log(x+y+z);
})();
})();
我们通过隐式的__parent__属性(用来指向作用域链中的下一个对象)来推理作用域链对象的联系,这种方法可以在real Rhino code检测。这种技术也在ES5的词法环境中使用(名字叫做outer连接)
。作用域链的另外一种解释就是一个简单数组。使用__parent__我们可以用下面的图来解释这个例子(父变量对象保存在函数的[[Scope]]属性中
):
图9作用域链
在代码执行时,作用域了可能被with和catch从句对象扩展。由于这些都是简单的对象 ,他们也可能有原型(和原型链)。这一事实就是的作用域链查找变成二维的了。(1)考虑作用域链(2)每一个作用域的链接-原型链链接的深处(如果链接流程中有原型)。
Object.prototype.x=10;
var w=20;
var y=30;
//在SpiderMonkey的全局对象,例如全局上下文对象的变量对象从"object.prototype"继承,因此当我们指向未定义的全局变量x,会在原型链中找到。
console.log(x);//
(function foo(){
//'foo'的局部变量
var w=40;
var x=100;
//"x"会在"Object.prototype"中找到,因为{z:50}继承自它
with({z:50}){
console.log(w,x,y,z);//40,10,30,50
}
//当"with"对象从作用域链删除后,"x"任然在"foo"上下文的变量对象中查找,"w"变量也成了局部变量
console.log(x,w);//100,40
//如何在我们浏览器宿主环境下获取全局的"w"变量
console.log(window.w);//
})
我们有下面的结构图(在我们考虑_parent_链接之前,首先应考虑_proto_链)
图10"with-augment"作用域链
注意,不是所有的实现中,全局对象都继承自Object.prototype。上图中描述的行为(从全局上下文引用“non-defined”的变量x)在SpiderMonkey可以验证
如果所有的父变量存在,从内部函数获取父数据没有什么特别的——我们遍历作用域链解析需要的变量。然而,正如我们上面提到的,一个上下文结束后,他所有 的状态和它自身就销毁了。这时候内部函数也可能从父函数中返回了。此外,这个返回的函数可能在另外一个上下文被激活。如果一些自由变量的上下文已 经"gone"了,这种激活会怎样了?一般情况下,帮助我们解决这个问题的概念叫做闭包(词法上),在ECMAScript中和作用域链的概念直接相关 的。
闭包 |
在ECMAScript中,函数是一等对象。这也就意味着函数可以作为参数传递给其他的函数(这种情况叫做"funargs",“functional arguments”的缩写)。获取"funargs"的函数叫做higher-order函数,函数也会在其他函数中返回。那么返回函数的函数叫做function valued functions(返回函数值的函数)
函数参数问题的第一种形式就是向上查找的问题。当一个使用了上面提到的自由变量的函数从另一个函数返回时。为了能够获取父上下文的变量,即使父上下文已经终结。内部函数在创建时把它的父作用域链保存在[[Scope]]属性中。当这个函数被激活时,结合活动对象和[[Scope]]属性,这个函数上下文的作用域链就形成了。
1 Scope chain = Activation object + [[Scope]]
再次注意一件主要的事情-在创建时刻-函数保存了父作用域链。因为这个被保存的作用域链在后期的函数调用中,将会用于变量查找
function foo(){
var x=10;
return function bar(){
console.log(x);
};
}
//"foo"返回一个函数,这个返回的函数使用了自由变量“x”
var returnedFunction=foo();
//全局变量“x”
var x=20;
//执行已返回的函数
returnedFunction();//10,不是20
这种作用域叫做静态(词法)作用域。我们看到在返回的bar函数——保存的[[Scope]]属性
中找到了变量x。一般的思想,在上面的例子中会有一个动态的作用域保存x的变量值为20,而不是10.然而ECMAScript不存在动态作用域。
函数参数问题的第二种形式就是向下查找的问题。在这种情况下,父作用域可能是存在的,但可能是以一个模糊的标示符保存的。问题是:标示符的值应该使用哪 一个作用域——静态保存在函数创建时的作用域还是在执行时动态形参的作用域(调用者的作用域)?为了避免这种模棱两可的作用域并产生闭包,决定使用静态作 用域:
//global x
var x=10;
//全局函数
function foo(){
console.log(x);
}
(function(funArg){
//local x
var x=20;
//这里是不模糊的,因为我们使用的全局变量x,静态的保存在foo函数的[[Scope]]中,而不是激活函数的调用者作用域中的x
funArg();//10,不是20
})(foo);
我们可以得出这样的结论:在语言中的闭包必须需要静态作用域。然而,有些语言提供了静态和动态作用域以供编程者选择-使用闭包或者不使用。由于ECMAScript只有静态作用域(例如我们解决的两种函数参数问题)。ECMAScript使用函数的[[Scope]]属性
完整的支持闭包,下面给出闭包的准确定义:
闭包就是代码块(ECMAScript中,这个代码块是函数)和静态保存的父作用域的结合。因此通过这个保存的作用域链,函数可以很容易的找到自由变 量。注意,每一个函数在创建的时候都保存了[[Scope]],理论上,ECMAScript中所有的函数都是闭包。
还需要记住的一个事实,许多函数可能有一个相同的(这真的是一个很常规的情形,比如有两个内部/全局函数)父作用域,这种情况下储存在[[Scope]]属性的变量在所有拥有同一个父作用域链的函数是共享的。一个闭包变量的改变会影响另个闭包读取的变量:
function baz(){
var x=1;
return {
foo:function foo(){return ++x;},
bar:function bar(){return --x;}
};
}
var closures=baz();
console.log(closures.foo(),//
closures.bar()//1)
这段代码可以用下图来阐释:
图10 共享的[[Scope]]
在循环中构建一些函数的迷惑也是和这个特点相关的。在构造的函数中使用循环计数器,一些编程者获得了一些意外的结果,当在函数中使用了同一个计数器时。现在清楚为什么了吧-因为所有的这些函数都有同样的[[Scope]],在这个
[[Scope]]中计数器是最后一次计数值。
1 var data = [];
3 for (var k = 0; k < 3; k++) {
4 data[k] = function () {
5 alert(k);
6 };
7 }
9 data[0](); // 3, but not 0
10 data[1](); // 3, but not 1
11 data[2](); // 3, but not 2
这里有一些技巧来解决这些问题。一种技巧就是在作用域链中使用一个附加对象:
var data=[];
for(var k=0;k<3;k++){
data[k]=(function(x){
return function(){
alert(x);
};
})(k)
}
data[0]();//
data[1]();//
data[2]();//
我们进入到下一部分,考虑执行上下文的最后一个属性this。
this值 |
this是和执行上下文相关的特殊对象。因此他可以命名为上下文对象(上下文执行时激活的对象)
任何对象都可以作为上下文的this值。我需要再次阐明一些与上下文相关的描述,特别是this值,它经常错误的描述为变量对象的属性。请再次记住:this是执行上下文的属性,而不是变量对象的属性
这个特性是很重要的,因为和变量不同,this从不参与标示符的解析过程。例如,在代码中的执行上下文直接获取this而不需要查找作用域链。this值仅仅决定于进入的上下文。
在全局上下文中,this是全局对象本身(也就是说this等于全局对象):
1 var x = 10;
3 console.log(
4 x, //
5 this.x, //
6 window.x //
7 );
在函数上下文中,this值在每个函数调用中都可能不同的。这里的this由调用者通过调用表达式提供(函数的调用方式)。例如下文中foo函数是被调用者,被全局上下文调用。我们看下面的例子,对同一段函数代码。不同的调用方式提供不同的this值
//foo 函数的代码是不变的。但在每一次激活时,this都是不一样的
function foo(){
alert(this);
}
//调用者激活了foo函数并提供this值
foo();//全局环境下的变量对象
foo.prototype.constructor();// foo.prototype
var bar={
baz:foo
};
bar,baz();//bar
(bar.baz)(); // also bar
(bar.baz = bar.baz)(); // but here is global object
(bar.baz, bar.baz)(); // also global object
(false || bar.baz)(); // also global object
var otherFoo = bar.baz;
otherFoo(); // again global object
javascript系列之核心知识点(二)的更多相关文章
- javascript系列之核心知识点(一)
JavaScript. The core. 1.对象 2.原型链 3.构造函数 4.执行上下文堆栈 5.执行上下文 6.变量对象 7.活动对象 8.作用域链 9.闭包 10.this值 11.总结 这 ...
- JavaScript 系列博客(二)
JavaScript 系列博客(二) 前言 本篇博客介绍 js 中的运算符.条件语句.循环语句以及数组. 运算符 算术运算符 // + | - | * | / | % | ++ | -- consol ...
- javascript中的一些核心知识点以及需要注意的地方
前言 近期杂事甚多,这些事情的积累对知识体系的提升有好处,但是却不能整理出来,也整理不出来 比如说我最近研究的Hybrid在线联调方案便过于依赖于业务,就算分享也不会有人读懂,若是抽一点来分享又意义不 ...
- javascript系列之DOM(二)
原文:javascript系列之DOM(二) 原生DOM扩展 我们接着第一部分来说,上文提到了两种常规的DOM操作:创建文档片段和遍历元素节点.我们知道那些雨后春笋般的库,有很大一部分工作就是提供了一 ...
- JavaScript 系列--JavaScript一些奇淫技巧的实现方法(二)数字格式化 1234567890转1,234,567,890;argruments 对象(类数组)转换成数组
一.前言 之前写了一篇文章:JavaScript 系列--JavaScript一些奇淫技巧的实现方法(一)简短的sleep函数,获取时间戳 https://www.mwcxs.top/page/746 ...
- 【SpringBoot MQ 系列】RabbitMq 核心知识点小结
[MQ 系列]RabbitMq 核心知识点小结 以下内容,部分取材于官方教程,部分来源网络博主的分享,如有兴趣了解更多详细的知识点,可以在本文最后的文章列表中获取原地址 RabbitMQ 是一个基于 ...
- JavaScript核心知识点
一.JavaScript 简介 一.JavaScript语言的介绍:JavaScript是基于对象和原型的一种动态.弱类型的脚本语言 二.JavaScript语言的组成:JavaScript是由核心语 ...
- 深入理解JavaScript系列(10):JavaScript核心(晋级高手必读篇)
本篇是ECMA-262-3 in detail系列的一个概述(本人后续会翻译整理这些文章到本系列(第11-19章).每个章节都有一个更详细的内容链接,你可以继续读一下每个章节对应的详细内容链接进行更深 ...
- JavaScript 系列博客(一)
JavaScript 系列博客(一) 前言 本系列博客为记录学习 JavaScript 的学习笔记,会从基础开始慢慢探索 js.今天的学习笔记主要为 js 引入.定义变量以及 JavaScript 中 ...
随机推荐
- 在线maven
仓库
findmaven.net是一个查找Jar和查找Maven的Maven仓库搜索引擎.它能够依据Java开发人员提供的Class名或者Jar名找到包括它的Jar,同一时候提供Jar的Maven仓库链接, ...
- 大哥可以写KMP该——达到strstr()
在最后采访,面试官要求实现strstr(),当场就蒙了. 这个题目是模式匹配问题.<算法导论>里列出了几种字符串匹配算法: 朴素算法 | Rabin-Karp | 有限自己主动机算法 | ...
- UVa 11069 - A Graph Problem
题目:给你一个集合{1,2,..,n},计算子集的个数,子集的元素不能相邻且不能再插入元素. 分析:dp,动态规划.相邻元素间仅仅能相差3或者2. 动态方程:f(k)= f(k-2)+ f(k-3): ...
- 在Ceph创建虚拟机的过程改进分析
作为个人学习笔记分享.有不论什么问题欢迎交流! 近期在Gerrit中看到一个change:https://review.openstack.org/#/c/94295/ , 它主要是对当前在Ceph中 ...
- Linux网络基础设施配置
1.TCP/IP网络配置文件 /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/sysconfig/network /etc/host.conf /etc/ ...
- Swift 编程语言学习0.1——Swift简单介绍
有的时候,认为看英文文档有些费时,看中文文档怕翻译不准,有些地方确实不须要抠字眼.当有些地方假设翻译不精准会产生歧义,所以用这样对比的方式.顺便学习一下Swift. Swift is a new pr ...
- 了解大数据的技术生态系统 Hadoop,hive,spark(转载)
首先给出原文链接: 原文链接 大数据本身是一个很宽泛的概念,Hadoop生态圈(或者泛生态圈)基本上都是为了处理超过单机尺度的数据处理而诞生的.你能够把它比作一个厨房所以须要的各种工具. 锅碗瓢盆,各 ...
- 翻译器DIY它———算在英文文本中的单词数,字符和行数
咳咳.这部分应该是序列化编译器DIY的,然而,在这样做DIY第一次使用前flex 为了练练手,对于后者的理解是有帮助. 在word 我经常看到一个字计数功能,因此,它是如何实现,当然,首先想到的是要经 ...
- IP地址和子网掩码
A分类IP住址 在第一个领域值规模:0-127 默认子网掩码:255.0.0.0 B分类IP就拿地址的第一个字段值范围:128-191 默认的子网掩码255.255.0.0 C类IP地址的第一个字 ...
- Android内置下拉刷新组件SwipeRefreshLayout
也许下拉刷新之前,你可能会使用一些第三方的开源库,例如PullToRefresh, ActionBar-PullToRefresh等待,但现在有的正式组成部分---SwipeRefreshLayout ...