zoukankan      html  css  js  c++  java
  • 探讨ES6的import export default 和CommonJS的require module.exports

    今天来扒一扒在node和ES6中的module,主要是为了区分node和ES6中的不同意义,避免概念上的混淆,同时也分享一下,自己在这个坑里获得的心得。

    在ES6之前

    模块的概念是在ES6发布之前就出现的,我感觉主要是为了适应大型应用开发的需要而引入了JavaScript世界。模块化编程已经从噱头上升为必备,所以ES6也顺应时代,把这个写进了标准。

    CommonJS和AMD都是JavaScript模块化规范,在ES6之前,Node主要遵循CommonJS,而AMD则主要运用在浏览器端,比如requirejs。

    而且node发布的时候,就天生具备module,所以从某种意义上讲,是node促进了js世界里面的模块化编程。

    // module-file.js for node
    module.exports = {
      a : function() {},
      b : 'xxx'
    };
    

    把上面这个js文件放在node环境中,我们这样去用它:

    var myModule = require('./module-file');
    var b = myModule.b;myModule.a();
    

    从模块化思想出发,requirejs和国内前端大牛发布的seajs(遵循CMD)也允许前端猿们通过require去加载另一个模块。不过在模块定义的时候,需要借助一个define函数:

    // module-file.js for requirejs
    define(function(require, exports, module){
       module.exports = {};
    });
    

    requirejs和seajs都支持上面这种形式,在define的回调函数中提供了模拟的require, exports, module,这让习惯了node环境中使用方法的猿类可以顺便在浏览器端写基本相同的代码。

    node,requirejs,seajs也同时支持下面这种导出模块的方式:

    define(function(){
      return {}; // return的值就是导出的模块
    });
    

    这就出现了UMD,即一个兼容多种环境的方案。

    Node,requirejs中的exports

    node中导出模块接口就是用exports,你可以这样做:

    module.exports.a = function() {};
    module.exports.b = 'xxx';
    

    也可以写在一个对象中:

    module.exports = {
      a : function() {},
      b : 'xxx'
    }
    

    在requirejs中,还提供了一个exports变量作为module.exports的别名:

    define(function(require, exports, module){
       exports.a = function(){};
       exports.b = 'xxx';
    });
    

    注意“别名”的含义:exports是module.exports的地址的引用。它的本质是:

    var exrpots = module.exports;
    

    因此,你必须注意两个点,就是导出接口的时候:
    1.不能直接用exports={}来导出整个接口;
    2.如果使用了module.exports={},那么exports.a等都会被覆盖无效。

    ES6中的import和export

    在ES6之前,要使用一个模块,必须使用require函数将一个模块引入,但ES6并没有采用这种模块化方案,在ES6中使用import指令引入一个模块或模块中的部分接口,并没有将require写入标准,这也就是说require对于ES6代码而言,只是一个普通函数。

    同理,在ES6标准中,导出模块的接口也只能使用export指令,而非exports对象,这也同样意味着module.exports只是node,requirejs等模块化库的自定义变量,而非ES标准接口。

    常见export方式

    在ES6规定中,这样导出一个模块的接口:

    export function fun() {};
    export { name1, name2, …, nameN };
    export { variable1 as name1, variable2 as name2, …, nameN };
    export let name1, name2, …, nameN; // also var
    export let name1 = …, name2 = …, …, nameN; // also var, const
    export default expression;
    export default function (…) { … } // also class, function*
    export default function name1(…) { … } // also class, function*
    export { name1 as default, … };
    export * from …;export { name1, name2, …, nameN } from …;
    export { import1 as name1, import2 as name2, …, nameN } from …;
    

    ES6里面,直接把要导出的变量、函数、对象、类等前面加一个export关键字。比如:

    // module-file.js
    export function a(){};
    export var obj = {};
    

    有一个点:export必须导出具有对应关系的变量,下面的接口输出是错误的:

    // 错误演示
    export 1; // 这种导出的内容不是变量是绝对错误的,包括导出表达式,也是绝对错误的
    var a = 1;
    export a;
    function b() {}
    export b;
    

    上面的这些方法都是错误的,不能这样导出接口。导出接口仅限两种:

    1.声明时导出
    2.以对象的形式导出(和解构联系起来)

    如果要导出某个变量,可以用花括号括起来,像这样:

    var a = 1;
    export {a}; // 等效于:{a:a}
    function b() {}
    export {b};
    

    我有点疑惑的是,为何export允许多次export {}这种形式?看上去很奇怪。另外,ES6更厉害之处在于,可以在export变量之后,继续修改变量:

    export var obj {};obj.a = 1;
    

    在import之后,obj的值仍然可以在模块内继续改变,这是CommonJS以往不可能做到的。

    import基本用法

    在另外一个js文件里面这样使用这些接口:

    import {a,obj} from './module-file';
    a();
    alert(obj.b);
    

    和node之前的require不一样,require只能把模块放到一个变量中,而在ES6中,拥有对象解构赋值的能力,所以直接就把引入的模块的接口赋值给变量了。内在机理也有不同,require需要去执行整个模块,将整个模块放到内存中(也就是我们说的运行时),如果只是使用到其中一个方法,性能上就差很多,而import...from则是只加载需要的接口方法,其他方法在程序启动之后根本触及不到,所以这种又被称为“编译时”,性能上好很多。

    as关键字

    编程的同学对as都容易理解,简单的说就是取一个别名。上面export中可以用,import中其实也可以用:

    // a.js
    var a = function() {};
    export {a as fun};
    // b.js
    import {fun as a} from './a';a();
    

    上面这段代码,export的时候,对外提供的接口是fun,它是a.js内部a这个函数的别名,但是在模块外面,认不到a,只能认到fun。

    import中的as就很简单,就是你在使用模块里面的方法的时候,给这个方法取一个别名,好在当前的文件里面使用。之所以是这样,是因为有的时候不同的两个模块可能通过相同的接口,比如有一个c.js也通过了fun这个接口:

    // c.js
    export function fun() {};
    

    如果在b.js中同时使用a和c这两个模块,就必须想办法解决接口重名的问题,as就解决了。

    default关键字

    其他人写教程什么的,都把default放到export那个部分,我觉得不利于理解。在export的时候,可能会用到default,说白了,它其实是别名的语法糖:

    // d.js
    export default function() {}
    // 等效于:function a() {}; export {a as default};
    

    在import的时候,可以这样用:

    import a from './d';
    // 等效于,或者说就是下面这种写法的简写,是同一个意思import {default as a} from './d';
    

    这个语法糖的好处就是import的时候,可以省去花括号{}。简单的说,如果import的时候,你发现某个变量没有花括号括起来,那么你在脑海中应该把它还原成有花括号的as语法。

    所以,下面这种写法你也应该理解了吧:

    import _,{each,map} from '_';
    

    *符号

    *就是代表所有,只用在import中,我们看下两个例子:

    import * as underscore from '_';
    

    在意义上和import _ from '';是不同的,虽然实际上后面的使用方法是一样的。它表示的是把''模块中的所有接口挂载到underscore这个对象上,所以可以用underscore.each调用某个接口。

    export * from '_';
    // 等效于:import * as all from '_';export all;
    

    该用require还是import?

    接下来的问题,就是我们在实操中,还有必要用require吗?我感觉ES6标准已经将之前的所有模块化规范都给碾压了,这在以前的标准发布中极少见,ES6用更加简单的方式,实现了更加有效的module,感觉require可以回家养老了。

    标准与非标准

    既然是标准,那么就是所有引擎应该去实现的,node和浏览器未来都会直接支持这种模块加载方式,require完成历史使命回家自己玩儿。而且作为node或浏览器,同时可以利用import提供自己的API,比如手机端提供基于网络的定位API,这都不用SDK了,直接内置在客户端内部,import一下就可以了。

    不过现在import导入模块还并不是全部环境都支持,使用babel可以让node支持ES6,但在浏览器端,则毫无办法,可能还得暂时依赖require。但是非常不好的消息是,require不是ES6标准,这也就是说如果将来浏览器支持import后,你想用它,就必须升级代码,而不能直接被兼容。

    import只能在文件开头使用,在import之前,你不能有其他的代码,这和其他语言是一样的。但是require则不同,它相当于node的一个定义在全局的函数,你可以在任意地方使用它,甚至使用变量表达式作为它的参数,这样有一个好处,就是可以在循环中加载模块。

    有没有兼容import和require的模块?

    但是很坑的是,node的模块导出和ES6标准也不符,因为node的模块体系遵循的是CommonJS规范,这就导致你写的模块文件,不可能同时支持require和import。

    要强调的就是,不要把require和import两种模块加载方案混用,比如:

    // module-file.js
    module.exports = {};
    // a.js
    import a from './moule-file';
    
    

    这种混搭感觉不是很好(但可以用,下面有解释)。所以,其实我没有任何建议,我只是觉得,躺在坑里,挺自在的……毕竟node中require的使用更加灵活一点,它没有必须放在哪里的限制,所以可以在任意位置使用,而且它的结果也非常形象,甚至可以把require当做一个引用类型别名,可以这样使用:

    require('./a')(); // a模块是一个函数,立即执行a模块函数
    var data = require('./a').data; // a模块导出的是一个对象
    var a = require('./a')[0]; // a模块导出的是一个数组
    

    这样的写法感觉像给模块取了一个别名,使用的时候非常灵活。但是需要注意的是,如果你打算使用require来导入这个模块,那么请使用module.exports导出这个模块。

    (临时)兼容方案

    有没有一种兼容方案呢?

    function a() {}
    class b {}
    module.exports = {a,b}; // {a,b}是ES6的写法
    

    在实践中发现,module.exports可以兼容require和import,而且这个案例需要你的node环境配置好支持ES6语法。module.exports导出的模块,如果使用import,那么完全就是一个对象赋值、解构的过程:

    import mod,{a,b} from './a';
    

    之所以这是成立的,是因为我们使用babel对ES6代码进行转码后执行,而实际上,目前为止,没有任何一个环境是支持ES6 module方案的,即使babel,也仅仅是将ES6的import,export转码为require, module.exports后交给node去执行。

    导出的模块接口被赋值给mod,所以mod是一个对象,含有a,b两个方法。这里的mod并没有通过default导出,所以和ES6有非常大的意义上的区别,这种非标准的写法,墙裂建议永远不要用。而且,由于require和module.exports是非标准的东西,仅在Node环境中有效,所以当未来浏览器支持模块导入时,并不会主动提供require,而是采用import,如果要使用require,还是不得不使用requirejs等库,借助define来用。

    所以,最终,如果你打算用CommonJS,就不要掺和进ES6.

    转载链接:https://www.tangshuang.net/2882.html

  • 相关阅读:
    475. Heaters
    69. Sqrt(x)
    83. Remove Duplicates from Sorted List Java solutions
    206. Reverse Linked List java solutions
    100. Same Tree Java Solutions
    1. Two Sum Java Solutions
    9. Palindrome Number Java Solutions
    112. Path Sum Java Solutin
    190. Reverse Bits Java Solutin
    202. Happy Number Java Solutin
  • 原文地址:https://www.cnblogs.com/both-eyes/p/10348511.html
Copyright © 2011-2022 走看看