JS中function的this指向

2022/10/19

JS 里的 this 我以前也会记成“谁调用就指向谁”,但这句话只能覆盖一部分情况。更准确一点说,普通函数的 this 是在调用时决定的,箭头函数没有自己的 this,它拿的是定义时外层作用域里的 this

所以看 this 的时候,我一般不会先看函数写在哪里,而是先看它是怎么被调用的。

一、对象方法里的 this

最常见的是对象方法调用:

const obj = {
  name: 'Alice',
  walk() {
    console.log(this.name);
  },
};

obj.walk(); // Alice

这里 walk 是作为 obj 的方法被调用的,所以普通函数里的 this 指向 obj

如果把这个方法拿出来单独调用,结果就变了:

const fn = obj.walk;
fn();

这时已经不是 obj.walk() 这种调用形式了,普通函数里的 this 不再指向 obj。在浏览器非严格模式下可能是 window,严格模式或 ES Module 里通常是 undefined

二、箭头函数不适合做对象方法

对象里如果直接写箭头函数,很容易让人误会:

const obj = {
  name: 'Alice',
  walk: () => {
    console.log(this.name);
  },
};

obj.walk();

这里 this 不会因为 obj.walk() 就指向 obj。箭头函数没有自己的 this,它会沿着外层作用域找 this。如果这段代码在浏览器全局作用域里,可能拿到的是 window;如果在模块或严格模式里,可能是 undefined

所以对象方法里需要用到当前对象时,我一般会用普通函数或者方法简写,不会用箭头函数。

三、事件监听里的 this

事件监听里也有一个很典型的区别:

const button = document.querySelector('button');

button.addEventListener('click', function () {
  console.log(this); // button
});

button.addEventListener('click', () => {
  console.log(this); // 不是 button
});

普通函数作为事件回调时,浏览器会把 this 设为触发事件的元素,也就是这里的 button。但箭头函数不会绑定自己的 this,所以它里面的 this 仍然来自外层。

如果我要在事件回调里拿当前元素,通常会用普通函数,或者直接用事件对象:

button.addEventListener('click', (event) => {
  console.log(event.currentTarget);
});

这样就不依赖箭头函数里的 this 了。

四、call、apply、bind 可以手动改 this

普通函数的 this 还可以用 callapplybind 手动指定。

const person = {
  name: 'Alice',
  sayHi(greeting) {
    console.log(`${greeting}, I'm ${this.name}`);
  },
};

const another = {
  name: 'Bob',
};

person.sayHi.call(another, 'Hello'); // Hello, I'm Bob
person.sayHi.apply(another, ['Hi']); // Hi, I'm Bob

const boundSayHi = person.sayHi.bind(another, 'Hey');
boundSayHi(); // Hey, I'm Bob

callapply 都是立即执行,区别主要是传参形式不同:call 是一个个传,apply 是数组传。bind 不会立即执行,它会返回一个新的函数,后面再调用。

五、我记 this 的方式

我现在会按这个顺序判断:

  1. 是不是箭头函数?如果是,就没有自己的 this,看外层。
  2. 是不是通过 obj.fn() 调用?如果是普通函数,this 通常指向 obj
  3. 是不是单独调用?如果是普通函数,严格模式下可能是 undefined
  4. 有没有用 callapplybind?如果有,就看手动绑定的对象。
  5. 是不是 DOM 事件普通函数回调?如果是,this 通常是触发事件的元素。

所以 this 不是固定看函数定义在哪,而是要结合函数类型和调用方式一起看。普通函数看调用,箭头函数看定义时的外层作用域,这样就不容易混了。