JavaScript中5种搞定this指向方式

 

学习JavaScript的都知道,在JavaScript中最灵活百变的就是this关键字,有时代表的是window对象,有时代表的有时函数本身,所以,这个this到底是指向哪里的呢?怎么区分呢?

在开始之前,我需要一个辅助函数execute(func),它仅执行作为参数提供的函数。

function execute(func) {
  return func();
}

execute(function() { return 10 }); // => 10

一.方法分离问题

假设有一个类Person包含字段firstName和lastName。此外,它还有一个方法getFullName(),该方法返回此人的全名。如下所示:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  this.getFullName = function() {
    this === agent; // => true
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person('小红', 'muimui');
agent.getFullName(); // => '小红', 'muimui'

可以看到Person函数作为构造函数被调用:new Person(‘前端’, ‘小智’)。 函数内部的 this 表示新创建的实例。

getfullname()返回此人的全名:’小红’, ‘muimui’。正如预期的那样,getFullName()方法内的 this 等于agent

如果辅助函数执行agent.getFullName方法会发生什么:

execute(agent.getFullName); // => 'undefined undefined'

执行结果不正确:'undefined undefined',这是 this 指向不正确导致的问题。 现在在getFullName() 方法中,this的值是全局对象(浏览器环境中的 window )。 this 等于 window${window.firstName} ${window.lastName} 执行结果是 'undefined undefined'

发生这种情况是因为在调用execute(agent.getFullName)时该方法与对象分离。 基本上发生的只是常规函数调用(不是方法调用):

execute(agent.getFullName); // => 'undefined undefined'
// 等价于:

const getFullNameSeparated = agent.getFullName;
execute(getFullNameSeparated); // => 'undefined undefined'

这个就是所谓的方法从它的对象中分离出来,当方法被分离,然后执行时,this 与原始对象没有连接。

为了确保方法内部的this指向正确的对象,必须这样做 以属性访问器的形式执行方法:agent.getFullName() 或者静态地将this绑定到包含的对象(使用箭头函数、.bind()方法等) 方法分离问题,以及由此导致this指向不正确,一般会在下面的几种情况中出现: 回调

// methodHandler()中的this是全局对象
setTimeout(object.handlerMethod, 1000);

// 在设置事件处理程序时
// React: methodHandler()中的this是全局对象
<button onClick={object.handlerMethod}>
  Click me
</button>



二. 关闭上下文

保持this指向类实例的最简单方法是使用一个额外的变量self:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  const self = this;

  this.getFullName = function() {
    self === agent; // => true
    return `${self.firstName} ${self.lastName}`;
  }
}

const agent = new Person('xiaohong', 'xxx');

agent.getFullName();        // => 'xiaohong', 'xxx'
execute(agent.getFullName); // => 'xiaohong', 'xxx'

getFullName()静态地关闭self变量,有效地对this进行手动绑定。 现在,当调用execute(agent.getFullName)时,一切工作正常,因为getFullName()方法内 this 总是指向正确的值。

三.使用箭头函数

有没有办法在没有附加变量的情况下静态绑定this? 是的,这正是箭头函数的作用。 使用箭头函数重构Person:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  this.getFullName = () => `${this.firstName} ${this.lastName}`;
}

const agent = new Person('xiaohong', 'xxx');

agent.getFullName();        // => 'xiaohong', 'xxx'
execute(agent.getFullName); // => 'xiaohong', 'xxx'

箭头函数以词法方式绑定this。 简单来说,它使用来自其定义的外部函数this的值。 建议在需要使用外部函数上下文的所有情况下都使用箭头函数。

四. 绑定上下文

现在让咱们更进一步,使用ES6中的类重构Person。

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person( 'xiaohong', 'xxx');

agent.getFullName();        // => 'xiaohong', 'xxx'
execute(agent.getFullName); // => 'undefined undefined'

不幸的是,即使使用新的类语法,execute(agent.getFullName)仍然返回“undefined undefined”。 在类的情况下,使用附加的变量self或箭头函数来修复this的指向是行不通的。 但是有一个涉及bind()方法的技巧,它将方法的上下文绑定到构造函数中:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

    this.getFullName = this.getFullName.bind(this);
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person( 'xiaohong', 'xxx');

agent.getFullName();        // =>  'xiaohong', 'xxx'
execute(agent.getFullName); // =>  'xiaohong', 'xxx'

构造函数中的this.getFullName = this.getFullName.bind(this)将方法getFullName()绑定到类实例。 execute(agent.getFullName) 按预期工作,返回 ‘xiaohong’, ‘xxx’。

五、箭头方法

bind 方式有点太过冗长,咱们可以使用胖箭头的方式:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName = () => {
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person( 'xiaohong', 'xxx');

agent.getFullName();        // =>  'xiaohong', 'xxx'
execute(agent.getFullName); // =>  'xiaohong', 'xxx'

胖箭头方法getFullName =() =>{…}绑定到类实例,即使将方法与其对象分离。 这种方法是在类中绑定this的最有效和最简洁的方法。

六、总结

在类中,可以使用bind()方法手动绑定构造函数中的类方法。当然如果你不用使用 bind 这种冗长方式,也可以使用简洁方便的胖箭头表示方法。

推荐阅读:

本文为博主原创文章,知识共享,开源精神,转载注明出处。