彻底搞懂JavaScript中的this指向

130 阅读3分钟

我们首先来看一个例子:

function fn() {
  console.log(this);
}
​
// 调用方式一: 直接调用
fn(); // window// 调用方式二: 将fn放到一个对象中,再调用
var obj = {
  name: "hh",
  fn: fn
}
obj.fn() // 对象obj

// 调用方式三: 通过call调用
fn.call("xxx"); // 字符串"xxx"

可以看到同一个函数通过不同的方式调用,其中的this也是不同的,而其中的绑定规则是怎样的呢?我们现在就来搞清楚!

1. 默认绑定

当函数作为独立函数(可以理解为函数没有绑定到任何对象上进行调用)调用为默认绑定,此时的this指向为全局对象(window)

例1:普通调用

function fn() {
  console.log(this); // window
}
​
fn();

例2:链式调用

function test1() {
  console.log(this); // window
  test2();
}
​
function test2() {
  console.log(this); // window
  test3()
}
​
function test3() {
  console.log(this); // window
}
test1();

例3:高阶函数调用

function fn(foo) {
  foo()
}
​
function bar() {
  console.log(this); // window
}
​
fn(bar);

这些例子的结果都为window,这是因为这些函数都没有任何的对象绑定,是作为一个独立函数的调用

2. 隐式绑定

当函数是通过某个对象进行调用为隐式绑定,此时的this指向为调用的对象

例1:通过对象调用

function fn() {
  console.log(this); // obj
}
​
var obj = {
  name: "why",
  fn: fn
}
​
obj.fn();

例2:隐式丢失

function fn() {
  console.log(this); // window
}
​
var obj = {
  name: "obj",
  fn: fn
}
​
var bar = obj.fn;
bar();

此时this指向的是window,这是为什么呢?因为fn最终是被bar调用的,而bar没有绑定任何的对象,也就没有形成隐式绑定,相当于是一种默认绑定

3. 显式绑定

明确的表示绑定了this指向的对象,我们称之为显示绑定

3.1 call、apply和bind

通过call、apply和bind显式绑定this对象后,此时this指向绑定的对象

  • 特殊情况:当传入参数为nullundefined时,this指向为全局对象(window)
function fn() {
  console.log(this);
}
​
var bar = fn.bind('haha');
​
fn.call(window); // window
fn.call(123); // 123
fn.call(null); // window
fn.call(undefined); // window
bar() // 'haha'

3.2 内置函数

有时候我们要调用一些JS的内置函数,或者第三方库中的内置函数,而这些函数中的this又是如何绑定的呢?

  • setTimeout

    setTimeout中会传入一个函数,这个函数中的this通常绑定的是全局对象(window)

    setTimeout(function() {
      console.log(this); // window
    }, 3000);
    
  • 数组的forEach

    在默认情况下传入的函数是自动调用函数(默认绑定),所以此时传入函数this指向也是全局对象(window)

    var arr = ["aa", "bb", "cc"];
    arr.forEach(function(item) {
      console.log(this); // window
    });
    

    如果我们想要改变forEach指向可不可以呢?答案是可以的,其实forEach可以传入第二个参数来指定this的指向

    var arr = ["aa", "bb", "cc"];
    arr.forEach(function(item) {
      console.log(this); // 666
    }, 666);
    
  • div的点击

    假设我们有一个id为box的div元素,js代码如下

    var box = document.querySelector("#box");
    box.onclick = function() {
      console.log(this); // box对象
    }
    

    此时this的指向为box对象,这是因为在发生点击时,传入的回调函数被调用时,会将box对象绑定到该函数中

4. new绑定

在JS中,可以使用new关键字来调用函数时,来创建一个全新的对象,此时this指向为创建的新对象

function Person(name) {
  this.name = name;
  return this; // Person {name: 'hh'}
}
​
var hh = new Person("hh");
console.log(hh);

了解了这么多绑定规则,那么如果某个函数调用出现多条规则时该怎样判断应用哪条规则呢?这时候就要知道这几条规则间的优先级是怎样的了

在这四条规则中优先级为:new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

大家感兴趣的话可以举些简单的例子验证一下

另外说明:this的四种标准规则不适用于箭头函数(也就是不绑定this),而是根据外层作用域来决定this。