本文深入解析JavaScript中this关键字的绑定规则。详细讲解对象方法调用、普通函数调用、构造函数调用以及apply/call调用四种方式下this的指向差异。特别针对内部函数this丢失问题,介绍保存this变量的传统解法,并重点阐述ES6箭头函数如何固定this指向定义时上下文,以及call/apply在箭头函数中失效的特性。
JavaScript this关键字概述
本文通过具体实例详细解析JavaScript中this关键字的使用机制,旨在帮助读者深入理解并正确应用,供参考学习。
JavaScript中的this指向具有多样性,它可能指向全局对象、当前所在对象或任意指定对象,具体取决于函数的调用方式。主要包含以下几种调用场景:作为对象的方法调用、作为普通函数直接调用、作为构造函数调用,以及通过apply或call方法调用。
对象方法调用中的this指向
对象方法调用场景
当函数作为对象的方法被调用时,this关键字会被自动绑定到该方法所属的对象上。
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
this.x = this.x + x;
this.y = this.y + y;
}
};
point.moveTo(1, 1)//this 绑定到当前对象,即 point 对象
需要特别强调的是,this的值是在函数执行时刻动态确定的,而非在函数定义时确定。即使是以对象方法的形式调用,如果将该方法以函数引用的形式传递到其他作用域中执行,this的指向也会随之改变。以下示例说明了这一机制:
var a = {
aa : 0,
bb : 0,
fun : function(x,y){
this.aa = this.aa + x;
this.bb = this.bb + y;
}
};
var aa = 1;
var b = {
aa:0,
bb:0,
fun : function(){return this.aa;}
}
a.fun(3,2);
document.write(a.aa);//3,this指向对象本身
document.write(b.fun());//0,this指向对象本身
(function(aa){//注意传入的是函数,而不是函数执行的结果
var c = aa();
document.write(c);//1 , 由于fun在该处执行,导致this不再指向对象本身,而是这里的window
})(b.fun);
通过上述示例可以清晰看到this指向的变化,这是初学者容易混淆的关键点。
普通函数调用与内部函数this丢失问题
普通函数调用场景
当函数被直接调用(非对象方法形式)时,this关键字默认绑定到全局对象(在浏览器环境中通常为window)。
var x = 1;
function test(){
this.x = 0;
}
test();
alert(x); //0
这种机制会导致潜在问题:在函数内部定义的内部函数,其this同样会指向全局对象,这往往与开发者的预期相悖。具体代码表现如下:
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
// 内部函数
var moveX = function(x) {
this.x = x;//this 绑定到了全局
};
// 内部函数
var moveY = function(y) {
this.y = y;//this 绑定到了全局
};
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
point.x; //==>0
point.y; //==>0
x; //==>1
y; //==>1
运行结果不仅未能实现预期的移动效果,反而意外生成了两个全局变量。解决这一问题的方法是,在进入内部函数之前,先将外层的this保存到一个变量中,然后在内部函数里使用该变量。修正后的代码如下:
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
var that = this;
// 内部函数
var moveX = function(x) {
that.x = x;
};
// 内部函数
var moveY = function(y) {
that.y = y;
}
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
point.x; //==>1
point.y; //==>1
构造函数调用中的this应用
构造函数调用场景
在JavaScript中,当使用new关键字创建构造函数实例时,可以利用this关键字指向新创建的对象。这种方式可以有效避免函数内部this指向全局对象的问题。
var x = 2;
function test(){
this.x = 1;
}
var o = new test();
alert(x); //2
apply与call方法对this的控制
apply或call调用场景
apply和call方法的主要作用是改变函数执行时的上下文环境,即手动指定this绑定的对象。两者的功能相似,区别在于参数传递方式:apply要求参数以数组形式传入,而call要求参数逐个分开传入。此处以apply为例进行说明:
var name = "window";
var someone = {
name: "Bob",
showName: function(){
alert(this.name);
}
};
var other = {
name: "Tom"
};
someone.showName(); //Bob
someone.showName.apply(); //window
someone.showName.apply(other); //Tom
从结果可以看出,正常访问对象方法时,this指向该对象。使用apply且无参数时,this指向全局对象;使用apply并传入参数时,this则指向传入的那个参数对象。
ES6箭头函数的this固定机制
箭头函数调用场景
需要补充说明的是,在ES6标准引入的箭头函数中,this的指向始终绑定在函数定义时所处的上下文环境中,而不是函数执行时的环境。通过以下示例进行理解:
var o = {
x : 1,
func : function() { console.log(this.x) },
test : function() {
setTimeout(function() {
this.func();
}, 100);
}
};
o.test(); // TypeError : this.func is not a function
上述代码执行报错,原因是this的指向从预期的对象o变为了全局对象。我们需要对代码进行如下修改:
var o = {
x : 1,
func : function() { console.log(this.x) },
test : function() {
var _this = this;
setTimeout(function() {
_this.func();
}, 100);
}
};
o.test();
通过预先在外部保存this变量即可解决。此时可以利用箭头函数的特性,如前所述,箭头函数的this始终指向定义时的上下文,而非执行时。因此,将上述代码修改为使用箭头函数的形式:
var o = {
x : 1,
func : function() { console.log(this.x) },
test : function() {
setTimeout(() => { this.func() }, 100);
}
};
o.test();
修改后,this成功指向对象o。还需注意一点:箭头函数中的this指向是固定的,不会随调用方式改变。这意味着即使使用call或apply方法,也无法改变箭头函数中this的指向。
var x = 1,
o = {
x : 10,
test : () => this.x
};
o.test(); // 1
o.test.call(o); // 依然是1
通过上述分析,可以清晰理解各种调用情况下this绑定对象的区别。
希望本文的阐述能对JavaScript程序设计有所帮助。