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()定义为方法好了。

现在,我们在C对象原型中定义方法f(),示例代码如下:

/**
* Defines a literal object.
* @constructor
*/
var C = function(){} C.prototype = {
x:10,
m: function(){
var x = 1;
this.f();
},
f: function(){
console.log(x, this.x); // Reference ERROR!!
}
}
var instance1 = new C();
instance1.m();

好啦,我们在C对象原型中定义方法f(),那么方法f()就可以获取对象的执行上下文。

现在,我们在Firefox运行以上代码,结果输出Reference ERROR,这究竟是什么原因呢?我们想了一下问题出于变量x中,由于方法f()不能获取方法m()的作用域,所以变量x不在方法f()中。

使用上下文解决作用域问题

我们处于两难的境地方法f()既要获取执行上下文,又要获取方法m()的作用域;如果我们要获取方法m()的作用域,那么我们需要把方法f()定义在m()中。

接下来,我们把方法f()定义在m()中,具体实现如下:

/**
* Defines a literal object.
* @constructor
*/
var C = function(){}
C.prototype = {
x:23,
m: function(){
var x = 1;
this.f = function(){
console.log(x, this.x); // outputs 1, 23
}
this.f();
}
}
var instance1 = new C();
instance1.m();

现在我们通过this调用方法f(),它现在可以获取对象instance1执行上下文,并且也可以获取方法m()的作用域,所以方法f()可以获取属性和变量x的值。

使用作用域解决上下文问题

接下来,继续看一个例子,我们要在函数setTimeout()中调用方法onTimeout(),具体定义如下:

/**
* setTimeout function with Broken Context issue
* @type {Object}
*/
var o = {
x:23,
onTimeout: function(){
console.log("x:", this.x);
},
m: function(){
setTimeout(function(){
this.onTimeout(); // ERROR: this.onTimeout is not a function
}, 1);
}
}
o.m();

同样在函数setTimeout()中调用方法onTimeout()失败,我们知道这是由于方法onTimeout()不能获取对象执行上下文。

我们知道在方法m()中可以获取对象执行上下文,所以可以通过临时变量引用this指向的对象,实例代码如下:

/**
* Fixs setTimeout function with Broken Context issue.
* @type {Object}
*/
var o = {
x:23,
onTimeout: function(){
console.log("x:", this.x); // outputs 23
},
m: function(){ // Keeps instance reference.
var self = this;
setTimeout(function(){
// Gets m scrope.
self.onTimeout();
}, 1);
}
}
o.m();

上面,我们通过临时变量self保存了this的引用,由于setTimeout()函数可以获取m()的作用域,所用我们可以通过self. onTimeout()的方式调用onTimeout()方法。

1.1.3 总结

本博文通过介绍执行上下文和作用域的异同、this的使用以及变量对象,让我们加深对Javascript 语言特性的理解。

首先,我们介绍了执行上下文和this的的关系,并且执行上下文是具有对象的;然后,介绍了作用域使变量在作用域范围内可见,并且作用域是基于函数的。

我们通过具体的例子介绍了在不同的作用域和执行上下文中,对this和变量的影响加深了作用域和执行上下文的理解,从而帮助我们更好的阅读和编写代码。

Javascript——Context和Scope的一些学习总结的更多相关文章

  1. javascript权威指南第6版学习笔记

    javascript权威指南第6版学习笔记 javascript数组.函数是特殊对象 看一点少一点. 3.1.4 hello.js内容是 var x=.3-.2;var y=.2-.1 console ...

  2. 2、JavaScript 基础二 (从零学习JavaScript)

     11.强制转换 强制转换主要指使用Number.String和Boolean三个构造函数,手动将各种类型的值,转换成数字.字符串或者布尔值. 1>Number强制转换 参数为原始类型值的转换规 ...

  3. 1、JavaScript 基础一 (从零学习JavaScript)

    1:定义:javascript是一种弱类型.动态类型.解释型的脚本语言. 弱类型:类型检查不严格,偏向于容忍隐式类型转换. 强类型:类型检查严格,偏向于不容忍隐式类型转换. 动态类型:运行的时候执行类 ...

  4. Html JavaScript网页制作与开发完全学习手册

    Html JavaScript网页制作与开发完全学习手册 篇 HTML技术章 HTML入门 1.1 什么是HTML 1.1.1 HTML的特点 1.1.2 HTML的历史 1.2 HTML文件的基本结 ...

  5. JavaScript设计模式之策略模式(学习笔记)

    在网上搜索“为什么MVC不是一种设计模式呢?”其中有解答:MVC其实是三个经典设计模式的演变:观察者模式(Observer).策略模式(Strategy).组合模式(Composite).所以我今天选 ...

  6. javascript中的闭包(Closure)的学习

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现. 下面是我在网上通过学习阮一峰老师的笔记,感觉总结很不错,特记录于此. 一.变量的作用域 要理解 ...

  7. 没有JavaScript的基础,我可以学习Angular2吗?

    Can I learn and understand Angular2 without understanding JavaScript? 没有JavaScript基础我能学习和理解Angular2吗 ...

  8. javascript解析引擎(每天有学习一点篇)

    ======================================================= 有一段时间,经常耳闻web前端的福音,对高性能的V8议论纷纷. 其实对js解析引擎没有深 ...

  9. (转载)HTML、CSS、JavaScript、PHP、MySQL 的学习顺序是什么?

    文章转载自 鸟巢 - 技术分享的社区 http://t.runoob.com/question/13 1.HTML.CSS.JavaScript 前端学习三部曲,照着这个顺序依次学习 HTML教程.C ...

随机推荐

  1. 快递api网接口快递调用方法

    ----------------实体类 [DataContract] public class SyncResponseEntity { public SyncResponseEntity() { } ...

  2. 性能工具MiniProfiler在Asp.Net WebForm跟踪EntityFramework

    http://www.xuebuyuan.com/356638.html 选择MiniProfiler.EF 默认会把MiniProfiler安装上,笔者程序是Asp.Net WebForm 不需要安 ...

  3. LSM存储模型

    LSM存储模型 数据库有3种基本的存储引擎: 哈希表,支持增.删.改以及随机读取操作,但不支持顺序扫描,对应的存储系统为key-value存储系统.对于key-value的插入以及查询,哈希表的复杂度 ...

  4. 新浪微博客户端(34)-block的细节与本质

    main.m #import <Foundation/Foundation.h> void test4(); int main(int argc, const char * argv[]) ...

  5. [CentOs7]安装mysql(2)

    摘要 之前安装过一次mysql,最后配置,发现在本地无法连接,重启服务的时候一直卡在那里不动,感觉是安装的过程出问题,最后没办法还是卸载了,然后重新安装一下. [CentOs7]安装mysql Mys ...

  6. Mono资源

    摘要 最近看了一部分mono方面的资料,这里整理一下,在这里列一个目录,方便以后用到的时候查找. Mono Mono 是一个由 Xamarin 公司(先前是 Novell,最早为 Ximian)所主持 ...

  7. 给各位聚聚和大大介绍一个开源项目 Expression2Sql(转)

    阅读目录 一.Expression2Sql介绍 二.单表简单查询 三.Where条件 四.多表关联查询 五.group by 六.order by 七.函数 八.delete 删除 九.update ...

  8. linux下google chrome浏览器字体修改

    今天安装了最新的chrome,我是下载的.deb包直接安装的. 安装完后,用chrome浏览页面时,发现字体有的大,有的小,还不清楚. 于是在网上搜索了一下如何设置字体. 1.打开Chrome浏览器. ...

  9. Vim 的 tab 设置

    文章转自:http://blog.csdn.net/shell_picker/article/details/6033023 摘自 Vim 手册: 选项:1. tabstop:表示一个 tab 显示出 ...

  10. django学习<二>:连接数据库

    发现假如没有很迫切的实际需求或者外界的压力的话,我这种人就很容易偷懒,之前看了一篇比较权威的谈django的文章,里面列举支持的数据库只有四种, 可是我熟悉的数据库只有sqlserver,然后就又怠工 ...