Skip to main content

关于模块化的学习

· 4 min read
wang bo
早睡早起

时间久了,之前学过的东西都忘记了,其中就包含模块化的知识点,直到公司后端大佬学前端的时候对于模块化有疑问,问到了我这个小菜鸟,然后翻水水;我在痛定思痛之余决心好好复习一下模块化知识,并且记录下来,方便以后复习。


梳理技术标准体系#

要了解前端标准和模块化之前,先了解一下前端发展史。

⽆标准阶段#

即 1993 年 NCSA 发布了 NCSA Mosaic,1994 年网景公司发布 Netscape Navigator 后,1995 年 Mocha 语言发布,9月改名 LiveScript,12月改名 JavaScript。微软也发布了 IE3,JScript 与 IE4。“用 Netscape 可达到最佳效果”和“用 IE 可达到最佳效果”这两句经典的话源自于无标准时代。

标准化阶段#

在1996年11月 网景向ECMA提交标准。并在1997年6月,ECMA 以 JavaScript 语⾔为基础制定了ECMAScript 标准规范 ECMA-262。

此处放上ECMAScript的版本表 ECMAScript.png

什么是模块化#

将一个复杂的程序依据一定的规则,封装成几个文件,并组合到一起,块内的数据和方法是私有的,并且向外界暴露出一些接口(方法)与其他模块通信。

模块化的进化#

  • 原始写法
    • 污染全局变量
function m1(){}
function m2(){}
  • 对象写法(namespace写法)
    • 内部状态可以被外部改写
var module1 = new Object({  _count : 0,
  m1 : function (){  },
  m2 : function (){  }});
module1._count = 5;
  • 立即执行函数写法(IIFE写法)
    • 不暴露私有成员
var module1 = (function(){  var _count = 0;
  var m1 = function(){  };
  var m2 = function(){  };
    return { m1, m2 };})();
console.log(module1._count); //undefined
  • 放大模式
    • react高阶组件的意思也差不多,我称之为一层套一层
    • 应用场景:一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块
var module1 = (function (mod){  mod.m3 = function () {  };
  return mod;})(module1);
  • 输入全局变量
    • jquery 的 “$” 简直不要太熟悉
var module1 = (function ($, YAHOO) {    ...})(jQuery, YAHOO);

CommonJS#

  • 主要实现者: Node.js
  • 关键词:exports,require
// ./people.js// 导出exports.people = {    say: function(name){        return 'hello, ' + name;    }}
// ./app.js// 导入let people = require('./people.js').say;console.log(people.say('wangbo122'));

AMD#

Asynchronous Module Definition

  • 主要实现者: RequireJS
  • 关键词:
    • define(id?, dependencies?, factory);
    • require([module],callback);
  • 在导入的时候会加载完定义的模块后才会运行代码
// ./people.jsdefine(function(){    say: function(name){        return 'hello, ' + name;    }})
// ./app.jsrequire(['people'],function(people){    let words = people.say('wb122');    document.write(words);})
<srcipt src="require.js"></srcipt><srcipt src="app.js"></srcipt>

CMD#

Common Module Definition

  • 主要实现者: SeaJS
    • AMD依赖前置,需要提前加载
    • CMD依赖后置,使用时才加载

SystemJS#

SystemJS 是⼀个通⽤的模块加载器,它能在浏览器或者 NodeJS 上动态加载模块,并且⽀持 CommonJS、AMD、全局模块对象和 ES6 模块。通过使⽤插件,它不仅可以加载 JavaScript,还可以加载 CoffeeScript 和 TypeScript。

// 异步输入一个模块System.import('module-name');
// 通过配置API配置SystemJSSystem.config({    transplier: 'babel',    baseURL: '/app'})

* ES Module#

Es6版本加入了Es Module模块

1.与commonJS值的区别#

  • CommonJS
    • 1.基本类型:值复制
    • 2.引用类型:浅拷贝(内存地址相同)
    • 3.工作空间可以修改引入的值
  • ES Module
    • 1.只读导入,动态读取
    • 2.不可以再外部修改引用,只能调用引入模块中的方法

