this的绑定方式基本有以下几种:

  1. 隐式绑定
  2. 显式绑定
  3. new 绑定
  4. window 绑定
  5. 箭头函数绑定


### 隐式绑定

第一个也是最常见的规则称为 隐式绑定

var a = {
    str: 'hello',
    sayHi() {
        console.log(this.str)
    }
}
a.sayHi()

a 调用sayHi,所以this指向了对象a


我们来看一个类似但稍微高级点的例子。

var wrapper = {
  name: 'user1',
  sayName() {
    console.log(this.name)
  },
  inner: {
    name: 'user2',
    sayName() {
      console.log(this.name)
    }
  }
}

wrapper.sayName()			//user1
wrapper.inner.sayName()		//user2

第一个sayName,指向的是wrapper

第二个sayName,指向的是inner,因为最后调用者是inner

以上就是隐式绑定方式,隐式绑定是最为常见的,因为大部分情况下,方法的调用都会有一个调用对象。

那如果没有呢?我们来看下一条规则。


### 显式绑定

看一下下面这个例子:

function sayName () {
  console.log(this.name)
}

var user = {
  name: 'wyh'
}

这里的sayName 函数不是 user 对象的函数,而是一个独立的函数

为了判断 this 的指向,首先要查看这个函数的调用对象

但是有一个问题,我们怎样能让 sayName 方法调用的时候将 this 指向 user 对象?

这里我们不能再像之前那样,使用 user.sayName(),因为我们的 user 对象里,没有 sayName 方法。

这时候就需要请我们的call出场了。

call 是每个函数都有的一个方法,它允许你在调用函数时为函数指定上下文。

传递给它的第一个参数会作为函数被调用时的上下文。

换句话说,this 将会指向传递给 call 的第一个参数。

利用call的方式调用函数,我们可以在调用 sayName 时,手动把this指向 user 对象,如下:

function sayName () {
  console.log(this)		//Object { name: "wyh" }
  console.log(this.name)
}

var user = {
  name: 'wyh'
}
sayName.call(user)		// wyh

这样我们就通过显示绑定的方式,使它指向了user, 因为我们使用 call这种方式,指定了 this 的指向。


我们稍微修改一下上面的例子:

因为我们上面的方式只能使用user内部的参数,但是使用call还可以为我们传递其他的参数,就像下面这样:

function sayHi(hobby1, hobby2, hobby3) {
    console.log(`Hello everyone,my name is ${this.name} ,and I like ${hobby1}、 ${hobby2}、${hobby3}`)	//注意只有name属于user,需要用this
}

var user = {
    name: 'cxk'
}

var hobbies = ['sing', 'dance', 'basketball']

sayHi.call(user, hobbies[0], hobbies[1], hobbies[2])	//在user后面依次传递参数,用逗号间隔
// Hello everyone, my name is cxk,and I like sing、 dance、basketball

但是我们需要一个一个的传递 hobbies 数组的元素,有点麻烦,所以这时候需要我们的apply 出场了。

applycall 其实一样,只是传递参数行式是使用数组的方式,而且 apply 会在函数中为你自动进行展开。

那么现在使用 apply,修改一下上面的代码:

var hobbies = ['sing', 'dance', 'basketball']

// sayHi.call(user, hobbies[0], hobbies[1], hobbies[2])
sayHi.apply(user, hobbies)
// Hello everyone, my name is cxk,and I like sing、 dance、basketball


以上我们知道了 `call` 和 `apply` 的显式绑定规则,可以自己手动指定 `this` 的指向。

其实我们还有一个手动指定this的方法,它叫 bind

而且bindcall 其实也一样,只是bind不会立刻调用函数,而是返回一个能在以后调用的新函数。

我们使用 bind,修改一下上面的例子:

function sayHi(hobby1, hobby2, hobby3) {
    console.log(this);  //Object { name: "cxk" }
    console.log(`Hello everyone,my name is ${this.name} ,and I like ${hobby1}、 ${hobby2}、${hobby3}`)
}

var user = {
    name: 'cxk'
}

var hobbies = ['sing', 'dance', 'basketball']

var newFn = sayHi.bind(user, hobbies[0], hobbies[1], hobbies[2])
newFn()
// Hello everyone, my name is cxk,and I like sing、 dance、basketball

以上就是显示绑定的三种方式了,接下来我们看一下new绑定


### new 绑定

在 js 中,为了实现类,我们需要定义一些构造函数,在调用一个构造函数的时候需要加上 new 这个关键字:

function Person(name) {
    this.name = name;
    console.log(this);		// Object { name: "wyh" }
}

var p = new Person('wyh');	//使用new

当作构造函数调用时,this 指向了这个构造函数调用时候,实例化出来的对象;


当然,构造函数其实也是一个函数,如果我们把它当作一个普通函数执行,这个 `this` 仍然执行全局:

function Person(name) {
    this.name = name;
    console.log(this);		// Window
}

var p = Person('wyh');		//不使用new

其区别在于,如何调用函数(new)。


### window 绑定

