ES6 方法得到一个 null "this" 并且 class 变量不可访问

ES6 methods get a null "this" and class variables are inaccessible

我正在使用 ES6 class 将 Node 中的一些功能捆绑在一起。这是(基本上)它的样子:

class processDocs {
  constructor(id) {
    this.id = id;
    // console.log(this) returns { id: id }
  }

  getDocs(cb) {
    // console.log(this) returns null
    docs
      .query(qb => {
         qb.where('id', this.id);
      })
      .fetch()
      .then(function(documents) {
        cb(null, documents);
      })
    ;
  }

  alterDocs(documents, cb) {
    //some logic
  }

  reindexSearch(cb) {
    //some logic
  }

  process() {
    // console.log(this) returns { id: id }
    async.waterfall([
      this.getDocs,
      this.alterDocs,
      this.reindexSearch
    ]);
  }
}


export default processDocs;

我认为在 ES6 classes 中,分配 public 变量的方式是简单地引用 this 并且通过构造函数初始化这些变量的方式正是它的方式出现在我的 class 定义中。

以下是我如何调用 class(在单独的文件中):

var Processor = require('./processDocs');

var pr = new Processor(id);
var docs;
pr.process();

这是问题所在,当我从构造函数中 console.log 输出 this 时,我得到了预测的 { id: id } 值;但是,当 process 为 运行ning 时,每当我在 getDocs 中注销 this 时,它为空。但是,当我在瀑布前 process() 注销 this 时,我得到了我原来的对象。

这有什么原因吗?

顺便说一句,我正在使用 node: v0.10.33 和 babel-node 4.6.6 并且我 运行 带有 --harmony 标志的 babel-node。在有人问之前,我无法更新到较新的 Node 版本,因为主要依赖项停留在 v0.10.x.

EDIT 我能够创建一个解决方法,但它不是很像 es6。问题似乎与 async.waterfall 有关。我不得不使用 .bind 来修复它:

    async.waterfall([
      this.getDocs.bind(this),
      this.alterDocs.bind(this),
      this.reindexSearch.bind(this)
    ]);

我自己创建了以下函数。

let bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; //from coffeescript's => operator

//use in a class's constructor to make the this pointer always refer to the object in which a function resides
function fixThisPointer(_this, func){
  _this[func.name] = bind(_this[func.name], _this);
}

function fixThisPointer2(_this, funcNameArray){
  for (name of funcNameArray){
    _this[name] = bind(_this[name], _this);
  }
}

然后,当我需要它时,我在我的构造函数中使用这个命令

fixThisPointer(this, foo)

或者这个命令

fixThisPointer2(this, ['foo', 'foo2', 'foo3'])

您可以在 class 中使用箭头函数,因为它们会自动绑定它。您可以将 class 方法编写为:

 getDocs = (cb) => {
    // console.log(this) will not returns null
    docs
      .query(qb => {
         qb.where('id', this.id);
      })
      .fetch()
      .then(function(documents) {
        cb(null, documents);
      })
    ;
 }

参见this MDN article:"Arrow functions capture the this value of the enclosing context"

考虑将 process() 的正文更新为:

process() {
  // console.log(this) returns { id: id }
  async.waterfall([
    (cb)=>{this.getDocs(cb);},
    (documents,cb)=>{this.alterDocs(documents,cb);},
    (cb)=>{this.reindexSearch(cb);}
  ]);
}

使用箭头函数确保 class 的成员函数在正确的上下文中被调用。

ES6 没有改变 "this" 的工作方式,因此它取决于 "where you call it" 的上下文而不是 "always have same" 值。这很不直观,在其他语言中也不常见。

考虑这个例子

class processDocs {
  constructor(id) {
    this.id = id;
    console.log(this)
  }

  getDocs(cb) {
    console.log(this)
  }

  alterDocs(documents, cb) {
    //some logic
  }

  reindexSearch(cb) {
    //some logic
  }

  process() {
    console.log(this)
  }
}

var process = new processDocs(10);
var docs = process.getDocs(function(){});
var processInstance = process.process();

var docsAsFunction = process.getDocs;
docsAsFunction(function(){});

输出是

processDocs {id: 10}
processDocs {id: 10}
processDocs {id: 10}
undefined

如你所见,最后一个是undefines,它是调用"docsAsFunction",因为你没有直接从它的class调用那个函数,所以context是不同的

你可以阅读它,例如 here