当前位置: 首页 > Web前端 > JavaScript

精读《默认、命名导出的区别》

时间:2023-03-27 11:54:49 JavaScript

从代码可维护性的角度来看,命名导出优于默认导出,因为它减少了由于引用而重命名的发生。但是namedexport和defaultexport的区别不止于此,在逻辑上也有很大的区别。为了减少开发时在这方面的绊脚石,有必要提前了解它们的区别。这周发现了一篇这方面的好文章:export-default-thing-vs-thing-as-default,先描述一下大纲,再说说我的理解。概述一般我们认为import导入的是引用而不是值,即当导入对象的值在模块中发生变化时,import导入的对象的值也应该同步变化。//module.jsexportletthing='initial';setTimeout(()=>{thing='changed';},500);上例中,500ms后修改导出对象的值。//main.jsimport{thingasimportedThing}from'./module.js';constmodule=awaitimport('./module.js');let{thing}=awaitimport('./module.js');setTimeout(()=>{console.log(importedThing);//“改变”console.log(module.thing);//“改变”console.log(thing);//“初始”},1000);1s后,输出发现前两个输出结果发生了变化,但是第三个输出没有变化。也就是说,对于命名导出,前两个是引用,第三个是值。但默认导出不同://module.jsletthing='initial';export{thing};exportdefaultthing;setTimeout(()=>{thing='changed';},500);//main.jsimport{thing,defaultasdefaultThing}from'./module.js';importanotherDefaultThingfrom'./module.js';setTimeout(()=>{console.log(thing);//“改变了”console.log(defaultThing);//“初始”console.log(anotherDefaultThing);//“初始”},1000);为什么默认导出的导入会产生值而不是引用?原因是默认导出可以看作是“默认赋值”的特例,就像老语法exportdefault=thing一样,本质上是赋值,所以得到的是值而不是引用。那么export{thingasdefault}exportedbydefault的另一种写法是不是一样的呢?不是://module.jsletthing='initial';export{thing,thingasdefault};setTimeout(()=>{thing='changed';},500);//main.jsimport{thing,defaultasdefaultThing}from'./module.js';importanotherDefaultThingfrom'./module.js';setTimeout(()=>{console.log(thing);//“改变”了console.log(defaultThing);//“改变了”console.log(anotherDefaultThing);//“改变了”},1000);可以看出这个默认导出的都是引用。所以一个导出是否是引用并不取决于它是否是命名导出,而是取决于它是如何写的。不同的写法有不同的效果,即使是同一个意思不同的写法也有不同的效果。是拼写的问题吗?可以,只要exportdefault导出值而不是引用即可。但不幸的是,有一种特殊情况://module.jsexportdefaultfunctionthing(){}setTimeout(()=>{thing='changed';},500);//main.jsimportthingfrom'./module.js';setTimeout(()=>{console.log(thing);//“改变”},1000);为什么导出默认函数是参考?原因是exportdefault函数是特例,这种写法会导致导出的是引用而不是值。如果我们以正常方式导出Function,它仍然遵循之前的规则://module.jsfunctionthing(){}exportdefaultthing;setTimeout(()=>{thing='changed';},500);只要不写exportdefaultfunction语法,即使导出的对象是Function,引用也不会改变。所以看效果的是写法,和导出对象的类型无关。对于循环引用有时生效有时不生效的问题,其实还是要看写法。以下循环引用工作正常://main.jsimport{foo}from'./module.js';foo();exportfunctionhello(){console.log('hello');}//module.jsimport{你好}来自'./main.js';hello();exportfunctionfoo(){console.log('foo');}为什么?因为导出函数是一个特例,JS引擎对它做了一个全局引用,所以两个模块可以单独访问。以下方法将不起作用,因为它不会进行全局提升://main.jsimport{foo}from'./module.js';foo();exportconsthello=()=>console.log('hello');//module.jsimport{hello}from'./main.js';hello();exportconstfoo=()=>console.log('foo');所以有没有效果要看有没有推广,有没有提升要看措辞。当然,下面的写法也会导致循环引用失败,因为这种写法会被解析为导出值://main.jsimportfoofrom'./module.js';foo();functionhello(){console.log('hello');}exportdefaulthello;笔者的探索到这里就结束了,我们来梳理一下思路,试着理解其中的规律。精读可以这样理解:当export和import都是引用时,最后的引用就是引用。导入的时候,除了{}=awaitimport()之外都是引用。导出的时候除了exportdefaultthing和exportdefault123都是引用。对于import,{}=awaitimport()相当于重新赋值,所以会丢失具体对象的引用,也就是说异步导入会被重新赋值,而constmodule=awaitimport()的引用保持不变的原因是,module本身是一个对象,即使模块被重新赋值,module.thing的引用也保持不变。对于export,defaultexport可以理解为exportdefault=thing的语法糖,所以default本身就是一个新赋值的变量,按道理不能导出基本类型的引用。连exportdefault'123'都是合法的,而export{'123'asthing}是非法的,就证明了这一点,因为namedexport的本质就是给default变量赋值,可以使用已有的变量赋值,或直接使用A值,但命名导出不存在用于赋值,因此您不能将文字用于命名导出。但是export有一个特例,exportdefaultfunction,这个我们可以尽量少写,写了也没关系,因为这个函数保持引用不变,一般不会出什么问题。为了保证导入总是被引用,一方面尽量使用namedimports,另一方面注意namedexports。如果这两点都达不到的话,尽量把需要维护引用的变量用Object封装起来,而不是使用简单的变量。最后,对于循环依赖,只有exportdefault函数有声明提升的魔法,可以保证循环依赖的正常工作,其他情况不支持。要避免这种问题,最好的办法就是不写循环依赖,遇到循环依赖就用第三个模块做中间人。总结一般情况下,我们希望导入的是引用而不是瞬时值,但是由于语义和特殊的语法糖,并不是所有的书写效果都是一致的。我也认为没有必要记住这些导入和导出细节之间的差异。只要在编写模块时使用标准名称import和export,少用默认的exports,就可以从语义和实际性能上避免这些问题。讨论地址是:Jingdu《export 默认/命名导出的区别》·Issue#342·dt-fe/weekly想参与讨论的请点这里,每周都有新话题,周末或周一发布。前端精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)