首页 > 解决方案 > 解释在 Javascript ES6 中调用对象方法的函数中“this”的使用

问题描述

我正在尝试处理一些 json 并将数据片段写入几个文件。

所以我有一个基于 json 数据和一些文字创建文件夹/文件的功能。文件名由包含不同类别的对象定义:

const folders = {
    category1: {
        fileName: 'default',
        path : '/path',
        subpath : () => `/subpath/${this.fileName}${this.uniqueId}`
    }
}

这就是你们中的一半人会跳到前面告诉我箭头函数看不到this它们自己的对象等的地方。我知道这是故意的,因为我稍后会得到必要的数据。

main 函数遵循以下模式:

function readSave() {
    //suppose readFileSync would return a string 'filename' and an int 1029

    this.fileName = 'filename'; 
    this.uniqueId = 1029;

    let filePath = folders.category1.path + folders.category1.subpath();

    // I'd go on to write stuff to that file but for now let's just return
    return filePath;
}

readSave() 
// returns '/path/subpath/undefinedundefined'
// expected '/path/subpath/filename1029'

我也知道我可以作为参数传递fileNameuniqueId但这不是重点。这篇文章并不完全是试图找到解决方案,而是要了解它为什么不起作用。

这里的混乱是thisinside的使用readSave。根据MDN this内部,常规函数与调用函数的对象相同。由于我在节点上的普通 js 文件上调用它,所以它是global.

目前一切都很好。如果我检查函数调用中的运行,this并且global属性设置没有问题。

问题是 folders.category1.subpath()评估为undefined

调试器显示何时进行评估() => /subpath/${this.fileName}${this.uniqueId}this不再是global,而是一个空对象。这个文档让我认为箭头函数应该继承this它被调用的范围,即readSave,这意味着this应该是global

为了增加我的困惑,在函数之外设置属性可以完美地工作:

function readSave2() {
   let filePath = folders.category1.path + folders.category1.subpath();
   return filePath;
}

this.fileName = 'filename'; 
this.uniqueId = 1029;

readSave() 
// returns '/path/subpath/filename1029'

检查上面的代码,在进行评估之前,一切都几乎相同() => /subpath/${this.fileName}${this.uniqueId}。现在,先前为空的 Object 在设置时具有两个属性。

最后,这也与前面的示例完全相同:

const readSave3 = () => {
    this.fileName = 'filename'; 
    this.uniqueId = 1029;
    let filePath = folders.category1.path + folders.category1.subpath();
    return filePath;
}

我已经浏览并阅读了几个小时,但仍然对为什么有些方法有效而有些方法无效感到非常困惑。

提前致谢

更新:原来我对给定文件或函数调用中节点的根对象做了一些错误的假设。并不真正了解模块包装器。

标签: javascriptnode.jsecmascript-6

解决方案


这个文档让我认为箭头函数应该继承它被调用的范围的this,即readSave,这意味着它应该是全局的。

箭头函数的nothis是在创建箭头函数时确定的:

[...] 无论如何, foo'sthis设置为它创建时的状态(在上面的示例中,全局对象)。这同样适用于在其他函数内部创建的箭头函数:它们this 仍然是封闭的词汇上下文[...]。

所以这里this的箭头函数指的是什么this

console.dir(this) // <----  refers to `exports`
const folders = {
    category1: {
        fileName: 'default'
        path : '/path',
        subpath : () => `/subpath/${this.fileName}${this.uniqueId}`
    }
}

this出于同样的原因,这两个代码块引用了同一个对象:

console.dir(this) // the `this` in the arrow function below is the same as here
                  // and `this` refers to `exports`
const readSave3 = () => {
    this.fileName = 'filename'; 
    this.uniqueId = 1029;

    // ...
}

readSave3()
function readSave() {
   // ...
}

this.fileName = 'filename'; 
this.uniqueId = 1029;

readSave() 

加载时节点文件的内容被包装到:(模块包装器

(function(exports, require, module, __filename, __dirname) {
   /*... filecontent ... */
})

然后调用该函数,将相应的值作为参数传递,并在传递给函数What is the root object in Node.js的对象调用该函数。exports

所以对于/subpath/${this.fileName}${this.uniqueId}thisexportsreadSave3和你最后的代码一样。对于您的readSave(第一个),this指的是全局对象。

因此,对于节点,您的代码将如下所示:

var moduleSetup = function(exports, require, module, __filename, __dirname) {
  // here `this` is exports 

  const folders = {
    category1: {
      fileName: 'default',
      path: '/path',
      // `this`referes to exports due to arrow function
      subpath: () => `/subpath/${this.fileName}${this.uniqueId}`
    }
  }


  function readSave1() {
    // here `this` refers to `global` because `readSave1` is not called on an object

    //suppose readFileSync would return a string 'filename' and an int 1029

    this.fileName = 'filename';
    this.uniqueId = 1029;

    let filePath = folders.category1.path + folders.category1.subpath();

    // I'd go on to write stuff to that file but for now let's just return
    return filePath;
  }

  readSave1()


  function readSave2() {
    let filePath = folders.category1.path + folders.category1.subpath();
    return filePath;
  }

  // `this` refers to `exports`
  this.fileName = 'filename';
  this.uniqueId = 1029;

  readSave2()


  const readSave3 = () => {
    // `this` refers to `exports` due to arrow function
    this.fileName = 'filename';
    this.uniqueId = 1029;

    let filePath = folders.category1.path + folders.category1.subpath();
    return filePath;
  }
  readSave3()
}

// and that's roughly how node would invoce that function:
var module = {
  exports: {}
}

moduleSetup.call(module.exports, // moduleSetup called on module.exports
                 // with these arguments:
                 module.exports, require, module, theFileName, theDirname)

推荐阅读