简体   繁体   中英

Explain use of 'this' inside a function that calls an object method in Javascript ES6

I'm trying to process some json and write pieces of data to a few files.

So I have a function to create folder/files based on the json data and some literals. The filenames are defined by an object containing different categories:

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

This is where half of you will jump ahead and tell me that arrow functions can't see this of their own objects etc. I know that and that's intentional, since I'll be getting the necessary data later on.

The main function follows this pattern:

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'

I also know that I could just pass fileName and uniqueId as args but that's not the point. This post is not exactly an attempt to find a solution but to understand why it's not working.

The confusion here is the use of this inside readSave . According to MDN this inside a regular function is the same object where the function is called. Since I'm calling it on a plain js file on node, it's global .

All good for now. If I inspect the run inside the function call, this is global and the properties are set without problems.

The problem is folders.category1.subpath() evaluates to undefined .

The debugger shows when it goes to evaluate () => /subpath/${this.fileName}${this.uniqueId} , this is not global anymore, instead it's an empty Object. This doc makes me think that the arrow function should inherit the this of the scope it's being called, which is readSave , which means this should be global .

To increase my confusion, setting the properties outside the function works flawlessly:

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

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

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

Inspecting the code above, everything is pretty much the same up until when it goes to evaluate () => /subpath/${this.fileName}${this.uniqueId} . Now the previously empty Object has two properties as they were set.

Finally, this also works exactly the same as the previous example:

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

I've browsed and read about this for hours but am still really confused as to why some methods work and some don't.

Thanks in advance

Update : Turns out I made some wrong assumptions on what Node's root object is in a given file or function call. Wasn't really aware of the module wrapper.

This doc makes me think that the arrow function should inherit the this of the scope it's being called, which is readSave, which means this should be global.

No the this of the arrow function is determined at the time the arrow function is created:

[...] No matter what, foo's this is set to what it was when it was created (in the example above, the global object). The same applies to arrow functions created inside other functions : their this remains that of the enclosing lexical context [...].

So this in the arrow function refers to what this is here:

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

And the this for those two code block refer to the same object for this exact same reason:

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() 

On load the content of a node file is wrapped into: ( The module wrapper )

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

That function is then called passing the corresponding values as arguments and that function is called on the object passed as exports to the function What is the root object in Node.js .

So for /subpath/${this.fileName}${this.uniqueId} the this refers to exports the same is with readSave3 and your last code. For your readSave (the first one) the this refers to the global object.

So for node your code would look like 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)

this always point the current context. when the arrow function going to execute this change to its current context which is in your case is the arrow function. so the this inside the arrow function and outside of the arrow functions are not same they are in different context this

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM