js this指向问题
今天就专门总结一下js中this的指向问题。今天通过题目的方式理解一下this指向,就不从理论上深入了,理论放在以后对闭包、作用域链等总结时候再与此联系起来。
先来几条纲领:
1.函数在被直接调用的时候,其中的this指针永远指向window
2.匿名函数this总是指向window对象
3.谁执行函数,this就指向谁
4.如果函数new了一下,那么就会创建一个对象,并且this指向新创建的对象
下面通过大量例子的对比来理解this指向
var name = "win";
var obj = {
name: "obj",
func: function() {
var self = this;
console.log(this.name);// obj
console.log(self.name);// obj
//a方法
(function() {
console.log(this.name);// win
console.log(self.name);// obj
}());
//b方法
(function() {
console.log(this.name);// win
console.log(self.name);// obj
})();
setTimeout(function(){
console.log(this.name);// win
console.log(self.name);// obj
}, 100);
}
}
obj.func();
这个题目中,用了一个self的变量将this的指向保存了下来,obj外面的name是个全局变量。
a方法和b方法效果其实是一样的,因为它们都是匿名函数,根据规则2,此时this指向window,因此this.name相当于window.name,结果自然是"win"
setTimeout中第一个参数同样是一个匿名函数,它的this同样指向window,结果同样是"win"
而self是obj事先保存下来的this指向,它始终指向obj,因此this.name相当于obj.name,结果自然是"obj"
-----------------------------------分割线-----------------------------------
var name = "the window";
var obj = {
name: "my object",
getName: function() {
return this.name;
}
};
//1
console.log( obj.getName() ); // my object
//2
console.log( (obj.getName = obj.getName)() ); // the window
//3
var temp = obj.getName;
temp(); // the window
//4
console.log( obj.getName.call(window) ); // the window
//5
console.log( obj.getName.apply(window) ); // the window
//6
console.log( obj.getName.bind(window)() ); // the window
第1个应该没什么疑问,this指向obj
第2个这种写法将this指向改变了,this指向window
第3个与第2个类似,temp中window环境下执行,因此this指向window
第4、5、6都是强行将方法getName绑定到window上执行,因此this很自然的指向window
-----------------------------------分割线-----------------------------------
function test() {
this.number = 10;
alert(this.number);
(function() {
alert(this.number);
}());
};
test();// 10 10
test方法在window环境下执行,此处第一个this指向window就没有疑问了。至于第二个,根据规则2可解释,this指向window
function test() {
this.number = 10;
alert(this.number);
(function() {
alert(this.number);
}());
};
new test();// 10 undefined
此处函数test被new了一下,根据规则4,如果函数new了一下,那么就会创建一个对象,并且this指向新创建的对象,此时第一个this指向test对象。第二个根据规则2,this指向window
这里有个类似的小题目,可以练习一下:
var a = 5;
function test(){
a = 0;
console.log(a);
console.log(this.a);
var a;
console.log(a);
}
test();// ?
new test();// ?
tip: 这里还涉及到对变量提升的理解
-----------------------------------分割线-----------------------------------
var length = 100;
function fn(){
console.log(this.length)
}
function Test(a, b){
var t1 = arguments.length;
var t2 = Test.length;
console.log(arguments[0]);
console.log(a);
console.log(a === arguments[0]);// true
a();// 100
arguments[0]();// 4
console.log(t1, t2);// 4 2
}
Test(fn, fn, fn, fn);
arguments.length表示实际参数个数,Test.length表示定义形参个数
大家可以思考下a()和结果与arguments[0]()执行的结果为什么不相同呢?明明 a === arguments[0] 返回true
原来,就是因为this在作怪。a()执行时是在window环境下的,因此this指向window。而arguments[0]()执行时却是在对象arguments环境下执行的,this指向arguments,求它的length属性,自然是4,因为它的实际参数确实是4个
-----------------------------------分割线-----------------------------------
来一道比较坑的题目^_^
var number = 2;
var obj = {
number: 4,
fn1: (function(){
this.number *= 2;
number = number * 2;
var number = 3;
return function(){
this.number *= 2;
number *= 3;
console.log(number);
}
})(),
fn2: function(){
this.number *= 2;
}
};
//第一步
var fn1 = obj.fn1;
//结果: window.number == 4, number == 3, obj.number == 4
console.log(number);// 4
//第二步
fn1();// 9
//结果: window.number == 8, number == 9, obj.number == 4
//第三步
obj.fn1();// 27
//结果: window.number == 8, number == 27, obj.number == 8
console.log(window.number);// 8
console.log(obj.number);// 8
首先要明确:obj.fn1在定义的时候就执行了前面3行,并返回了一个函数,由于是闭包,obj.fn1中的内容执行完毕后并没有被销毁,而是保留在了内存中。
第一步,将obj.fn1执行后返回的函数赋给全局变量fn1。
函数执行时,this.number *= 2,根据规则2,this指向window,相当于执行window.number *= 2,原本window.number定义为2,因此此时window.number == 4。
number = number * 2; var number = 3;这里涉及到变量提升,等价代码如下:
var number;
number = number * 2;
number = 3;
第1行执行完毕number为undefined;第2行执行完number为NaN;第3行执行完number为3;因此这里的number = number * 2;并没有什么卵用
好了,第一步执行完,整理一下此时各个变量的值:
window.number == 4, number == 3, obj.number == 4
第二步,执行函数fn1,也就是:
this.number *= 2;
number *= 3;
console.log(number);
此时在window下执行fn1,因此this指向window,上一步执行完window.number == 4,执行完this.number *= 2后window.number变成了8
轮到执行number *= 3了,我们发现fn1中并没有number的定义,需要到上一级的作用域中寻找number,之前已经说了,闭包并没有销毁,依然保存在了内存中,第一步执行完后结果为3,因此这里执行完number *= 3后number的值变为9
第二步执行完,整理一下此时各个变量的值:
window.number == 8, number == 9, obj.number == 4
第三步,执行obj.fn1,根据规则3,此时this指向obj,obj.number == 4
obj下执行:
this.number *= 2;
number *= 3;
console.log(number);
第1行,this.number *= 2相当于obj.number *= 2,执行完后,obj.number == 8;
第2行,number *= 3执行,原来保存下来的number == 9,执行完后,number == 27
第三步执行完,整理一下此时各个变量的值:
window.number == 8, number == 27, obj.number == 8
-----------------------------------分割线-----------------------------------
最后还有个练习题目:
function test() {
this.data = 5;
this.log = function() {
console.log(this.data);
}
}
var a = new test();
a.log();// ?
(a.log)();// ?
(a.log = a.log)();// ?
var b = a.log;
b();// ?
(function(){
a.log();// ?
})();
setTimeout(a.log, 100);// ?
setTimeout(function(){
a.log();// ?
}, 100);