2

I have code like this:

mycode.js:

export default {
  foo() {
    return "foo";
  },
  bar() {
    return "bar" + this.foo();
  }
}

othercode.js:

import mycode from "mycode.js";

mycode.bar();  // usually works fine, returns "barfoo"

I'm having difficulty, though, calling mycode.bar() inside an arrow function. The problem is that "this" within bar() no longer refers to the object in mycode, it refers to the caller's context.

If I want to continue to use this code style for defining functions and exporting them, how do I call foo() within bar()?

I have tried to bind() the methods in mycode to the exported object, but so far as I can tell, you have to do that for each method individually.

I also tried to do this at the top of mycode:

let self = this;

and then refer to "self.foo()", but "this" isn't defined at the time I assign it to self.

I could totally change the code style. I could define each function like this:

mycode.js

function foo() {
  return "foo";
}

export default {
  foo, bar, ...
}

but then I have to name each function individually.

There's got to be a better way.

ccleve
  • 15,239
  • 27
  • 91
  • 157
  • 1
    What's wrong with naming things properly? The JIT parser certainly isn't going to judge you for it, so if it makes your code easier to read and work with: isn't that literally what we all need out of our code? – Mike 'Pomax' Kamermans Feb 21 '20 at 00:14

3 Answers3

1

You could express or declare your functions, use them internally within the file and than decide which ones to make public (exports).

Named import

since by using wildcards you can get a larger payload and adds an unneeded layer of secrecy, you should preferably always name the desired methods you want to import.

By using a { named } list of imports there's no more an as <alias> pointer, making the this result in undefined

PS: get rid of the default when exporting properties

// foo.mjs

function foo() {
  return "foo";
}

function bar() {
  console.log(this);    // undefined 
  return "bar" + foo();
}

export {
  foo,
  bar
}

using expressive named imports:

// index.mjs

import { bar } from './foo.mjs';

console.log(bar()); // "barfoo"

Run $ node --experimental-modules ./index.mjs and you'll see in log "barfoo"


Wildcard import * as alias

Careful: a wildcard import will refer the this to the constant as alias importing the [Module]

// foo.mjs

function foo() {
  return "foo";
}

function bar() {
  console.log(this) // [Module] { bar: [Function: bar], foo: [Function: foo] }
  return "bar" + this.foo(); // `this` points to the importing [Module] alias
  // ANTIPATTERN, and can result in undefined if imported as a named list
}

export {
  foo,
  bar
}
// index.mjs

import * as test from './foo.mjs';

console.log(test.bar()); // "barfoo"

Brittle, error-prone since if used a named import like import { bar } from "./foo.mjs"; console.log(bar()); can result in

TypeError: Cannot read property 'foo' of undefined

and should be avoided.

Links:

https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import

https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/
https://github.com/airbnb/javascript/issues/710

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • Thank you. That is the final thing I tried, but I was asking if there was a way I could keep the same code style, that is, export default { foo() {}, ... } – ccleve Feb 21 '20 at 00:19
  • I just rewrote my code per your suggestion. It does work. It also has the advantage that the code looks the same externally, so I don't have to modify anything in other parts of my app. – ccleve Feb 21 '20 at 00:35
  • @ccleve you have some interesting ways (from answers), but here's the beauty in keeping it clean, and simple. `foo()` is a function (or method if you want) within your file scope. Mentally you can treat it just like `this.foo()` but without the `this` :) – Roko C. Buljan Feb 21 '20 at 00:40
1

If you use a named export instead of a default export, you could refer to the name of the object you're exporting, eg:

export const myMethods = {
  foo() {
    return "foo";
  },
  bar: () => {
    return "bar" + myMethods.foo();
  }
};

Make sure to change the imports to named imports too:

import { myMethods } from 'mycode.js';

You can also import the current module (thanks Bergi), though it looks extremely strange:

import myMethods from './mycode.js'; // <--- THE CURRENT FILE
export default {
  foo() {
    return "foo";
  },
  bar: () => {
    return "bar" + myMethods.foo();
  }
}

One of the bigger problems with default exports is that there's no way to refer to what you're exporting, unless you import the current module, like above. If you're using a default export and the function is an arrow function, this will be inherited from the outer scope of the module, so the object you're exporting can't be referenced without a strange hack like above.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
1

The problem isn't in how you export; it's in how this works in JavaScript. If you reference a function outside the context of being a method, the body of the method won't have a consistent this unless you explicitly bind it, and you won't be able to bind anything unless there's an identifier to bind to. You could do this, for example:

const mod = {
  foo() {
    return "foo";
  },
  bar() {
    return "bar" + this.foo();
  }
};

mod.foo = mod.foo.bind(mod);
mod.bar = mod.bar.bind(mod);

export default mod;

...but that hardly has the succinctness you're going for, I'm sure. If you use classes and a version of JS that supports class properties, another alternative would be:

class MyModule {
  foo: () => "foo";
  bar: () => "bar" + this.foo();
}

export default new MyModule();

Personally, though I don't know your whole situation, I think named exports make way more sense and using the module closure instead of this:

export function foo() {
  return "foo";
}

export function bar() {
  return "bar" + foo();
}
Jacob
  • 77,566
  • 24
  • 149
  • 228