From a code maintainability perspective, named exports are better than default exports because they reduce renaming by reference.

But named exports are not only different from default exports, they are also logically very different, and it’s important to know the differences in advance to minimize development stumbles.

This week, I found a good article in this aspect: export-default-thing-vs-thing-as-default. I will first describe the outline and then talk about my understanding.

An overview of the

Generally we think of import as importing references rather than values, that is, when the value of an imported object changes within the module, the value of the imported object should change as well.

// module.js
export let thing = 'initial';

setTimeout(() = > {
  thing = 'changed';
}, 500);
Copy the code

In the above example, modify the value of the exported object after 500ms.

// main.js
import { thing as importedThing } from './module.js';
const module = await import('./module.js');
let { thing } = await import('./module.js');

setTimeout(() = > {
  console.log(importedThing); // "changed"
  console.log(module.thing); // "changed"
  console.log(thing); // "initial"
}, 1000);
Copy the code

After 1s of output, it is found that the first two outputs have changed, and the third one has not changed. That is, for named exports, the first two are references and the third is values.

But the default export is different:

// module.js
let thing = 'initial';

export { thing };
export default thing;

setTimeout(() = > {
  thing = 'changed';
}, 500);
Copy the code
// main.js
import { thing, default as defaultThing } from './module.js';
import anotherDefaultThing from './module.js';

setTimeout(() = > {
  console.log(thing); // "changed"
  console.log(defaultThing); // "initial"
  console.log(anotherDefaultThing); // "initial"
}, 1000);
Copy the code

Why is the import result for the default export a value rather than a reference?

The reason is that the default export can be regarded as a special case of “default assignment”, as expressed in the old syntax export default = thing, which is essentially an assignment, so you get a value instead of a reference.

Is the same true of export {thing as default}, another way of writing the default export? Not:

// module.js
let thing = 'initial';

export { thing, thing as default };

setTimeout(() = > {
  thing = 'changed';
}, 500);
Copy the code
// main.js
import { thing, default as defaultThing } from './module.js';
import anotherDefaultThing from './module.js';

setTimeout(() = > {
  console.log(thing); // "changed"
  console.log(defaultThing); // "changed"
  console.log(anotherDefaultThing); // "changed"
}, 1000);
Copy the code

As you can see, the default export is all references. So whether an export is a reference depends not on whether it is named, but on how it is written. Different writing effect is different, even if the same meaning of different writing, the effect is also different.

Is it the way you write it? Yes, export default exports values instead of references. Unfortunately, there is a special case:

// module.js
export default function thing() {}

setTimeout(() = > {
  thing = 'changed';
}, 500);
Copy the code
// main.js
import thing from './module.js';

setTimeout(() = > {
  console.log(thing); // "changed"
}, 1000);
Copy the code

Why is export Default Function a reference? The reason is that export default Function is a special case, which results in exporting references instead of values. If we export Function in the normal way, we still follow the previous rule:

// module.js
function thing() {}

export default thing;

setTimeout(() = > {
  thing = 'changed';
}, 500);
Copy the code

As long as the export default Function syntax is not written, the reference does not change even if the exported object is a function. So the effect depends on how you write, not on the type of object you export.

Circular references also work sometimes, sometimes not, depending on how you write them. The following circular reference works fine:

// main.js
import { foo } from './module.js';

foo();

export function hello() {
  console.log('hello');
}
Copy the code
// module.js
import { hello } from './main.js';

hello();

export function foo() {
  console.log('foo');
}
Copy the code

Why is that? As export function is a special case, JS engine has made global reference promotion to it, so both modules can access it separately. This is not possible because global promotion is not done:

// main.js
import { foo } from './module.js';

foo();

export const hello = () = > console.log('hello');
Copy the code
// module.js
import { hello } from './main.js';

hello();

export const foo = () = > console.log('foo');
Copy the code

So whether it works depends on whether it improves, and whether it improves depends on how you write it. Of course, the following will also fail as a circular reference, because it will be resolved to the derived value:

// main.js
import foo from './module.js';

foo();

function hello() {
  console.log('hello');
}

export default hello;
Copy the code

Here is the end of the author’s exploration, let’s sort out the ideas, try to understand the law.

Intensive reading

It can be understood as follows:

  1. An export and an import are references that are ultimately references.
  2. When importing, divide by{} = await import()All are references.
  3. When exporting, divide byexport default thingexport default 123All are references.

{} = await import() is equivalent to reassignment for imports, so references to concrete objects are lost, that is, asynchronous imports are reassigned, and const Module = await import() references remain unchanged because the module itself is an object, The reference to module.thing remains the same, even if module is reassigned.

For exports, the default export can be understood as the syntactic sugar of export default = thing, so default itself is a new variable to be assigned, so it is reasonable that references to the underlying type cannot be exported. Even export default ‘123’ is legal, and export {‘123’ as thing} is illegal, because the named export is essentially assigning to the default variable. You can either assign to an existing variable or use a value directly. But named exports don’t have assignments, so you can’t use a literal for named exports.

However, there is a special case for export, export default function, which we should write as little as possible, and it doesn’t matter if we write it, because it will not cause any problems if the function keeps reference unchanged.

To ensure that imports are always references, try to use named imports and named exports. If neither of these is possible, try to wrap variables that need to maintain references in Object rather than simple variables.

Finally, for circular dependency, only export default function has Magic to declare promotion, which can ensure the normal Work of circular dependency, but it is not supported in other cases. The best way to avoid this problem is not to write out circular dependencies and to use a third module as a middleman when it does.

conclusion

It is generally desirable to import references rather than transient values, but not all writing effects are the same because of semantics and special syntax.

I also think there is no need to memorize the details of import and export differences, as long as the module is written with the standard name of import and export, less default export, can avoid these problems in terms of semantics and practical performance.

The discussion address is: intensive reading export Default/Named export difference · Issue #342 · dT-fe /weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)