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和变量的影响加深了作用域和执行上下文的理解,从而帮助我们更好的阅读和编写代码。

最新文章

  1. Oracle 数据库基础学习 (七) SQL语句综合练习
  2. 【积累】validate验证框架的使用
  3. 分组找ID
  4. C#同一项目中不同文件或类中的方法进行调用
  5. SpringBoot配置Email发送功能
  6. wpf 旋转效果
  7. 搭建你的第一个Django应用程序
  8. ArcGlobe点击IGlobeServerLayer图层读取信息
  9. poj 2196 Specialized Four-Digit Numbers
  10. 演义江湖PC端意见汇总
  11. linxu,window系统
  12. 初识Java--线程同步(2)
  13. Spring整合JMS(一)-基础篇
  14. Chipmunk僵尸物理对象的出现和解决(二)
  15. JS响应数据
  16. [java,2017-06-12] myEclipse双击无法打开文件
  17. 学习Python第六天
  18. js------保留指定位数小数
  19. ubuntu下以16进制形式查看class文件、反编译class文件
  20. kvm图形化管理工具

热门文章

  1. 工具,百度编辑器 UEditor 使用实例化
  2. php.ini中有关安全的设置
  3. 缺陷跟踪系统Mantis Bug Tracker
  4. tar命令的详细解释
  5. 不解压直接查看tar包内容
  6. CF449B Jzzhu and Cities (最短路)
  7. 常见的几个angular.js的问题
  8. codevs2574 波兰表达式
  9. Hadoop 之MongoDB
  10. C++调用shell