【JS基础】如何正确判断 this?

2,203 阅读5分钟

前言

this是很多初学者容易混淆的概念,其实它并不难,在这篇文章中,你将会弄清this关键字指的是什么;同时,你还将学习到.call,.apply,.bindnew以及箭头函数等知识。

this是什么

thisJavaScript中很特别的一个关键字,被自动定义在所有函数作用域中,但是它即不指向函数自身也不指向函数的词法作用域,它指向的是调用函数的对象。

this到底指向哪里

要判断this指向哪里,我们必须理解每个函数的this并不是在声明时就被绑定的,而是在调用时被绑定。
正如上文所说this指向的是调用函数的对象,所以要判断this到底指向哪里,首先我们要知道 “是哪个对象调用了函数?”

举个例子来帮助同学们理解这一点:

例子1:

function person(name) {
  console.log(name);
}

可以看到例子里声明了一个person函数,接收一个name参数,想知道name会打印出什么,必须得看person函数调用过程中传入参数是什么。同样的道理想判断this指向哪里,就得看函数调用方式是什么。

根据函数调用方式不同,我将其分为下面👇几种情况:

1、首先就是最常用的函数调用方法:函数名直接调用

例2:

function person() {
  console.log(this);
}

function personStrict() {
  'use strict'
  console.log(this);
}

person(); //  window

personStrict(); // undefined

可以看到,函数被直接调用时,就相当于全局对象调用的;这样它的 this就指向全局对象:window

PS:但是在严格模式下,this不会指向全局对象,而是指向undefined,这是为了尽量减少不严谨的出错行为,你只需要在你所写代码作用域的最顶端加上use strict;建议将其放在一个立即执行函数中,避免污染全局。

例3:

(function(){
  //'use strict'
  function getDoc() {
    console.log(this.document);
  }
  getDoc();
})();


可以看到,如果程序在非严格模式下运行,不会有错误抛出,那是因为在全局对象window中存在一个名为document的属性,而在严格模式下,由于此时this指向undefined,于是抛出一个错误。

2、作为对象的方法调用

例4:

let name = 'window';
let person = {
  name : 'Heternally',
  getName : function(){
    console.log(this.name);
  }
}
person.getName(); // 'Heternally'
let name = 'window';
let person = {
  name : 'Heternally',
  getName
}
function getName(){
  console.log(this.name)
}
person.getName(); // 'Heternally'

可以看到在上面👆例子中,getName函数是对象person调用的,所以打印出来的值是personname的值

例5:

let name = 'window';
let person = {
  name : 'Heternally',
  getName : function () {
    console.log(this.name);
  }
}

let getName = person.getName;
getName(); // 'window'

例5对例4做了一点点小改动,可以看到打印出的结果就是window了,这是因为getName函数最终还是被window调用,所以这个this指向的是window

这又应了上文的话,this的指向不能在声明的时候确定,而是取决于谁调用了它

例6:

let person1 = {
  name : 'person1',
  getThis: function () {
    console.log(this);
  }
}

let person2 = {
  name : 'person2',
  getThis: function () {
    return person1.getThis();
  }
}

let person3 = {
  name : 'person3',
  getThis: function () {
    var getThis3 = person1.getThis;
    return getThis3();
  }
}

person1.getThis(); // person1
person2.getThis(); // person1
person3.getThis(); // window

看到这可能有同学会问,不是说this的指向取决于谁调用了它吗,那为什么person3.getThis()打印出来的是window呢,因为在person3.getThis里,调用this的函数是getThis3,所以此时this指向了window

由上面👆几个例子可以得出,this指向最后调用它的那个对象。

常用改变this指向方法

1、call、apply、bind

这三个函数的作用都是改变函数执行时的上下文,即改变函数运行时的this指向。有了这个人生,接下来我们通过几个例子来学习如何使用这三个函数。
简单说一下三个方法的用法:

1.1 call
fun.call(thisArg[, arg1[, arg2[, ...]]])

它会立即执行函数,第一个参数时指定执行函数中this的上下文,如果不传或者传nullundefined,则表示this指向window,后面的参数是执行函数所需的参数;

1.2 apply
fun.apply(thisArg[, [arg1, arg2, ...]])

它会立即执行函数,第一个参数时指定执行函数中this的上下文,如果不传或者传nullundefined,则表示this指向window,第二个参数接收一个数组(这是与call唯一的区别);

1.3 bind
var foo = fun.bind(thisArg[, arg1[, arg2[, ...]]]);

它不会立即执行函数,而是返回一个新的函数,这个新的函数被指定了this上下文,接收的参数与call一致;

例7:

let person = {
  name : 'Heternally'
}
var name = 'window';
function getName() {
  console.log(`Hello, my name is ${this.name}`);
}
getName(); // 'Hello, my name is window'
getName.call(person); // 'Hello, my name is Heternally'
getName.apply(person); // 'Hello, my name is Heternally'
getName.bind(person)(); // 'Hello, my name is Heternally'

可以看到,使用callapplybind方法后,可以动态改变函数执行上下文的this指向

2、new关键字

每当使用new关键字调用函数时,会自动把this绑定在新对象上,然后再调用这个函数

例8:

function Person(name) {
  this.name = name;
  console.log(this);
}
var people = Person('Heternally'); // window
var people1 = new Person('Heternally'); // Person {name: "Heternally"}
people.name; //Cannot read property 'name' of undefined
people1.name; // 'Heternally'

3、ES6箭头函数

ES6中,新加入了箭头函数,它和普通函数最不同的一点就是,对于箭头函数的this指向,只需要看它是在哪里创建即可

例9:

var person = {
  name : 'Heternally',
  getName1 : function () {
    setTimeout(function(){
      console.log(this);
    },1000)
  },
  getName2 : function () {
    setTimeout(()=>{
      console.log(this);
    },1000)
  }
}
person.getName1(); //window
person.getName2(); // {name:'Heternally',getName1:f,getName2:f}

可以看到,在getName2方法的setTimeout函数中,没有跟getName1setTimeout函数一样打印window,而是打印出person对象。
简单说:箭头函数中的this只和定义它时作用域有关,并不受在哪里及如何调用的影响;

优先级

了解了this指向及多种改变this指向的方法后,我们还需要了解这多种方法的优先级,

函数直接调用  < 对象方法调用 < call/apply < bind < new < 箭头函数

总结

要判断this指向,需要找到是这个函数的直接调用位置,找到之后可以依据如下方法进行判断this的指向:

1、 是否由箭头函数调用 ->指向箭头函数外层非箭头函数的this指向 ;

2、 是否由new调用 ->指向新创建的对象 ;

3、 是否由call || apply || bind 调用 -> 指向指定的对象 ;

4、 是否由对象调用 -> 指向这个对象 ;

5、 上面几种情况都不是:严格模式下指向undefined,非严格模式下指向全局对象