分类: javascript 1005阅读阅读模式
this 关键字是 JavaScript 中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数的作用域中。this 的绑定一直是一件非常令人困惑的事,即使经验丰富的开发者有时也较难说清它的指向。
当一个函数被调用时,会创建一个活动记录
(有时候也称为执行上下文
)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this
就是记录(上下文)的其中一个属性,会在函数执行的过程中用到。
一、this的指向
this 总是指向执行时
的当前对象
。
JavaScript 的 this 总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境。
也就是说 this 的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
除了使用 with 和 eval 的情况,常用的可分为以下几种:
- 作为对象的方法调用。
- 作为普通函数调用。
- 构造器调用。
- Function.prototype.call 或 Function.prototype.apply 调用。
1. 作为对象的方法调用
对象的方法被调用时,this 指向该对象。
- var obj = {
- a: 1,
- getA() {
- alert ( this === obj ); // 输出:true alert ( this.a ); // 输出: 1
- }
- };
- obj.getA();
2. 作为普通函数调用
当函数不作为对象的属性被调用时,也就是我们常说的普通函数方式,此时的 this 总是指向全局对象。
这里注意对象的方法被单独拷贝出来后执行,那么原来的this会丢失
,变成指向全局对象
- //在浏览器的JavaScript 里,这个全局对象是window 对象。
- window.name = 'globalName';
- var getName = function(){
- return this.name;
- };
- console.log( getName() ); // 输出:globalName
- //或者:
- window.name = 'globalName';
- var myObject = {
- name: 'sven',
- getName: function(){
- return this.name;
- }
- };
- var getName = myObject.getName;
- console.log( getName() ); // globalName
3. 构造器调用
通常情况下,构造器里的 this 就指向返回的这个对象;
- 如果构造器不显式地返回任何数据,或者是返回一个非对象类型的数据,this指向返回的这个对象;
- 如果构造器显式地返回了一个对象,则实例化的结果会返回这个对象,而不是this;
- //构造器里的this 就指向返回的这个对象,见如下代码:
- var MyClass = function(){
- this.name = 'sven';
- };
- var obj = new MyClass();
- alert ( obj.name ); // 输出:sven
- var MyClass = function(){
- this.name = 'sven';
- return { // 显式地返回一个对象
- name: 'anne'
- }
- };
- var obj = new MyClass();
- alert ( obj.name ); // 输出:anne
- var MyClass = function(){
- this.name = 'sven'
- return 'anne'; // 返回string 类型
- };
- var obj = new MyClass();
- alert ( obj.name ); // 输出:sven
4. call 或 apply 调用
apply和call可以动态地改变传入函数的 this
- var obj1 = {
- name: 'sven',
- getName: function(){
- return this.name;
- }
- };
- var obj2 = {
- name: 'anne'
- };
- console.log( obj1.getName() ); // 输出: sven
- console.log( obj1.getName.call( obj2 ) ); // 输出:anne
二、call, apply 和 bind
简介及区别
call 和 apply作用一模一样,区别仅在于传入参数形式的不同。
apply
apply
接受两个参数,第一个参数指定了函数体内 this 对象的指向,第二个参数为一个带下标的集合,这个集合可以为数组,也可以为类数组
。
类数组:
- 对象本身要可以存取属性;
- 对象的 length 属性可读写。
call
call
传入的参数数量不固定,第一个参数也是代表函数体内的 this 指向,从第二个参数开始往后,每个参数被依次传入函数
bind
bind
参数类型和 call 相同,不过它不会执行函数,而是修改 this 后返回一个新的函数
- var func = function( a, b, c ){
- alert ( [ a, b, c ] );
- };
- func.apply( null, [ 1, 2, 3 ] );
- func.call( null, 1, 2, 3 );
call 和 bind 是包装在 apply 上面的语法糖
:
- Function.prototype.call = function( context ){
- var argus = [];
- for (var i = 1; i < arguments.length; i++) {
- argus.push(arguments[i]);
- }
- this.apply(context, argus);
- };
- Function.prototype.bind = function( context ){
- var self = this; // 保存原函数
- // 返回一个新的函数
- return function(){
- // 执行新的函数的时候,会把之前传入的 context // 当作新函数体内的 this
- return self.apply( context, arguments );
- }
- };
用途
- 改变 this 指向
- 借用其他对象的方法: 在
操作类数组
(比如 arguments) 的时候,可以找 Array.prototype 对象借用方法Array.prototype.push.call(a, 'first' )
三、eval 和 with
词法作用域由写代码期间函数所声明的位置来定义,而 eval 和 with 可以在运行时修改词法作用域。
eval(..) 和 with 会在运行时修改或创建新的作用域,以此来欺骗其他在书写时定义的词 法作用域。 看起来它们能实现更复杂的功能,并且代码更具有扩展性。
但这些功能已经过时,性能也较差,使用不当容易导致难以预料的问题,已经不被提倡,严格模式下会被限制和禁止,所以不要轻易使用它们。
eval
eval(..) 函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
- function foo(str, a) {
- eval( str );
- console.log( a, b );
- }
- var b = 2;
- foo( "var b = 3;", 1 ); // 1, 3
- // 相当于
- function foo(str, a) {
- var b = 3;
- console.log( a, b );
- }
字符串的内容可以是一段动态生成的函数代码。
JavaScript中 还有其他一些功能效果和eval(..)很相似。setTimeout
、setInterval
的第一个参数可以是字符串。
with
with 可以改变作用域,通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。
- var obj = {
- a: 1,
- b: 2,
- c: 3
- };
- // 单调乏味的重复 "obj"
- obj.a = 2;
- obj.b = 3;
- obj.c = 4;
- // 简单的快捷方式
- with (obj) {
- a = 3;
- b = 4;
- c = 5;
- }