还是之前的这段代码:

function sayName () {
  console.log(this.name)
}

var user = {
  name: 'wyh'
}

上面我们说过,如果想让sayName方法指向user对象,你可以使用 callapplybind

但如果我们没有用显示绑定的方法,直接调用 sayName 结果会是什么呢?

function sayHello() {
    console.log(this.str)
}

var user = {
    //注意这里不要使用name作为属性名,因为在window对象下有一个name属性
    str: 'hello'
}
sayHello()  //  undefined

我们得到了undefined。这是因为这里既没有隐式绑定,也没有显示绑定或者 new 关键字

所以this指向了window对象,但在window中我们并没有str属性,所以得到的是undefined

这意味着如果我们向 window 对象添加 str 属性并再次调用 sayHello 方法,this.str 将不再是 undefined ,而是变成window对象的 str 属性值。

window.str = 'world';

function sayHello() {
    console.log(this.str)
}
sayHello()	// world

这就是 window 绑定 。如果其它规则都没满足,JavaScript就会默认 this 指向 window 对象。

在 ES5 添加的 严格模式 中,JavaScript 不会默认 this 指向 window 对象,而会正确地把 this 保持为 undefined。

'use strict'

window.str = 'world';

function sayHello() {
    console.log(this.str)
}
sayHello()	// TypeError: this is undefined


### 箭头函数绑定

在上面的函数中,this 有各种各样的指向(隐式绑定,显示绑定,new 绑定, window 绑定......)

虽然灵活方便,但由于不能在定义函数时知道指向,直到实际调用时才能知道 this 指向

假如我们有下面这段代码

function User() {
    this.name = 'wyh';

    setTimeout(function sayName() {
        console.log(this.name); // wyh
        console.log(this); // window
    }, 1000);
}

var user = new User();

sayName 里的 this 可以由上面的四个规则判断出来。对,因为没有显示绑定、隐式绑定或 new 绑定、所以直接得出结论 this 指向 window

但实际上我们想把 this 指向 user 对象

以前是怎么解决的呢?看下面的代码:

1. 使用闭包

闭包就是指有权访问另一个函数作用域中的变量的函数。

function User() {					//这里的User函数是一个闭包
    this.name = 'wyh';
    const _this = this;

    setTimeout(function sayName() {
        console.log(_this.name);	// wyh
        console.log(_this);			// Object { name: "wyh" }
    }, 1000);
}

var user = new User();


**2. 使用显示绑定 — `bind`**

function User() {
    this.name = 'wyh';

    setTimeout(function sayName() {
        console.log(this.name);		// wyh
        console.log(this);			// User { name: "wyh" }
    }.bind(this)(), 1000);
}

var user = new User();

上面的方法都可以解决问题,但是都要额外写冗余的代码来指定 this


ES6 引入箭头函数(Arrow Function) 来解决这个问题的,它可以轻松地让 sayName 函数保持 this 指向 user 对象。

下面是箭头函数版本:

function User() {
    this.name = 'wyh';

    setTimeout(() => {
        console.log(this.name);		 // wyh
        console.log(this); 			// User { name: "wyh" }
    }, 1000);
}

var user = new User();

通过直接把普通函数改成箭头函数就能解决问题

箭头函数在自己的作用域内不绑定 this,即没有自己的 this

如果要使用 this ,就会指向定义时所在的作用域的 this 值,在上面这个例子中,箭头函数内的 this 指向定义这个箭头函数时作用域内的 this,也就是 User中的 this,而 User 函数通过 new 绑定,所以 this 实际指向 user 对象。


那在严格模式下会有影响吗?

function User() {
    this.name = 'wyh';

    setTimeout(() => {
        'use strict'
        console.log(this.name);	 	// wyh
        console.log(this);			// User { name: "wyh" }
    }, 1000);
}

var user = new User();

不会有影响,因为箭头函数没有自己的 this,它的 this 来自于 Userthis只要 Userthis 不变,箭头函数的 this 也保持不变


那么使用 `bind`,`call` 或者 `apply` 呢?

function User() {
  this.name = 'wyh';

  setTimeout((() => {
    console.log(this.name); 		 // wyh
    console.log(this); 				// User { name: "wyh" }
  }).bind('no body'), 1000);
}

var user = new User();

答案还是没有影响。因为箭头函数没有自己的 this,使用 bindcall 或者 apply 时,箭头函数会自动忽略掉 bind 的第一个参数,即 thisArg


### 总结

基本上可以通过以下几点判断this的具体指向:

  • 查看函数在哪被调用
  • 有没有对象调用?如果有,就指向最后调用的对象
  • 该函数是不是用 “call”、“apply” 或者 “bind” 调用的?如果是,它会显式地指明 “this” 的引用。
  • 该函数是不是用 “new” 调用的?如果是,“this” 指向的就是新创建的对象
  • 是否使用了闭包
  • 是否存在箭头后函数
  • 非严格模式下,如果没有其他规则,“this” 会指向 “window” 对象
  • 严格模式下,没有其他规则,“this” 指向undefined