美文网首页Harlen_luan的Web前端之路
ES Module之exports、module.exports

ES Module之exports、module.exports

作者: Harlen_luan | 来源:发表于2019-05-26 10:12 被阅读21次

    前言:本文着重介绍ES5和ES6的模块化加载,至于AMD和CMD因为已经过时了,仅仅只是提了下。早些年的前端都仅仅只是静态页面,还没有模块化这个概念,但随着时间的推移,前端代码愈发庞大,那么自然而然会暴露很多问题:

    • 命名冲突
    • 文件依赖

    那个时候都是通过匿名自执行函数来解决命名冲突,文件依赖只能手动保证引入的顺序正确,直到后来某国外大神造出来Require.js,从此前端模块化的概念就出现了;再后来淘宝前端大神玉伯根据require.js的思想造出了sas.js,至此,两大模块加载器在前端领域独领风骚一段时间。

    1. AMD

    AMDRequireJS 在推广过程中对模块定义的规范化产出,是浏览器端的模块化解决方案。

    2. CMD

    CMD是 SeaJS 在推广过程中对模块定义的规范化产出,是浏览器端的模块化解决方案。

    3. AMD与CMD的差异

    • AMD是提前执行(RequireJS2.0开始支持延迟执行,不过只是支持写法,实际上还是会提前执行),CMD是延迟执行
    • AMD推荐依赖前置,CMD推荐依赖就近

    4. CommonJS

    CommonJS是NodeJs服务器端模块的规范,ES Module是在ES5中推出的,基于CommonJS规范,而export和export default是ES6中的模块化加载语法。
    在这之前,必须了解 ES6 模块与 CommonJS 模块完全不同。它们有两个重大差异:

    • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

    第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。具体可参考阮一峰大神的ES6文章Module加载语法

    重点来了,下面着重介绍ES5中和ES6中的模块化加载方案:

    ES5中exports和module.exports的区别

    module.exports才是真正的接口,exports只不过是它的一个辅助工具。nodejs只会导出module.exports的指向,最终返回给调用者的是module.exports而不是exports。
    所有的exports收集到的属性和方法,都赋值给了Module.exports。当然,这有个前提,就是module.exports本身不具备任何属性和方法。
    如果,module.exports已经具备一些属性和方法,那么exports收集来的信息将被忽略。

    挂载在exports上的方式和属性,都会传递给module.exports。二者没区别。

    exports.fun = function() {
        console.log('Hello world')
    }
    exports.messgae = 'Nice to meet you'
    
    var isEq = (exports === module.exports);
    console.log(exports);
    console.log(module.exports);
    console.log(isEq);
    // output:
    // { fun: [Function], message: 'Nice to meet you' }
    // { fun: [Function], message: 'Nice to meet you' }
    // true
    

    支持用importrequire的具名和匿名引入

    let mod = require('./mod.js') // mod: // { fun: [Function], message: 'Nice to meet you' }
    import { fun, messgae } from './mod.js' // fun: [Function], message: Nice to meet you
    

    直接把变量赋值给exports,那么exports指向变了,那就仅仅是exports不再指向module.exports

    exports.fun = function() {
        console.log('Hello world')
    }
    exports = 'Nice to meet you'
    
    var isEq = (exports === module.exports);
    console.log(exports);
    console.log(module.exports);
    console.log(isEq);
    // output
    // Nice to meet you
    // { fun: [Function] }
    // false
    
    let mod = require('./mod.js') // mod: // { fun: [Function] }
    

    直接把变量赋值给module.exports,那就说明module.exports和exports的引用关系断开了,二者不相等了。

    exports.fun = function() {
        console.log('Hello world')
    }
    
    module.exports = 'Nice to meet you'
    
    var isEq = (exports === module.exports);
    console.log(exports);
    console.log(module.exports);
    console.log(isEq);
    // output:
    // { fun: [Function] }
    // Nice to meet you
    // false
    
    let mod = require('./exports.js') // mod: Nice to meet you
    

    ⚠️建议NodeJS开发者注意一下两点:

    1. 最好别分别定义module.exports和exports
    2. 导出对象用module.exports,导出多个方法和变量用exports

    导出多个属性或方法:

    exports.fun = function() {
        console.log('Hello world')
    }
    
    exports.message = 'Nice to meet you'
    
    // 匿名引入
    let mod = require('./mod.js') // { fun: [Function], message: 'Nice to meet you' }
    // or 具名引入
    let { fun, message } = require('./mod.js') // [Function], Nice to meet you
    

    导出一个对象:

    let fun = function() {
        console.log('Hello world')
    }
    
    let message = 'Nice to meet you'
    
    module.exports = {
        fun,
        message
    }
    
    // 匿名引入
    let mod = require('./mod.js') // { fun: [Function], message: 'Nice to meet you' }
    // or 具名引入
    let { fun, message } = require('./mod.js') // [Function], Nice to meet you
    

    ⚠️这里必须用module.exports,而不能用exports,因为nodejs只会导出module.exports的指向

    ES6中export 和 export default区别

    1. export与export default均可用于导出常量、函数、文件、模块等
    2. 在一个文件或模块中,export、import可以有多个,export default仅有一个
    3. export方式只能具名导入,在导入时要加{ };export default则只能匿名导入
    4. export能直接导出变量表达式,export default不行。

    export

    const Programmer = {name: 'UncleFirefly',age:25}
    export let message = 'hello'
    export { Programmer }
    console.log(module.exports) // { message: 'hello', Programmer: { name: 'UncleFirefly', age: 25 } }
    

    import引入时必须具名导入,也就是需要声明式指定对象里的key来导入你需要的

    import { Programmer } from './mod.js' // Programmer: {name: 'UncleFirefly',age:25}
    

    如果使用require引入的话,则可以直接匿名引入

    let mod = require('./mod.js') // mod: {  message: 'hello', Programmer: { name: 'UncleFirefly', age: 25 } }
    
    // or require也支持具名导入
    let { Programmer } = require('./export.js') // Programmer: {name: 'UncleFirefly',age:25}
    

    export default

    const message = 'hello'
    const Programmer = {name: 'UncleFirefly',age:25}
    export default Programmer
    // export default message // 报错:export default只能用一次
    console.log(module.exports) // { default: { name: 'UncleFirefly', age: 25 } }
    

    只能匿名导入

    import mod from './mod.js' // mod: { name: 'UncleFirefly', age: 25 }
    

    如果使用require引入的话,必须通过default属性拿到实际导出的变量:

    let mod = require('./mod.js') // mod: { default: { name: 'UncleFirefly', age: 25 } }
    

    export与import混合使用

    具名接口改为默认接口的写法如下:

    export { es6 as default } from './someModule';
    
    // 等同于
    import { es6 } from './someModule';
    export default es6;
    

    同样地,默认接口也可以改名为具名接口:

    export { default as es6 } from './someModule';
    

    相关文章

      网友评论

        本文标题:ES Module之exports、module.exports

        本文链接:https://www.haomeiwen.com/subject/tdcxtctx.html