人生三大问题,我是谁?我从哪儿来?我到哪里去?哲学上,没有人能真正回答这三个问题,同样,在js中,因为情况太多,要想真正理解各种情况下js中this代表什么也是一比较难的事情,更加尴尬的是,this这个关键字,在js中不但复杂而且使用的很多,以至于不弄懂this到底代表什么,就不敢去写js,更不必谈去看别人的代码,更加的吃力。 理解this指向的一个原则,this的指向一定是一个对象,永远指向函数运行时所在的对象,而不是函数被创建时所在的对象,而且这个对象是最后调用函数的那个对象,


但是箭头函数和bind绑定过的函数除外。this的四大绑定规则:默认绑定、显式绑定、隐式绑定、new绑定,前面已经说到this不但复杂而且很重要,但是也不要怕,只要理解了显示绑定,基本就能掌握this,当然是建立在大量练习的前提下,因为隐式绑定和默认绑定就是从显式绑定派生来的,下面结合各种情况分析this的指向。

call 、apply与this(显式绑定)

call和apply的作用是给函数重新指定调用者,指定this的指向,在js中函数也是对象,既然是对象,就有方法,call、apply就是函数的方法,可以显式指定函数的this指向的对象,即call、apply第一个参数。


function foo() {
console.log( this.name);
}

var obj = {
name: '小明'
};

foo.call(obj); // => 小明
foo.apply(obj); // => 小明
var tmp = foo.bind(obj)
tmp() // => 小

bind与this(显式绑定,硬绑定)

bind和call,apply有点类似,只不过后者是立即执行,而bind是返回一个永远绑定到某对象的函数,bind 方法来设置函数的 this 值,而不用考虑函数如何被调用的。调用 f.bind(someObject) 会创建一个与 f 具有相同函数体和作用域的函数,但是在这个新函数中,this 将永久地被绑定到了 bind 的第一个参数,无论这个函数是如何被调用的。


function foo() {
return this.a;
}
var bar = foo.bind({
a: 'hello'
});
console.log(bar()) // hello
var o = {
a: 'world',
foo: foo,
bar: bar
};
console.log(o.foo(), o.bar()) // world hell

全局的this(默认绑定)


function foo() {
console.log( this);
}
foo();//windo

用显式绑定的方式去理解,以上代码可以理解为foo.call(window)。

new与this


function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
console.log(this);
}
//作为构造函数使用
var o = new Person("Lily", 18, "F"); //this为当前对象 Person {name: "Lily", age: 18, gender: "F"

构造函数与普通函数区别是调用方式而不是定义方式,使用new关键字就是构造函数,否则就是普通函数。new关键字改变了函数内this的指向,使其指向刚创建的对象。 不能返回一个空对象。


function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
return {};//返回空对象
}
//作为构造函数使用
var o = new Person("Lily", 18, "F");
console.log(o.name) //undefine

但是可以返回基本数据问题。


function Person(n, a, g){
this.name = n;
this.age = a;
this.gender = g;
return 1; //返回标量
}
//作为构造函数使用
var o = new Person("Lily", 18, "F");
console.log(o.name) //Lil

作为对象方法的this(隐式绑定)


let name = '全局';
var obj = {
name: '小明',
getName: function () {
return this.name;
}
};
//作为对象方法调用
console.log(obj.getName()); //小

用显式绑定的方式去理解,以上代码可以理解为obj.getName.call(obj)。 this指向最后一个调用函数的对象,而不是开始的那个对象


var parent = {
age:30,
son:{
age:5,
getAge:function(){
console.log(this.age); //5 ,会打印出来son的年龄,而不是parent的年龄
}
}
}
parent.son.getAge()

对象方法作为函数调用的this(默认绑定)


window.age = 100;
var obj = {
age: 10,
getAge: function () {
return this.age;
}
};
var getAge = obj.getAge;
//作为普通函数调用
console.log(getAge()); //10

尽管getAge方法是obj对象的, 但是最终调用这个方法的对象是window,可以理解为getAge.call(window)

对象原型链上的this


function Foo(){
this.x = 10;
}
Foo.prototype.getX = function () {
console.log(this); //Foo {x: 10, getX: function}
console.log(this.x); //10
}
var foo = new Foo();
foo.getX()

在 Foo.prototype.getX 函数中,this 指向的 foo 对象。不仅仅如此,即便是在整个原型链中,this 代表的也是当前对象的值。

Es6 箭头函数中的 this

箭头函数不仅仅是匿名函数的简写,更大的好处是解决了匿名函数的this指向问题,有利于封装回调函数。 箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。 如何去理解定义函数所在作用域指向的对象,看了以下代码就会明白:


var name = 'window';
var A = {
name: 'A',
sayHello: () => {
console.log(this.name)
}
}
A.sayHello();// 这里不是输出A,而是输出windo

作用域是指函数内部,这里的箭头函数,也就是sayHello,所在的作用域其实是最外层的js环境,因为没有其他函数包裹;然后最外层的js环境指向的对象是winodw对象,所以这里的this指向的是window对象。 如果要想this指向对象A,那么就要在箭头函数外面再包一层函数


