JavaScript处理this指向的几种方式


前言

this 指向,即函数执行上下文,是我们在 JS 编程中无法避免的问题。一方面,更改 this 指向能达到我们的一些目的,例如,在类数组对象上使用数组方法:

const reduce = Array.prototype.reduce;
function sumArgs() {
  return reduce.call(arguments, (sum, value) => {
    return (sum += value);
  });
}
console.log(sumArgs(1, 2, 3)); // => 6

但是另一方面,this 对象很难把握,经常会造成 this 指向不正确的问题。所以,今天整理一下,将 this 绑定到所需的值的一些方法。
首先,我们先定义一个辅助函数execute(fn),它仅作为参数提供的函数。

function execute(fn) {
  return fn();
}
execute(function() {
  console.log("hello,world!");
}); // => hello,world!

方法分离

当利用构造函数创建一个实例时,在 new 的时候。构造函数内部的 this 表示的是新创建的实例。

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

  // 返回全名
  this.getFullName = function() {
    this === beauty; // => true
    return `${this.firstName} ${this.lastName}`;
  };
}

const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"

上述代码看起来好像符合了我们的预期,但实际上真是这样吗?我们尝试一下用之前定义的 execute 函数来执行一下试试看:

console.log(execute(beauty.getFullName)); // => "undefined" "undefined"

我们可以看到用 execute 辅助函数的方法却不能实现我们的目标,这是为什么呢?
这就是因为 this 指向不正确的原因。如果是采用beauty.getFullName的方式,this 的指向是beauty对象,所以可以获取到全民,但是包裹了一层execute之后,getFullName的方法中this指向的是全局对象(浏览器环境中的 window )。this 等于 window${window.firstName} ${window.lastName} 执行结果是 undefined undefined。发生这种情况是因为在调用 execute(beauty.getFullName)时该方法与对象分离。当方法被分离,然后执行的时候,this 与原始对象没有连接。所以上述代码等价于:

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

方法分离问题,以及由此导致 this 指向不正确,一般出现几种情况如下:

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

所以,为了保证函数内部的 this 指向正确的对象,我们可以:

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

保证 this 指向正确对象的方法总结

  1. 关闭上下文

    这个方法是使用一个额外的变量来保存 this 指向,这是保证 this 指向正确的一个最简单的方法。
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  const self = this;
  // 返回全名
  this.getFullName = function() {
    self === beauty; // => true
    return `${self.firstName} ${self.lastName}`;
  };
}

const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
  1. 使用箭头函数

    有没有办法在没有附加变量的情况下静态绑定 this? 当然可以,这就可以利用箭头函数实现。箭头函数是 ES6 中出现的新内容,了解的朋友都知道,箭头函数本身没有 this 指向,他的他 this 来自其定义的外部函数 this 的值。所以箭头函数是以词法方式绑定 this。这种方法简单又方便,所以建议在需要使用外部函数上下文的所有情况下都使用箭头函数。上述例子可以用箭头函数改写:
function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  // 返回全名
  this.getFullName = () => `${this.firstName} ${this.lastName}`;
}
const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
  1. 绑定上下文

    在类的情况下,使用附加的变量 self 或箭头函数来修复 this 的指向是行不通的。这时候就需要用到 bind,bind()等方法的作用就是将 this 对象绑定到指定对象上,所以利用 bind()对 this 指向进行手动绑定可以达到我们的目的。
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

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

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

const beauty = new Person("Cassiel", "Lee");

console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
  1. 使用胖箭头

    胖箭头的方式是 bind()方法的简化版,将类中的方法绑定到类实例,是在类中绑定 this 的最有效和最简洁的方法。
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

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

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

const beauty = new Person("Cassiel", "Lee");

console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"

参考

结语

到这里,更改 this 指向的方法就梳理完毕了,this 指向是 JS 知识中非常重要的一部分,很多人觉得他很复杂,但是搞清楚之后其实没有我们想象的那么复杂。感谢那些发布帖子的前辈们,让我可以学习到这么多知识,前端的学习之路道阻且长,我也会继续努力的!


文章作者: CassielLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CassielLee !
评论
  目录