在 JavaScript 中轻松处理 “this”

做者:Dmitri Pavlutin

翻译:疯狂的技术宅javascript

原文:https://dmitripavlutin.com/fi...前端

未经容许严禁转载java

我喜欢 JavaScript 中可以更改函数执行上下文(也称为 this)的特性。git

例如,你能够在相似数组的对象上使用数组方法:程序员

const reduce = Array.prototype.reduce;

function sumArgs() {
  return reduce.call(arguments, (sum, value) => {
    return sum += value;
  });
}

sumArgs(1, 2, 3); // => 6

可是从另外一方面来讲,this 关键字很难掌握。github

你可能会常常去检查 this 的值不正确的缘由。如下各节将会教给你一些把 this绑定到所需的值简单的方法。面试

在开始以前,我须要一个辅助函数 execute(func)。它只是用来执行做为参数的函数:segmentfault

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

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

如今,让咱们继续了解围绕 this 的错误的本质:方法分离。数组

1. 方法分离问题

Person 类包含字段 firstNamelastName。另外,它还有 getFullName()方法,返回全名。浏览器

Person 的一种可能的实现方式是:

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('John', 'Smith');
agent.getFullName(); // => 'John Smith'

你会看到 Person 函数做为构造函数被调用:new Person('John','Smith')。在 Person 函数内部建立新的实例。

agent.getFullName() 返回 person 的全名:'John Smith'。不出所料,getFullName() 方法中的 this 等同于 agent

若是帮助函数执行 help.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'

// is equivalent to:

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

这种效果就是我所说的与对象分离的方法。当方法被分离并随后执行时,它与其原始对象没有任何关系。

为了确保方法中的 this 指向正确的对象,你必须:

  1. 以属性访问器的形式执行该方法:agent.getFullName()
  2. 或将 this 静态绑定到包含的对象(使用箭头函数,.bind() 方法等)

在方法分离问题中,返回的 this 不正确,如下面不一样的形式出现:

在设置回调时

// `this` inside `methodHandler()` is the global object
setTimeout(object.handlerMethod, 1000);

在设置事件处理程序时

// React: `this` inside `methodHandler()` is the global object
<button onClick={object.handlerMethod}>
  Click me
</button>

让咱们继续了解一些有用的方法,来解决即便方法与对象是分开的,也能使其始终指向所需对象的问题。

2. 关闭上下文

使 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('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

getFullName() 会静态关闭 self 变量,从而有效地手动绑定到 this

如今,当调用 execute(agent.getFullName) 时返回 'John Smith',由于 getFullName()方法始终具备正确的 this 值,因此可以正常工做。

3. 使用箭头功能对 this 进行语义化

有没有一种能够在没有其余变量的状况下静态绑定 this 的方法?是的,这正是箭头函数的做用。

为了使用箭头函数,让咱们重构 Person

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

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

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

箭头函数用词法绑定 this。简而言之,它使用定义在其中的外部函数的 this 值。

我建议在全部须要使用外部函数上下文的状况下都使用箭头函数。

4.绑定上下文

让咱们再向前迈出一步,并使用 ES2015 类来重构 Person

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

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

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
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('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

构造函数中的 this.getFullName = this.getFullName.bind(this)getFullName() 方法绑定到类实例。

execute(agent.getFullName) 能够正常工做,返回 'John Smith'

5. 胖箭头方法

上述使用手动上下文绑定的方法须要样板代码。幸运的是,仍有改进的空间。

你能够用 JavaScript 的类字段建议来定义胖箭头方法:

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

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

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

胖箭头函数 getFullName = () => { ... } 已绑定到类实例,即便你将方法与其对象分离开也是如此。

这是在类中绑定 this 的最有效,最简洁的方法。

六. 结论

与对象分离的方法对 this 产生了许多误解。你应该意识到这种影响。

要静态绑定 this,你能够手动使用一个附加变量 self 来保存正确的上下文对象。可是更好的选择是使用箭头函数,它天生被设计为按词法绑定 this

在类中,你可使用 bind() 方法在构造函数内部手动绑定类方法。

若是你想跳过编写样板代码,那么新的 JavaScript 建议类字段会带来胖箭头方法,该方法会自动将 this 绑定到类实例。


本文首发微信公众号:前端先锋

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

欢迎继续阅读本专栏其它高赞文章:


相关文章
相关标签/搜索