var name = 'window';
var A = {
name: 'A',
sayHello: function(){
var s = () => console.log(this.name)
return s//返回箭头函数s
}
}
var sayHello = A.sayHello();
sayHello();// 输出A

具体逻辑是因为sayHello是一个函数所以箭头函数s作用域是sayHello,而sayHello指向的对象是A, 所以箭头函数的this指向对象A。 由于JavaScript函数对this绑定的错误处理,下面的代码无法得到预期结果,fn函数中的this会指向指向window或undefined,需要使用var that = this的hack方法修正。


var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = function () {
return new Date().getFullYear() - this.birth; // this指向window或undefined
};
return fn();
}
}

但是箭头函数完全修复了this的指向,this总是指向词法作用域,也就是外层调用者obj:


var obj = {
birth: 1990,
getAge: function () {
var b = this.birth; // 1990
var fn = () => new Date().getFullYear() - this.birth; // this指向obj对象
return fn();
}
};
obj.getAge(); // 2

因为this在箭头函数中已经按照词法作用域绑定了,箭头函数中的this可不改变,仅仅与其定义的位置有关,所以,用call()或者apply()调用箭头函数时,无法对this进行绑定,即传入的第一个参数被忽略。


var obj = {
birth: 1990,
getAge: function (year) {
var b = this.birth; // 1990
var fn = (y) => y - this.birth; // this.birth仍是1990
return fn.call({birth:2000}, year);//call方法的第一个参数会被忽略
}
};
obj.getAge(2015); // 2

DOM事件绑定与this

函数被用作事件处理函数时,它的this指向触发事件的元素 click me! btn1 click me! btn2 内联事件的this指向window


<button id='btn1' onclick="clickHandler()">click me! btn1</button>
<button id='btn2' onclick="clickHandler()">click me! btn2</button>
<script>
function clickHandler() {
console.log(this, this.id)
}
</script

如果要想在内联事件处理函数中的 this 也指向该元素,那么我们可以用 bind 函数指定其 this 值。


<button id='btn1' onclick="clickHandler.bind(this)()">click me! btn1</button>
<button id='btn1' onclick="clickHandler.call(this)">click me! btn1</button>
<button id='btn1' onclick="clickHandler.apply(this)">click me! btn1</button

window定时器与this

由于setTimeout()调用的代码运行在与所在函数完全分离的执行环境上。这会导致这些代码中包含的 this 关键字会指向 window (或全局)对象。


var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(function(){
console.log(this.num);
}, 1000)
}
}
var obj = new Obj;
obj.getNum();//1  打印的为obj.num,值为1
obj.getNumLater()//0  期望打印出来obj的num,然后却打印出来window的num,this指向了windo

如果要想定时器里面的this指向Obj,


var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(function(){
console.log(this.num);
}.bind(this), 1000)//利用bind()将this绑定到这个函数上
}
}
var obj = new Obj;
obj.getNum();//1  打印的为obj.num,值为1
obj.getNumLater()//1  打印的为obj.num,值为

或者使用箭头函数纠正this的指向


var num = 0;
function Obj (){
this.num = 1,
this.getNum = function(){
console.log(this.num);
},
this.getNumLater = function(){
setTimeout(() => {console.log(this.num);}, 1000)
}
}
var obj = new Obj;
obj.getNum();//1  打印的为obj.num,值为1
obj.getNumLater()//0  打印的为window.num,值为

with和this

with 语句 为一个或一组语句指定默认对象,使用 with 语句时,代码变得更短且更易读,如果在 with 语句块中使用 this,它就代表 with 所指定的对象。,但是因为with会降低代码的效率,所以在实际开发中不推荐使用,所以不详细讨论。

严格模式下的this

严格模式并没有让this变复杂, 严格模式影响的只是context的默认值,仅此而已。

[]与this,迷惑的[]


function fn (){ console.log(this) }
var arr = [fn, fn2]
arr[0]()

这段代码很容易把arr[0]() 理解成fn(),从而让人误解执行的过程是fn.call(window),从而让人理解this指向window,然后实际上的执行过程是arr[0]() => arr.fn() => this指向arr。 总结,this的指向一定是一个对象,永远指向函数运行时所在的对象,而不是函数被创建时所在的对象,而且这个对象是最后调用函数的那个对象,但是箭头函数和bind绑定过的函数除外: 1 . 函数是否在 new 中调用 (new 绑定),如果是,那么 this 绑定的是新创建的对象。 2 . 函数是否通过 call,apply 调用,或者使用了 bind(即硬绑定),如果是,那么 this 绑定的就是指定的对象。 3 . 函数是否在某个上下文对象中调用 (隐式绑定),如果是的话,this 绑定的是那个上下文对象。一般是 obj.foo()。 4 . 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。 5 . 如果把 Null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。 6 . 如果是箭头函数,箭头函数的 this 继承的是外层代码块的 this。

参考文章

​JavaScript this详解​​​

​箭头函数​​​

​Javascript this详解​​​

​全面解析 Javascript - this​