总结this指向问题

  • 白小霁
  • 14 Minutes
  • March 19, 2017

灵感

上个星期课余时间很少编程😏,联合了两三个寝室的同学一起玩转「狼人杀」游戏,直到昨晚老霄问我一个this指向的问题,让我有了少许的回顾时间。突然有一个灵感告诉我:JavaScript里的this其实就相当于「狼人杀」中跳身份以及逻辑判断(这真的是一个讲逻辑的游戏)。

出现地点

作为函数调用

在函数被直接调用时,this会绑定到全局对象。而在我们浏览器中,window就是全局对象

1
2
3
4
5
6
7
console.log(this) // Window{..}
function fn(){
console.log( this )
}
fn() // Window{..} 👉 window.fn()

其实很多时候,我们会忘记我们写的代码还在一个隐藏的作用域里(即全局作用域)。初学的时候,看见过这样的一句话「申明的变量都会挂在window(全局对象)上」,其实window就是一匹铁狼,还是那一匹我们最容易忘记它身份的😲

内部函数

当我们开始写一些嵌套函数的时候,当里面出现this的时候,请注意执行的地方。

1
2
3
4
5
6
7
8
function fn0(){
function fn(){
console.log(this); // 👇去找 fn 执行的地方 👇
}
fn(); // 👇 去找 fn0 执行的地方 👇
}
fn0(); // Window{..} 还是有Window调用的 👉 window.fn0()

setTimeout、setInterval

这两个是延时函数,运行的规则是看运行队列是否已经结束,最后由window执行回调函数,所以this指向的是全局对象

1
2
3
4
5
6
document.addEventListener('click', function(e){
console.log(this);
setTimeout(function(){
console.log(this);
}, 200);
}, false);

作为构造函数调用

构造函数就是通过一个函数生成一个新对象(object)。这时候,this的指向就是这个新对象。
因为使用了new运算符,其后面跟着一个函数F以及函数的参数:new F(args)。在生成一个新对象这一过程分为三步:

  1. 创建类的实例:把一个空对象的__proto__属性绑定到F.prototype
  2. 初始化实例:将传入的参数的函数F执行一遍,this被绑定到了这个实例上
  3. 返回实例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Person(name){
    this.name = name;
    }
    Person.prototype.printName = function(){
    console.log(this.name);
    };
    var p1 = new Person('Byron');
    p1.printName(); // Byron

关于new运算符的相关阅读

作为对象方法调用

如果一个函数是是写在对象里面,此时该函数叫做该对象的方法,这时调用函数,this自然绑定到改对象

1
2
3
4
5
6
7
8
var obj1 = {
name: 'Byron',
fn : function(){
console.log(this);
}
};
obj1.fn(); // Object{name:....} 👉 boj1

DOM对象绑定事件

在事件处理程序中this代表事件源DOM对象(低版本IE有bug,指向了window)

1
2
3
4
5
6
7
8
document.addEventListener('click', function(e){
console.log(this); // #document
var _document = this; // #document
setTimeout(function(){
console.log(this); // Window{...}
console.log(_document); //#document
}, 200);
}, false);

新招

上面只是列举了一些ES5语法下出现的方式。那时候我记得方法是谁调用,那么this指向的就是他,因为笔者专业偏管理,就想到会计中的谁受益谁承担的概念。
其实还有一种方法来解释this:

F.call(context,arg1)

函数还有call/apply的调用方法,而且里面的第一个参数context就代表着this的指向。call使用MDN介绍

1
2
3
4
5
6
function fn(){
console.log( this )
}
fn.call(underfined)

这时候你会说,那不应该是underfined吗?可是这个API的文档有规定:

如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象)

1
2
3
4
5
6
7
8
var obj1 = {
name: 'Byron',
fn : function(){
console.log(this);
}
};
obj1.fn.call(obj1);

那么其他的出现的方法也就类似的解法了

昨晚问题

1
2
3
4
5
6
7
8
9
10
11
12
13
X.prototype = {
name: 'x',
bindEvents:function(){
self = this
btn.addEventListener('click', this.yyy.bind(this))
// 这里的两个this指向的还是 X
// why?
// 看他们此时的作用域还是在 bindEvents 里面根本就不是 DOM元素绑定事件的作用域中
},
yyy: function(){
console.log(this.name) // 'x'
}
}

在线预览

总结

  1. 谁调用,this就是谁
  2. 可以转变call调用,this就是第一个参数
  3. 有时候细节很可怕,还要看清楚作用域在哪里