Open FrankKai opened 4 years ago
module.exports是由Module系统创建出的。有时这是不可接受的;许多人想让他们的模块成为一些class的实例。为了做到这个,将目标导出对象赋予module.exports。将对象赋予exports会重新板顶exports变量,这不是我们想要的。
a.js
const EventEmitter = require('events'); module.exports = new EventEmitter(); setTimeout(() => { module.exports.emit('ready'); }, 1000);
b.js
const a = require('./a'); a.on('ready', () => { console.log('module "a" is ready'); });
x.js
setTimeout(() => { module.exports = { a: 'hello' }; }, 0);
y.js
const x = require('./x'); console.log(x.a); // undefined
Module { id: '/Users/frank/weidian/weidian-crm/a.js', exports: { a: 'hello' }, parent: // a.js的父模块是b.js Module { id: '.', exports: {}, parent: null, filename: '/Users/frank/weidian/weidian-crm/b.js', loaded: false, children: [ [Circular] ], paths: [ '/Users/frank/weidian/weidian-crm/node_modules', '/Users/frank/weidian/node_modules', '/Users/frank/node_modules', '/Users/node_modules', '/node_modules' ] }, filename: '/Users/frank/weidian/weidian-crm/a.js', loaded: false, children: [], // 注意这里,a.js的子模块是空的 paths: [ '/Users/frank/weidian/weidian-crm/node_modules', '/Users/frank/weidian/node_modules', '/Users/frank/node_modules', '/Users/node_modules', '/node_modules' ] }
Module { id: '.', exports: {}, parent: null, // b.js的父模块是null filename: '/Users/frank/weidian/weidian-crm/b.js', loaded: false, children: // b.js的子模块包括a.js [ Module { id: '/Users/frank/weidian/weidian-crm/a.js', exports: [Object], parent: [Circular], filename: '/Users/frank/weidian/weidian-crm/a.js', loaded: true, children: [], paths: [Array] } ], paths: [ '/Users/frank/weidian/weidian-crm/node_modules', '/Users/frank/weidian/node_modules', '/Users/frank/node_modules', '/Users/node_modules', '/node_modules' ] }
前提条件是按照exports.foo = foo; exports.bar = bar;的方式,若是exports被重新赋值,比如exports = {foo:'foo'},那么exports不再是module.exports的引用,模块不能导出。
exports变量在模块的文件级作用域是有效的,在模块计算之前赋值给module.exports。 支持缩写的方式,module.exports.f = ... 可以写为exports.f = ... 然而,如果为exports完全赋予一个新值,那么exports就不再与module.exports绑定了。
module.exports.hello = true; // 可以导出,模块可以按照require()引入 exports.hello = true; // 可以导出,模块可以按照require()引入 exports = { hello: true }; // 不能导出,因为exports不再是module.exports的缩写
当module.exports属性完全被一个新对象取代,可以重新赋值exports:
module.exports = exports = function Constructor() { // ... etc. };
为了解释这个现象,可以想象一下require()的实现。
function require(/* ... */) { const module = { exports: {} }; // 全局module对象 ((module, exports) => { // 模块代码 function someFunc() {} // 这里的exports是module.exports的引用 exports.someFunc = someFunc; // 这里的exports被重新赋值,不再是module.exports的引用 exports = someFunc; // 这里的module是const module = { exports: {} };的module module.exports = someFunc; })(module, module.exports); // 传入module.exports作为exports的引用 return module.exports; }
从上面的代码及注释,可以很清晰看出 重新赋值exports会导致模块导出失败 的原因:
重新给exports赋值后,局部变量(对象)丢失了对全局变量(对象)的引用。
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); }
module.exports = Vue;
module.exports导出了Vue全局构造函数。commonjs模块机制。
export default Vue;
export default同样导出了Vue全局构造函数。es modules模块机制。
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.Vue = factory()); }...)
common.js 如果exports是对象,而且module有定义,也就是说module.exports导出factory()。 amd规范,导出factory。 否则就生成一个向global的Vue属性挂载factory()。
media.js
/** * * 功能:转换Blob和File为Base64 string * * 思路:创建FileReader实例,读取file文件,FileReader实例的loadend事件触发获得duration * * 作者:FrankKai * * 日期:2019/11/8 * @param {Blob|File} file The file to be transform * @return {number} Return the file's Base64 string */ async function transferBlobFileToBase64(file) { return new Promise((resolve) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.onloadend = () => { const fileBase64 = reader.result; resolve(fileBase64); }; }); } /** * * 功能:获取音频文件时长(audio duration) * * 思路:创建伪<audio>,加载audio文件,<audio>的durationChange事件触发获得duration * * 作者:FrankKai * * 日期:2019/11/7 * @param {File|Url} source The source to be analyse * @return {number} Return the audio's duration */ function getAudioDuration(source) { return new Promise((resolve) => { const audioElement = document.createElement('audio'); audioElement.src = window.URL.createObjectURL(source); audioElement.ondurationchange = (e) => { const duration = e.path[0].duration; resolve(duration); URL.revokeObjectURL(audioElement.src); }; }); } export default { getAudioDuration, transferBlobFileToBase64 };
es modules方式导出函数。
代码编译后:
webpackJsonp[0], { aPPT: function(e, t, n) { "use strict"; var r = n("//Fk") , i = n.n(r); t.a = { getAudioDuration: function(e) { return new i.a(function(t) { var n = document.createElement("audio"); n.src = window.URL.createObjectURL(e), n.ondurationchange = function(e) { var r = e.path[0].duration; t(r), URL.revokeObjectURL(n.src) } } ) } } }, {
manifest.js中
function b(c) { if (a[c]) return a[c].exports; var f = a[c] = { i: c, l: !1, exports: {} }; return e[c].call(f.exports, f, f.exports, b), f.l = !0, f.exports }
webpack最终还是以exports导出的。
参考资料:https://nodejs.org/dist/latest-v12.x/docs/api/modules.html#modules_exports_shortcut
module.exports
module.exports是由Module系统创建出的。有时这是不可接受的;许多人想让他们的模块成为一些class的实例。为了做到这个,将目标导出对象赋予module.exports。将对象赋予exports会重新板顶exports变量,这不是我们想要的。
最常见的例子
a.js
b.js
module.exports不能在callback中完成。
x.js
y.js
打印子a.js Module 对象和 父b.js Module对象
子a.js Module对象
父b.js Module对象
exports shortcut
exports是module.exports的缩写。但是需要一个前提条件。
前提条件是按照exports.foo = foo; exports.bar = bar;的方式,若是exports被重新赋值,比如exports = {foo:'foo'},那么exports不再是module.exports的引用,模块不能导出。
exports变量在模块的文件级作用域是有效的,在模块计算之前赋值给module.exports。 支持缩写的方式,module.exports.f = ... 可以写为exports.f = ... 然而,如果为exports完全赋予一个新值,那么exports就不再与module.exports绑定了。
重新赋值exports会导致模块导出失败
当module.exports属性完全被一个新对象取代,可以重新赋值exports:
为了解释这个现象,可以想象一下require()的实现。
为什么重新赋值exports会导致模块导出失败?
从上面的代码及注释,可以很清晰看出 重新赋值exports会导致模块导出失败 的原因:
重新给exports赋值后,局部变量(对象)丢失了对全局变量(对象)的引用。
vue.js分析
vue.runtime.common.js
module.exports导出了Vue全局构造函数。commonjs模块机制。
vue.runtime.esm.js
export default同样导出了Vue全局构造函数。es modules模块机制。
vue.runtime.js
common.js 如果exports是对象,而且module有定义,也就是说module.exports导出factory()。 amd规范,导出factory。 否则就生成一个向global的Vue属性挂载factory()。
项目实战分析
media.js
es modules方式导出函数。
代码编译后:
manifest.js中
webpack最终还是以exports导出的。
参考资料:https://nodejs.org/dist/latest-v12.x/docs/api/modules.html#modules_exports_shortcut