本文是在《你不知道的JS》上阅读了其他的资料之后整理的。
this是很重要的一个对象,它不指向它本身不指向它的作用域,它指向函数调用所处的位置。需要分析函数的调用栈,调用位置就是调用栈中当前函数的上一个函数。
比如如下代码:
function foo(){
console.log(this.a);
}
var a = 2;
var obj = {
a : 1,
foo: foo
}
obj.foo(); // 1 调用栈为 obj->foo,所以this指向obj
var bar = obj.foo;
foo(); // 2 foo在全局作用域下被调用,所以this指向全局作用域
bar(); // 2 bar其实就是foo,所以调用栈就是foo,this指向全局作用域
首先函数的调用方式大致分为普通函数调用,作为对象方法调用,call/apply调用,new构造器调用。我们分别来说这几种情况下this的指向问题,以及几种调用情况共存时候的优先级。
new绑定
new一个对象的具体过程如下:
- 新创建一个对象;
- 建立这个对象与原型链的联系;
- 将这个新对象绑定this;
- 如果没有返回的对象,就自动返回this。
所以用new创建出来的函数,this都指向这个新建的实例。
function foo(){
this.a = 2;
this.hello = function(){
console.log(this.a);
}
}
var bar = new foo();
bar.hello(); // 2
正常情况下,foo函数中的this指向全局作用域,但是new的操作强行将新创建的bar对象绑定this。new绑定的优先级是最高的。
显示绑定
直接用apply或者call的方式强行指定this对象。
function foo(){
console.log(this.a);
}
var a = 2;
var obj = {
a : 1
};
var obj2 = {
a : 10
};
foo.call( obj ); // 1 强制将this指向obj对象
foo.call( obj2 ); // 10 强制将this指向obj2对象
foo(); //2
这里仍然有个问题,foo的this可以被改变的。下面这种硬绑定的方法可以在一般情况下,foo的this固定。
function foo(){
console.log(this.a);
}
var obj = {
a : 1
};
var obj1 = {
a : 10
};
function bar(){
return foo.apply(obj, arguments) ;
}
bar(); // 1
bar.apply(obj1); // 1 this仍然指向obj,未改变
ES5中封装了bind函数,去实现上述的功能。简单的实现版本如下
Function.prototype.bind = function(context){
var self = this;
return function(){
return self.apply(context, arguments);
}
}
另外,在原生JS中,有些接口本来就能做this的设置,比如forEach, map, every, some, map, filter等。显示绑定的优先级仅次于new绑定。
隐式绑定
当函数作为对象的方法被调用时,this指向这个调用的对象。没什么特别之处,按照调用栈来看就好。优先级次之。需要注意的是,当多个属性嵌套时,使用最近原则。
function foo(){
console.log(this.a);
}
var a = 2;
var obj = {
a : 1,
foo: foo,
obj2: obj2
}
var obj2 = {
a : 2,
foo: foo
}
obj.obj2.foo(); // 2 调用栈为 obj->obj2->foo,所以this指向obj2
默认绑定
就是普通函数的调用。需要说明的是,在非严格模式下,this指向全局对象window,而严格模式下,this指向undefined。优先级最低。如果绑定null或者undefined,应用的是默认绑定规则。
箭头函数
我们先来看个例子。
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
这个例子看起来不是上面四个规则中的任何一个。 point.moveTo是以对象属性方法的方式调用的,当moveTo函数内部this应该指向point对象。当它并没有直接在moveTo函数内部直接调用this,而是在内部又定义了两个函数变量moveX,moveY,将两个匿名函数赋值给它们。那匿名函数内部的this应该指向point.moveTo? ………..(此时省略N个字,具体的原因谁能说说) 总之,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
另外,ES6出来了一个箭头函数,不仅是形式上的简化,它还严格符合词法作用域。可以替换平时写代码时加的var that = this
。
var point = {
x : 0,
y : 0,
moveTo : function(x, y) {
// 内部函数
var moveX = x => {this.x = x;};
// 内部函数
var moveY = y => {this.y = y;};
moveX(x);
moveY(y);
}
};
point.moveTo(1, 1);
point.x; //==>1
point.y; //==>1
参考资料:
https://github.com/getify/You-Dont-Know-JS 你不知道的JS
http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html JavaScript的this用法
https://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/ 深入浅出 JavaScript 中的 this
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this MDN this