小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
本文同时参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金
背景
-
前端模块与时代息息相关,很久以前, JS 并没有模块体系,也就意味着无法将大的程序拆分为相互依赖的小文件、再拼装起来。
-
ES6(ES2015)之前主要使用的模块规范
- CommonJS 用于服务器,特点是同步加载,缺点是前端不友好,同步意味着阻塞
- AMD、CMD用于浏览器,AMD开发成本较高、代码阅读困难,CMD模块的加载逻辑偏重
-
ES6以后
- 模块思想:尽量静态化,使得编译时就能够确定模块的依赖关系、输出和出入的变量;CommonJS 和 AMD 模块,都只能在运行时确定这些。(比如:CommonJS 模块就是对象,输入时必须查找对象属性)
- ES6 在语言标准的层面上,实现了模块功能。但截止目前,各类浏览器引擎还未完全实现 ES6,现阶段需借助BABEL工具、转换为 ES5 再执行,这就导致 import 与 module.exports,require 与 export 这些在我们开发时候是可以混用的
-
本文主要探讨,混用时应该注意些什么?服务端应该如何使用?纯前端JS模块如何混用(借助webpack-build)?
问题:文末解答
- 为什么 React、Vue、element-ui 的 export default 可以直接在 import 时解构?
- 前端开发ES6语法标准下, 为什么能混用 require?module.exports? 引入的时候注意些什么?
- node 和 es6 关系?
- webpack 的 tree-shaking 干啥的?
- app-api 中 es6/index.js 的 require 用法是否可以用 import 替代?为什么?
- 怎么开发一个 Node 和 ES6 环境都支持的npm包?
模块规范
CommonJS模块规范
- 每个文件就是一个模块,有自己的作用域,文件内定义的变量、函数、类都是私有的,对其他文件不可见
- Node 遵循 CommonJS 模块规范
- Node为每个模块提供一个exports变量 (对外的接口),指向module.exports
// 每个模块头部,有一行这样的命令
var exports = module.exports;
- module的属性如下
module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。
-
注意:不能将exports变量指向其他值
-
加载模块:require,其实是加载模块的module.exports属性
- require 加载机制:输入的是被输出的值的拷贝,一旦模块输出一个值,模块内部的变化不会影响到已经输出的值
- TODO require 内部处理
ES6模块规范
- default是 ES6 独有的关键字
- export default 为模块指定默认输出,只能使用一次
- export const xxx 单独输出,可多次使用
常见组合方式
import
- import 与 export const xxx
//export.js
export const obj = { name: 'module'};
//index.js
import { obj } from './export';
console.log(obj.name); //module
import * as customObj from './export';
console.log(customObj.name); //module
- import 与 export default
//export.js
export default { name: 'module'};
//index.js
import obj from './export';
console.log(obj.name); //module
//错误写法
import { name } from './export';
- import 与 module.exports
//export.js
module.exports = { name: 'module' };
//index.js
import { name } from './export';
console.log(name); //module
import customObj from './export';
console.log(customObj.name); //module
require
- require 与 module.exports
//export.js
mocule.exports = { name: 'module'};
//index.js
const exportsObj = require("./export");
console.log(exportsObj.name); //module
- require 与 export const xxx
//export.js
export const obj = { name: 'module' };
//index.js
const { exportObj }| = require("./export");
console.log(exportObj.name); //module
- require 与 export defalut
//export.js
export default { name: 'module' };
//index.js
const { exportObj }| = require("./export").default;
console.log(exportObj.name); //module
TODO Webpack模块化解析
webpack 和 babel 的原理 webpack 混合打包的情况
错误的用法
- 语法错误
//exoprt.js
export 1;
//export.js
const obj = { name: 'module' };
export obj;
// 以上错误原因:没有对外提供接口,直接输出了值,不是接口
// 导出的本质是:接口名和比内部变量需要建立一对一的关系
//export.js
export default const obj = { name: 'module' };
// 以上错误原因:试图重新定义default变量
- 同一个模块中
//export.js
import { a } from './export.js';
module.exports = {
name: a
}
//export.js
// 切断了 exports 与 module.exports 的联系
exports = (param) => { //... }
// 因为 module.exports 被重新赋值,sayHello 无法对外输出
exports.sayHello = function() {
return 'hello'
}
module.exports = 'Hello world'
#不建议的用法
- 纯前端模块文件中出现 module.exports 导出
- node应用的模块中单独对exports赋值或修改其指向
答案
- 这些开源包在打包后,大多数打成符合Commonejs规范的包、或者UMD类型的包
- 纯前端工程中使用ES6为模块标准,是webpack帮我们实现了require
- NodeJS 9.0+ 版本支持ES6模块标准
- tree-shaking 自动检测、静态分析没有使用的代码模块,前提是必须使用ES6标准模块
- 可以代替,app-api中环境变量属于浏览器端运行时判断,不会存在tree-shaking优化、模块都会打包输出到浏览器端
- webpack导出指定 Library 和 libraryTarget
综上
- 开发时候要分清楚运行环境
- 不要随意混用CommoneJs 和 ES6
- 开发多端支持的npm包时请先进行模块设计
点赞支持、手留余香、与有荣焉,动动你发财的小手哟,感谢各位大佬能留下您的足迹。
往期精彩推荐
Vue 虚拟 DOM 搞不懂?这篇文章帮你彻底搞定虚拟 DOM
Git 相关推荐
面试相关推荐
更多精彩详见:个人主页