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. Specified key was too long; max key length is 767 bytes mysql

    Specified key was too long; max key length is 767 bytes 说明: 执行当前 Web 请求期间,出现未经处理的异常.请检查堆栈跟踪信息,以了解有关该 ...

  2. NoSQL 简介及什么是AICD

    NoSQL 简介 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL". 在现代的计算系统上每天网络上都会产生庞大的数据量. 这些数据有很大一部分是由关 ...

  3. Ajax load html page

    jQuery ajax - load() 方法 jQuery Ajax 参考手册 实例 使用 AJAX 请求来改变 div 元素的文本: $("button").click(fun ...

  4. 边框(border)边距(margin)和间隙(padding)属性的区别

    边框属性(border):用来设定一个元素的边线.边距属性(margin):用来设置一个元素所占空间的边缘到相邻元素之间的距离.间隙属性(padding):用来设置元素内容到元素边界的距离.这三个属性 ...

  5. 解决label点击事件触发两次问题

    问题描述: 通常,为了用户体验,我们点击单选框或者复选框后面文字,即可选中当前项.代码如下: <label> <input type="radio" name=& ...

  6. 降维PCA技术

    降维技术使得数据变得更易使用,并且它们往往能够去除数据中的噪声,使得机器学习任务往往更加精确. 降维往往作为预处理步骤,在数据应用到其它算法之前清洗数据.有很多技术可以用于数据降维,在这些技术中,独立 ...

  7. Linux下的特殊权限SetUID

    1.SetUID的功能 只有可以执行的二进制程序才能设置SUID权限 命令执行者要对改程序拥有x执行权限 命令执行者在执行改程序的时候获得该程序文件属主的身份(在执行程序的过程中灵魂附体为文件的属性) ...

  8. 网页引导:jQuery插件实现的页面功能介绍引导页效果

    现在很多网站不仅是介绍,更多的是有一些功能,怎么样让客户快速的知道网站有哪些功能呢?这里pagewalkthrough.js插件能帮我们实现,它是一个轻量级的jQuery插件,它可以帮助我们创建一个遮 ...

  9. 有利于SEO优化的DIV+CSS的命名规则小结

    可以先去这里温习一下CSS和HTML的知识!DIV+CSS规范命名大全集合  CSS开发技巧整理 一.CSS文件及样式命名 1.CSS文件命名规范 全局样式:global.css/master.css ...

  10. sql事务和锁

    摘自:http://www.cnblogs.com/lxconan/archive/2011/10/20/sql_transaction_n_locks_1.html 最近在项目中进行压力测试遇到了数 ...