Javascript this 的一些学习总结


Posted in Javascript onAugust 31, 2012

1.1.1 摘要
相信有C++、C#或Java等编程经验的各位,对于this关键字再熟悉不过了。由于Javascript是一种面向对象的编程语言,它和C++、C#或Java一样都包含this关键字,接下来我们将向大家介绍Javascript中的this关键字。

本文目录

•全局代码中的this
•函数中的this
•引用类型
•函数调用以及非引用类型
•引用类型以及this的null值
•函数作为构造器被调用时this的值
•手动设置函数调用时this的值

1.1.2 正文

由于许多面向对象的编程语言都包含this关键字,我们会很自然地把this和面向对象的编程方式联系在一起,this通常指向利用构造器新创建出来的对象。而在ECMAScript中,this不仅仅只用来表示创建出来的对象,也是执行上下文的一个属性:

activeExecutionContext = { 
// Variable object. 
VO: {...}, 
this: thisValue 
};

全局代码中的this
// Global scope 
// The implicit property of 
// the global object 
foo1 = "abc"; 
alert(foo1); // abc // The explicit property of 
// the global object 
this.foo2 = "def"; 
alert(foo2); // def 
// The implicit property of 
// the global object 
var foo3 = "ijk"; 
alert(foo3); // ijk

前面我们通过显式和隐式定义了全局属性foo1、foo2和foo3,由于this在全局上下文中,所以它的值是全局对象本身(在浏览器中是window object);接下来我们将介绍函数中的this。

函数中的this

当this在函数代码中,情况就复杂多了,并且会引发很多的问题。

函数代码中this值的第一个特性(同时也是最主要的特性)就是:它并非静态的绑定在函数上。

正如此前提到的,this的值是在进入执行上下文(Excution context)的阶段确定的,并且在函数代码中的话,其值每次都不尽相同。

然而,一旦进入执行代码阶段,其值就不能改变了。如果要想给this赋一个新的值是不可能的,因为在那时this根本就不是变量了。

接下来,我们通过具体的例子说明函数中的this。

首先我们定义两个对象foo和person,foo包含一个属性name,而person包含属性name和方法say(),具体的定义如下:

// Defines foo object. 
var foo = { 
name: "Foo" 
}; // Defines person object. 
var person = { 
name: "JK_Rush", 
say: function() { 
alert(this === person); 
alert("My name is " + this.name); 
} 
}; 
person.say(); // My name is JK_Rush 
// foo and person object refer to 
// the same function say 
foo.say = person.say; 
foo.say(); // My name is Foo.

通过上面的代码,我们发现调用person的say()方法时,this指向person对象,当通过赋值方式使得foo的say()方法指向peson中的say()方法时。我们调用foo的say()方法,发现this不是指向person对象,而不是指向foo对象,这究竟是什么原因呢?

首先,我们必须知道this的值在函数中是非静态的,它的值确定在函数调用时,具体代码执行前,this的值是由激活上下文代码的调用者决定的,比如说,调用函数的外层上下文;更重要的是,this的值是由调用表达式的形式决定的,所以说this并非静态的绑定在函数上。

由于this并非静态地绑定在函数上,那么我们是否可以在函数中动态地修改this的值呢?

// Defines foo object. 
var foo = { 
name: "Foo" 
}; // Defines person object. 
var person = { 
name: "JK_Rush", 
say: function() { 
alert(this === person); 
this = foo; // ReferenceError 
alert("My name is " + this.name); 
} 
}; 
person.say(); // My name is JK_Rush

现在我们在方法say()中,动态地修改this的值,当我们重新执行以上代码,发现this的值引用错误。这是由于一旦进入执行代码阶段(函数调用时,具体代码执行前),this的值就确定了,所以不能改变了。

引用类型

前面我们提到this的值是由激活上下文代码的调用者决定的,更重要的是,this的值是由调用表达式的形式决定的;那么表达式的形式是如何影响this的值呢?

首先,让我们介绍一个内部类型——引用类型,它的值可以用伪代码表示为一个拥有两个属性的对象分别是:base属性(属性所属的对象)以及该base对象中的propertyName属性:

// Reference type. 
var valueOfReferenceType = { 
base: mybase, 
propertyName : 'mybasepropertyName' 
};

引用类型的值只有可能是以下两种情况:

•当处理一个标识符的时候
•或者进行属性访问的时候
标识符其实就是变量名、函数名、函数参数名以及全局对象的未受限的属性。

// Declares varible. 
var foo = 23; // Declares a function 
function say() { 
// Your code. 
}

中间过程中,对应的引用类型如下:
// Reference type. 
var fooReference = { 
base: global, 
propertyName: 'foo' 
}; var sayReference = { 
base: global, 
propertyName: 'say' 
};

我们知道Javascript中属性访问有两种方式:点符号和中括号符号:
// Invokes the say method. 
foo.say(); 
foo['say']();

由于say()方法是标识符,所以它对应于foo对象引用类型如下:
// Reference type. 
var fooSayReference = { 
base: foo, 
propertyName: 'say' 
};

我们发现say()方法的base属性值为foo对象,那么它对应的this属性也将指向foo对象。

假设,我们直接调用say()方法,它对应的引用类型如下:

// Reference type. 
var sayReference = { 
base: global, 
propertyName: 'say' 
};

由于say()方法的base属性值为global(通常来说是window object),那么它对应的this属性也将指向global。

函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。

函数调用以及非引用类型
前面我们提到,当调用括号左侧为非引用类型的时,this的值会设置为null,并最终隐式转换为全局对象。

现在我们定义了一个匿名自执行函数,具体实现如下:

// Declares anonymous function 
(function () { 
alert(this); // null => global 
})();

由于括号()左边的匿名函数是非引用类型对象(它既不是标识符也不属于属性访问),因此,this的值设置为全局对象。
// Declares object. 
var foo = { 
bar: function () { 
alert(this); 
} 
}; (foo.bar)(); // foo. 
(foo.bar = foo.bar)(); // global? 
(false || foo.bar)(); // global? 
(foo.bar, foo.bar)(); // global

这里注意到四个表达式中,只有第一个表达式this是指向foo对象的,而其他三个表达式则执行global。

现在我们又有疑问了:为什么属性访问,但是最终this的值不是引用类型对象而是全局对象呢?

我们注意到表达式二是赋值(assignment operator),与表达式一组操作符不同的是,它会触发调用GetValue方法(参见11.13.1中的第三步)。 最后返回的时候就是一个函数对象了(而不是引用类型的值了),这就意味着this的值会设置为null,最终会变成全局对象。

第三和第四种情况也是类似的——逗号操作符和OR逻辑表达式都会触发调用GetValue方法,于是相应地就会丢失原先的引用类型值,变成了函数类型,this的值就变成了全局对象了。

引用类型以及this的null值
对于前面提及的情形,还有例外的情况,当调用表达式左侧是引用类型的值,但是this的值却是null,最终变为全局对象(global object)。 发生这种情况的条件是当引用类型值的base对象恰好为活跃对象(activation object)。

当内部子函数在父函数中被调用的时候就会发生这种情况,通过下面的示意代码介绍活跃对象:

// Declares foo function. 
function foo() { 
function bar() { 
alert(this); // global 
} 
// The same as AO.bar(). 
bar(); 
}

由于活跃对象(activation object)总是会返回this值为——null(用伪代码来表示AO.bar()就相当于null.bar()),然后,this的值最终会由null转变为全局对象。

当函数调用包含在with语句的代码块中,并且with对象包含一个函数属性的时候,就会出现例外的情况。with语句会将该对象添加到作用域链的最前面,在活跃对象的之前。 相应地,在引用类型的值(标识符或者属性访问)的情况下,base对象就不再是活跃对象了,而是with语句的对象。另外,值得一提的是,它不仅仅只针对内部函数,全局函数也是如此, 原因就是with对象掩盖了作用域链中更高层的对象(全局对象或者活跃对象):

函数作为构造器被调用时this的值
函数作为构造函数时,我们通过new操作符创建实例对象是,它会调用Foo()函数的内部[[Construct]]方法;在对象创建之后,会调用内部的[[Call]]方法,然后所有Foo()函数中this的值会设置为新创建的对象。

// Declares constructor 
function Foo() { 
// The new object. 
alert(this); 
this.x = 10; 
} var foo = new Foo(); 
foo.x = 23; 
alert(foo.x); // 23

手动设置函数调用时this的值
Function.prototype原型上定义了两个方法,允许手动指定函数调用时this的值。这两个方法分别是:.apply()和.call()。这两个方法都接受第一个参数作为调用上下文中this的值,而这两个方法的区别是传递的参数,对于.apply()方法来说,第二个参数接受数组类型(或者是类数组的对象,比如arguments), 而.call()方法接受任意多的参数(通过逗号分隔);这两个方法只有第一个参数是必要的——this的值。

通过示例代码介绍call()方法和apply()方法的使用:

var myObject = {}; 
var myFunction = function(param1, param2) { 
//setviacall()'this'points to my Object when function is invoked 
this.foo = param1; 
this.bar = param2; //logs Object{foo = 'foo', bar = 'bar'} 
console.log(this); 
}; 
// invokes function, set this value to myObject 
myFunction.call(myObject, 'foo', 'bar'); 
// logs Object {foo = 'foo', bar = 'bar'} 
console.log(myObject);

call()方法第一个参数是必要的this值,接着我们可以传递任意多个参数,接着介绍apply()方法的使用。
var myObject = {}; 
var myFunction = function(param1, param2) { //set via apply(), this points to my Object when function is invoked 
this.foo=param1; 
this.bar=param2; 
// logs Object{foo='foo', bar='bar'} 
console.log(this); 
}; 
// invoke function, set this value 
myFunction.apply(myObject, ['foo', 'bar']); 
// logs Object {foo = 'foo', bar = 'bar'} 
console.log(myObject);

通过与call()方法对比,我们发现apply()方法和call()方法没有太大的区别,只是方法签名不一样。

1.1.3 总结
本文介绍Javascript中this的使用,更重要的是帮助我们能更好地理解this值在全局、函数、构造函数以及一些特例的情况中值的变化。

对于在函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。

对于特例情况,我们要记住赋值符、逗号操作符以及||逻辑表达式,会使this丢失原先的引用类型值,变成了函数类型,this的值就变成了全局对象了

参考
[1] http://dmitrysoshnikov.com/ecmascript/chapter-3-this/ 英文版
[2] http://blog.goddyzhao.me/post/11218727474/this 译文
[3] https://net.tutsplus.com/tutorials/javascript-ajax/fully-understanding-the-this-keyword/

Javascript 相关文章推荐
动态创建的表格单元格中的事件实现代码
Dec 30 Javascript
图片onload事件触发问题解决方法
Jul 31 Javascript
javascript-简单的日历实现及Date对象语法介绍(附图)
May 30 Javascript
Express.JS使用详解
Jul 17 Javascript
javascript定义变量时带var与不带var的区别分析
Jan 12 Javascript
JavaScript中使用Math.PI圆周率属性的方法
Jun 14 Javascript
js console.log打印对像与数组用法详解
Jan 21 Javascript
讲解JavaScript的Backbone.js框架的MVC结构设计理念
Feb 14 Javascript
jQuery调用Webservice传递json数组的方法
Aug 06 Javascript
Angularjs 事件指令详细整理
Jul 27 Javascript
Vue实现简单分页器
Dec 29 Javascript
微信小程序中使用echarts的实现方法
Apr 24 Javascript
javascript动画浅析
Aug 30 #Javascript
jquery方法+js一般方法+js面向对象方法实现拖拽效果
Aug 30 #Javascript
JS跨域代码片段
Aug 30 #Javascript
JS跨域总结
Aug 30 #Javascript
js中判断Object、Array、Function等引用类型对象是否相等
Aug 29 #Javascript
xml转json的js代码
Aug 28 #Javascript
基于jquery创建的一个图片、视频缓冲的效果样式插件
Aug 28 #Javascript
You might like
解密ThinkPHP3.1.2版本之模板继承
2014/06/19 PHP
php实现的短网址算法分享
2014/06/20 PHP
PHP使用文件锁解决高并发问题示例
2018/03/29 PHP
Mozilla中显示textarea中选择的文字
2006/09/07 Javascript
js url传值中文乱码之解决之道
2009/11/20 Javascript
javascript 数组学习资料收集
2010/04/11 Javascript
NodeJs中的非阻塞方法介绍
2012/06/05 NodeJs
快速解决jquery之get缓存问题的最简单方法介绍
2013/12/19 Javascript
JS+CSS实现表格高亮的方法
2015/08/05 Javascript
js立即执行函数: (function ( ){})( ) 与 (function ( ){}( )) 有什么区别?
2015/11/18 Javascript
JavaScript中const、var和let区别浅析
2016/10/11 Javascript
Vue.js组件tabs实现选项卡切换效果
2016/12/01 Javascript
jQuery基本筛选选择器实例代码
2017/02/06 Javascript
[原创]SyntaxHighlighter自动识别并加载脚本语言
2017/02/07 Javascript
JQuery页面随滚动条动态加载效果的简单实现(推荐)
2017/02/08 Javascript
JavaScript实现按键精灵的原理分析
2017/02/21 Javascript
jQuery插件zTree实现的多选树效果示例
2017/03/08 Javascript
vue-router路由懒加载和权限控制详解
2017/12/13 Javascript
Fundebug支持监控微信小程序HTTP请求错误的方法
2019/02/21 Javascript
webpack项目使用eslint建立代码规范实现
2019/05/16 Javascript
Node4-5静态资源服务器实战以及优化压缩文件实例内容
2019/08/29 Javascript
用Python遍历C盘dll文件的方法
2015/05/06 Python
浅析python中SQLAlchemy排序的一个坑
2017/02/24 Python
python 全局变量的import机制介绍
2017/09/07 Python
Python实现插入排序和选择排序的方法
2019/05/12 Python
Python设置matplotlib.plot的坐标轴刻度间隔以及刻度范围
2019/06/25 Python
关于jupyter打开之后不能直接跳转到浏览器的解决方式
2020/04/13 Python
全球虚拟主机商:HostGator
2017/02/06 全球购物
生产主管岗位职责
2013/11/10 职场文书
预备党员转正思想汇报
2014/01/12 职场文书
给老师的检讨书
2014/02/11 职场文书
读书伴我成长演讲稿
2014/05/07 职场文书
销售人才自我评价范文
2014/09/27 职场文书
小学音乐教师个人工作总结
2015/02/05 职场文书
病危通知单
2015/04/17 职场文书
MySQL数据库 任意ip连接方法
2022/05/20 MySQL