2.与commonJS执行顺序的区别#

  • CommonJS
    • 1.检查是否有该模块的缓存
    • 2.如果有,则使用缓存
    • 3.如果没有,执行该模块代码,并缓存
  • ES Module
    • 1.检查该模块是否被引入过
    • 2.是,暂时认为该模块为{}
    • 3.否,进入该模块执行代码(不缓存)
    • import会被提升到最先执行

export#

// 导出单个特性export let name1, name2, …, nameN; export let name1 = …, name2 = …, …, nameN; export function FunctionName(){...}export class ClassName {...}
// 导出列表export { name1, name2, …, nameN };
// 重命名导出export { variable1 as name1, variable2 as name2, …, nameN };
// 解构导出并重命名export const { name1, name2: bar } = o;
// 默认导出export default expression;export default function (…) { … } // also class, function*export default function name1(…) { … } // also class, function*export { name1 as default, … };
// 合并 modulesexport * from …; // does not set the default exportexport * as name1 from …;export { name1, name2, …, nameN } from …;export { import1 as name1, import2 as name2, …, nameN } from …;export { default } from …;

import#

// 导入整个模块的内容import * as myModule from '/modules/my-module.js';
// 导入多个接口import {foo, bar} from '/modules/my-module.js';
// 导入带有别名的接口import {reallyReallyLongModuleExportName as shortName} from '/modules/my-module.js';
// 整个模块仅为副作用导入,不导入模块中的任何接口。 将会运行模块中的全局代码, 但不导入任何值。import '/modules/my-module.js';
// 导入默认值import myDefault from '/modules/my-module.js';import myDefault, * as myModule from '/modules/my-module.js';// myModule used as a namespaceimport myDefault, {foo, bar} from '/modules/my-module.js';// specific, named imports
// 动态importimport('/modules/my-module.js')  .then((module) => {    // Do something with the module.  });let module = await import('/modules/my-module.js');

node环境下#

ES Module#

// 第一,将文件的扩展名由 .js 改为 .mjs;// 第二,启动时需要额外添加 `--experimental-modules` 参数;// Node v12 之后的版本,可以通过package.json中添加type字段为module,将默认模块系统修改为ES Module,此时就不需要修改文件扩展名为.mjs了// 如果需要在type=module的情况下继续使用CommonJS,需要将文件扩展名修改为.cjs
import { foo, bar } from './module.mjs';
// 此时我们也可以通过 esm 加载内置模块了import fs from 'fs';fs.writeFileSync('./foo.txt', 'es module working');
// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式import { writeFileSync } from 'fs';writeFileSync('./bar.txt', 'es module working');
// 对于第三方的 NPM 模块也可以通过 esm 加载import _ from 'lodash';_.camelCase('ES Module');
// 不支持,因为第三方模块都是导出默认成员// import { camelCase } from 'lodash'// console.log(camelCase('ES Module'))

与 CommonJS 共同使用#

  • ES Module 中可以导入 CommonJS 模块
  • CommonJS 中不能导入 ES Module 模块
  • CommonJS 始终只会导出一个默认成员
  • 注意 import 不是解构导出对象

commonJS

// CommonJS 模块始终只会导出一个默认成员
// module.exports = {//   foo: 'commonjs exports value'// }
// exports.foo = 'commonjs exports value'
// 不能在 CommonJS 模块中通过 require 载入 ES Module
// const mod = require('./es-module.mjs')// console.log(mod)

esm.mjs

// ES Module 中可以导入 CommonJS 模块
// import mod from './commonjs.js'// console.log(mod)
// 不能直接提取成员,注意 import 不是解构导出对象
// import { foo } from './commonjs.js'// console.log(foo)
// export const foo = 'es module export value'

与 CommonJS 的差异#

esm.mjs

// ESM 中没有模块全局成员了
// 加载模块函数,模块对象, 导出对象别名// require, module, exports 通过 import 和 export 代替
// 当前文件的绝对路径, 当前文件所在目录// __filename 和 __dirname 通过 import 对象的 meta 属性获取// const currentUrl = import.meta.url
// 通过 url 模块的 fileURLToPath 方法转换为路径import { fileURLToPath } from 'url';import { dirname } from 'path';const __filename = fileURLToPath(import.meta.url);const __dirname = dirname(__filename);

参考文献#