1.1.1 摘要
在我们学习Javascript过程中,常常会遇到作用域(Scope)和执行上下文(Context)等概念。其中,执行上下文与this关键字的关系密切。
有面向对象编程经验的各位,对于this关键字再熟悉不过了,因此我们很容易地把它和面向对象的编程方式联系在一起,它指向利用构造器新创建出来的对象;在ECMAScript中,也支持this,然而, 正如大家所熟知的,this不仅仅只用来表示创建出来的对象。
在接下来的博文我们讲介绍Javascript的作用域和执行上下文,以及它们的异同之处。
目录
- 作用域
- 执行环境
- 上下文问题
- 上下文实例问题
- 跨作用域的上下文
- 使用上下文解决作用域问题
- 使用作用域解决上下文问题
1.1.2 正文
执行环境(Execution context)也称为“环境”是Javascript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
看到了执行环境的定义有点头昏了,简而言之“每个执行环境都有一个与之关联的变量对象”;这里我们有一个疑问就是这个变量对象是怎样定义的呢?
接下来,让我们看一下变量对象的定义,具体实现如下:
/*** Execution context skeleton.
*/
activeExecutionContext = {
// variable object.
VO: {...},
this: thisValue
};
通过上面的伪代码我们知道对象字面量activeExecutionContext,它包含一个变量对象VO和this属性。
这说明了this与上下文的可执行代码类型有关,其值在进入上下文阶段就确定了,并且在执行代码阶段是不能改变的(关于this使用可以阅读《Javascript this 的一些学习总结》)。
作用域(Scope)控制着变量和参数的可见性及生命周期。
简而言之,执行环境是基于对象的,而作用域是基于函数的。
作用域我们将通过一个例子介绍作用域的使用,首先,我们定义了一个函数FooA()和FooB,示例代码如下:
/*** Defines a function.
*/
var FooA = function(){
var a = 1;
var FooB = function(){
var b = 2;
console.log(a, b); // outputs: 1, 2
}
console.log(a, b); // Error! b is not defined
}
FooA();
在示例中,第二个log输出变量为未定义,这是由于在Javascript中定义在函数里面的参数和变量在函数外部是不可见的,而在一个函数内部任何位置定义的参数和变量,在该函数内部任何地方都是可见的。
执行环境首先,我们定义了对象字面量o,它包含一个属性x和方法m(),示例代码如下:
/*** Defines a literal object.
* @type {Object}
*/
var o = {
x:23,
m: function(){
var x = 1;
console.log(x, this.x); // outputs 1, 23
}
}
o.m();
示例中的两个变量和属性x都能被访问,但它们被访问的方式是截然不同,在log中访问第一个x是通过作用域方式访问了本地变量x,而this.x是通过执行上下文方式访问对象o的属性x,因此输出值也不尽相同。
上下文问题接下来,我们修改一下前面的例子,在方法m()中添加一个函数f(),示例代码如下:
/*** Defines a literal object.
* @type {Object}
*/
var o = {
x:23,
m: function(){
var x = 1;
var f = function(){
console.log(x, this.x); // outputs 1, undefined
}
f();
}
}
o.m();
上面,我们通过调用方法m()来输出x的值,由于方法m()的具体实现是通过调用函数f()来实现。
当我们调用对象o的方法m()时,发现this.x是未定义的。
这究竟是什么原因呢?回忆前面的例子,由于方法m()获取了对象o的上下文,所以this是指向对象o的,难道是函数f()没有获取对象o的上下文,因此它不清楚this指向哪个对象?
首先让我们回顾一下函数和方法以及属性和变量的区别:方法和对象关联,如:object.myMethod = function() {},而函数非对象关联:var myFunc = function {};同样属性也是对象关系的,如:object.myProperty = 23,而变量:var myProperty = 23。
因为我们提到上下文是基于对象的,所以函数f()不能获取对象o的执行上下文。
我们是否可以让函数f()获取对象o的执行上下文呢?我们仔细地想一下,既然函数f()不清楚this指向的对象,那么可以直接调用对象的属性就OK了。
/*** Fixs broken context issue.
* @type {Object}
*/
var o = {
x:23,
m: function(){
var x = 1;
var f = function(){
console.log(x, o.x); // outputs 1, 23
}
f();
}
}
o.m();
我们在函数f()中直接调用对象o的属性x,这样函数f()就无需获取执行上下文直接调用对象的属性了。
现在,我们又遇到一个新的问题了,如果对象不是o而是p,那么我们就需要修改函数f()中的对象了,更严重的情况就是我们没有办法确定具体是哪个对象,示例代码如下:
/*** Defines a literal object.
* @constructor
*/
var C = function(){}
C.prototype = {
x:23,
m: function(){
var x = 1;
var f = function(){
console.log(x, this.x); // outputs 1, undefined
}
f();
}
}
var instance1 = new C();
instance1.m();上下文实例问题
上面,我们定义了函数C和它的原型对象,而且我们可以通过new方式创建C对象实例instance1,按照前面的方法解决Broken Context问题,具体实现如下:
/*** Defines a literal object.
* @constructor
*/
var C = function(){}
C.prototype = {
x:23,
m: function(){
var x = 1;
var f = function(){
console.log(x, instance1.x); // outputs 1, undefined
}
f();
}
}
var instance1 = new C();
instance1.m();
如果我们在创建一个C的对象实例instance2,那么我们就不能指定函数f()中的对象了。
其实,this是对象实例的抽象,当实例有多个甚至成千上百个的时候,我们需要通过this引用这些对象实例。
因此,指定对象方法不能有效解决Broken Context问题,我们还是需要使用this来引用对象,前面我们讲到由于函数f()没有获取对象o的执行上下文,因此它不清楚this指向哪个对象,所以输出this.x未定义,那么我们是否可以让函数f()获取对象的执行上下文。
跨作用域的上下文我们想想既然方法是基于对象的,而且可以获取对象的执行上下文,那么我们直接把f()定义为方法好了。
读《JavaScript高级程序设计》第4、7章有感。
一、基本概念
1.什么是执行环境?(execution context)
执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
个人感悟:
所谓执行环境,说的是某一段特殊的代码(比如函数),它在这个“执行环境”下执行,通过这个执行环境的限制,决定了这段代码(此处是函数)允许访问的数据是什么。
2.什么是变量对象?(variable object)
每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它)
个人感悟:
我觉得吧,执行环境是一个很抽象的概念,而在JS代码实现中,需要具体化这个抽象概念,于是,就用一个与执行环境关联的对象,用于表示这个环境的具象存在。
3.什么是作用域链?(scope chain)
当代码在一个环境中执行时,会创建由变量对象构成的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
个人感悟:
前面说道,JavaScript通过执行环境的限制,决定变量或函数允许访问的数据。那么,现在就通过一条链表存放所有可以访问的变量对象(变量对象中存放可以访问的数据),以便我们有序地访问这些数据。(为什么要这样搞,下文会说)
另外,作用域链本质上是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。
4.什么是活动对象?(activation object)
如果这个环境是函数,则将其活动对象作为变量对象。函数的活动对象在最开始时只包含一个变量,即arguments对象。
个人感悟:
按我目前狭隘地知识储备,我所知道的就是凡是函数均会将自己的活动对象作为变量对象,然后插入作用域链的最前端。
二、执行流程
1.关键性知识
先说说一些关键性的东西,再讲流程。
(1)首先,全局执行环境是最外围的一个执行环境。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。
(2)其次,某个执行环境中的所有代码执行完毕后,该环境就会被销毁,保存在其中的所有变量和函数定义也随之销毁。像函数这样的局部环境的变量对象只在函数执行的过程中存在,而全局执行环境直到应用程序退出时才会被销毁。
(3)然后,每个函数在第一次被调用时都会创建自己的执行环境及相应的作用域链,并把作用域链赋值给一个特殊的内部属性[[Scope]],然后,使用this、arguments和其他命名参数的值来初始化函数的活动对象。(函数均会将自己的活动对象作为变量对象,用以表示自己的执行环境)
(4)最后,关于上一点还有一些关于创建作用域链的细节。以函数为例,在创建函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的[[Scope]]属性中。当调用函数时,会为函数创建一个执行环境,然后通过复制函数的[[Scope]]属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(在此作为变量对象使用)被创建并被推入执行 环境作用域链的前端。
2.执行过程
有上面的铺垫,下面开始讲流程。
(1)开始执行代码。 // 初始环境是在全局执行环境中
(2)构建一条作用域链,链接到全局对象中 // 为window对象创建作用域链,此时链中元素只有一个对象,这就是全局执行对象
执行流进入一个函数时:
{
(3)创建函数自己的执行环境。 // 即使用this、arguments和其他命名参数的值来初始化自己的活动对象
// 并将活动对象作为变量对象,用以表示自己的执行环境
(4)构建一条作用域链,将链头指针赋值到函数的一个特殊的内部属性[[Scope]]中。
// 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
// 活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)
// 作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。
这样,一直延续到全局执行环境,全局执行环境的变量对象始终都是作用域链中的最后一个对象
(5)将函数的环境推入一个环境栈中。
(6)执行函数代码。
// 如果中途需要搜索标识符,则此时就是沿着作用域链一级一级地搜索,这就是作用域链的用途
(7)函数执行完毕,栈将其环境弹出,把控制权返回会之前的执行环境。
// 也就是作用域链中函数执行环境的上一个执行环境
(8)销毁该函数的环境。
//即销毁该函数的变量对象,保存在其中的所有变量和函数定义也随之销毁
}
(9)执行其他代码,退出程序时销毁全局执行环境。
好吧我承认我写得太抽象了,来个例子理解一下吧:
function changeColor(){
var
2013-03-22和鸣605楼整理,大四没学生正好整理了,呵呵!(材料来源于网络)
1 什么是单点登陆单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
较大的企业内部,一般都有很多的业务支持系统为其提供相应的管理和IT服务。例如财务系统为财务人员提供财务的管理、计算和报表服务;人事系统为人事部门提供全公司人员的维护服务;各种业务系统为公司内部不同的业务提供不同的服务等等。这些系统的目的都是让计算机来进行复杂繁琐的计算工作,来替代人力的手工劳动,提高工作效率和质量。这些不同的系统往往是在不同的时期建设起来的,运行在不同的平台上;也许是由不同厂商开发,使用了各种不同的技术和标准。如果举例说国内一著名的IT公司(名字隐去),内部共有60多个业务系统,这些系统包括两个不同版本的SAP的ERP系统,12个不同类型和版本的数据库系统,8个不同类型和版本的操作系统,以及使用了3种不同的防火墙技术,还有数十种互相不能兼容的协议和标准,你相信吗?不要怀疑,这种情况其实非常普遍。每一个应用系统在运行了数年以后,都会成为不可替换的企业IT架构的一部分,如下图所示。
随着企业的发展,业务系统的数量在不断的增加,老的系统却不能轻易的替换,这会带来很多的开销。其一是管理上的开销,需要维护的系统越来越多。很多系统的数据是相互冗余和重复的,数据的不一致性会给管理工作带来很大的压力。业务和业务之间的相关性也越来越大,例如公司的计费系统和财务系统,财务系统和人事系统之间都不可避免的有着密切的关系。
为了降低管理的消耗,最大限度的重用已有投资的系统,很多企业都在进行着企业应用集成(EAI)。企业应用集成可以在不同层面上进行:例如在数据存储层面上的“数据大集中”,在传输层面上的“通用数据交换平台”,在应用层面上的“业务流程整合”,和用户界面上的“通用企业门户”等等。事实上,还用一个层面上的集成变得越来越重要,那就是“身份认证”的整合,也就是“单点登录”。
通常来说,每个单独的系统都会有自己的安全体系和身份认证系统。整合以前,进入每个系统都需要进行登录,这样的局面不仅给管理上带来了很大的困难,在安全方面也埋下了重大的隐患。下面是一些著名的调查公司显示的统计数据:
· 用户每天平均16分钟花在身份验证任务上 - 资料来源:IDS
· 频繁的IT用户平均有21个密码 - 资料来源:NTA Monitor Password Survey
· 49%的人写下了其密码,而67%的人很少改变它们
· 每79秒出现一起身份被窃事件 - 资料来源:National Small Business Travel Assoc
· 全球欺骗损失每年约12B - 资料来源:Comm Fraud Control Assoc
· 到2007年,身份管理市场将成倍增长至$4.5B - 资料来源:IDS
使用“单点登录”整合后,只需要登录一次就可以进入多个系统,而不需要重新登录,这不仅仅带来了更好的用户体验,更重要的是降低了安全的风险和管理的消耗。请看下面的统计数据:
· 提高IT效率:对于每1000个受管用户,每用户可节省$70K
· 帮助台呼叫减少至少1/3,对于10K员工的公司,每年可以节省每用户$75,或者合计$648K
· 生产力提高:每个新员工可节省$1K,每个老员工可节省$350 - 资料来源:Giga
· ROI回报:7.5到13个月 - 资料来源:Gartner
另外,使用“单点登录”还是SOA时代的需求之一。在面向服务的架构中,服务和服务之间,程序和程序之间的通讯大量存在,服务之间的安全认证是SOA应用的难点之一,应此建立“单点登录”的系统体系能够大大简化SOA的安全问题,提高服务之间的合作效率。
2 单点登陆的技术实现机制随着SSO技术的流行,SSO的产品也是满天飞扬。所有著名的软件厂商都提供了相应的解决方案。而是对SSO技术本身进行解析,并且提供自己开发这一类产品的方法和简单演示。
单点登录的机制其实是比较简单的,用一个现实中的例子做比较。颐和园是北京著名的旅游景点。在颐和园内部有许多独立的景点,例如“苏州街”、“佛香阁”和“德和园”,都可以在各个景点门口单独买票。很多游客需要游玩所有德景点,这种买票方式很不方便,需要在每个景点门口排队买票,钱包拿进拿出的,容易丢失,很不安全。于是绝大多数游客选择在大门口买一张通票(也叫套票),就可以玩遍所有的景点而不需要重新再买票。他们只需要在每个景点门口出示一下刚才买的套票就能够被允许进入每个独立的景点。
单点登录的机制也一样,如下图所示,当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录(1);根据用户提供的登录信息,认证系统进行身份效验,如果通过效验,应该返回给用户一个认证的凭据--ticket(2);用户再访问别的应用的时候(3,5)就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行效验,检查ticket的合法性(4,6)。如果通过效验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。
从上面的视图可以看出,要实现SSO,需要以下主要的功能:
· 所有应用系统共享一个身份认证系统。
统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志(ticket),返还给用户。另外,认证系统还应该对ticket进行效验,判断其有效性。
· 所有应用系统能够识别和提取ticket信息
要实现SSO的功能,让用户只登录一次,就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取,通过与认证系统的通讯,能自动判断当前用户是否登录