
青城派
V1
2023/03/08阅读:16主题:默认主题
前端模块化分享
什么是模块化
-
模块化其实是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。 -
每个模块完成一个特定的子功能(单一职责),所有的模块按某种方法组装起来,成为一个整体,从而完成整个系统所要求的功能。
为什么需要模块化
-
随着 Web 技术的发展,各种交互以及新技术等使网页变得越来越丰富,代码量急速上涨、复杂度在逐步增高,越来越多的业务逻辑和交互都放在 Web 层实现,代码一多,各种命名冲突、代码冗余、文件间依赖变大等等一系列的问题就出来了,甚至导致后期难以维护。
-
在这些问题上,其他如 java、php 等后端语言中早已有了很多实践经验,那就是模块化,因为小的、组织良好的代码远比庞大的代码更易理解和维护,于是前端也开启了模块化历程。
JS 模块化
-
commonJS -
AMD -
CMD -
UMD -
ESM
早期JS模块化方案
-
普通函数
function fn1(){
//...
}
function fn2(){
//...
}
function fn3() {
fn1()
fn2()
}
缺点:如果函数过多,并且在多个文件中,还是无法保证它们不与其它模块发生命名冲突,而且模块成员之间看不出直接关系,还是会给后期的维护造成麻烦。
-
命名空间
var myModule = {
name: "jenny",
getName: function (){
console.log(this.name)
}
}
// 使用
myModule.getName()
缺点:对象内部属性全部会暴露出来,内部状态可以被外部更改
-
立即执行函数(IIFE) -
利用函数闭包的特性来实现私有数据和共享方法 -
通过 myModule.getName() 来获取 name,并且实现 name 属性的私有化
-
var myModule = (function() {
var name = 'zhihu'
function getName() {
console.log(name)
}
return { getName }
})()
CommonJS 规范
// num.js
const a = 1
const b = 2
const add = function (){
return a + b
}
// 导出
module.exports.a = a
module.exports.b = b
module.exports.add = add
// index.js 引用
const num = require('./num.js')
console.log(num.a) // 1
console.log(num.b) // 2
console.log(num.add(num.a, num.b)) // 3
console.log(module)
-
require 命令则负责读取并执行一个 JS 文件,并返回该模块的 exports 对象,没找到的话就抛出一个错误。
-
每个模块内部,都有一个 module 实例,该对象就会有下面几个属性:
-
module.id 模块的识别符,通常是带有绝对路径的模块文件名 -
module.filename 模块的文件名,带有绝对路径 -
module.loaded 返回一个布尔值,表示模块是否已经完成加载 -
module.children 返回一个数组,表示该模块要用到的其他模块 -
module.exports 表示模块对外输出的值
-
Module {
id: '.',
path: '/Users/wanghaixin/work/webpack-demo/src',
exports: {},
filename: '/Users/wanghaixin/work/webpack-demo/src/index.js',
loaded: false,
children: [
Module {
id: '/Users/wanghaixin/work/webpack-demo/src/num.js',
path: '/Users/wanghaixin/work/webpack-demo/src',
exports: [Object],
filename: '/Users/wanghaixin/work/webpack-demo/src/num.js',
loaded: true,
children: [],
paths: [Array]
}
],
paths: [
'/Users/wanghaixin/work/webpack-demo/src/node_modules',
'/Users/wanghaixin/work/webpack-demo/node_modules',
'/Users/wanghaixin/work/node_modules',
'/Users/wanghaixin/node_modules',
'/Users/node_modules',
'/node_modules'
]
}
-
commonJS 特点:
-
所有代码都运行在模块作用域,不会污染全局作用域 -
模块可以多次加载,但是只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果,要想让模块再次运行,必须清除缓存 -
模块加载的顺序,按照其在代码中出现的顺序
-
-
commonJS 简单实现:
let path = require('path');
let fs = require('fs');
let vm = require('vm');
let n = 0
// 构造函数Module
function Module(filename){
this.id = n++; // 唯一ID
this.filename = filename; // 文件的绝对路径
this.exports = {}; // 模块对应的导出结果
}
// 存放可解析的文件模块扩展名
Module._extensions = ['.js'];
// 缓存
Module._cache = {};
// 拼凑成闭包的数组
Module.wrapper = ['(function(exports,require,module){','\r\n})'];
// 没写扩展名,默认添加扩展名
Module._resolveFilename = function (p) {
p = path.join(__dirname, p);
if(!/\.\w+$/.test(p)){
//如果没写扩展名,尝试添加扩展名
for(let i = 0; i < Module._extensions.length; i++){
//拼接出一个路径
let filePath = p + Module._extensions[i];
// 判断文件是否存在
try{
fs.accessSync(filePath);
return filePath;
}catch (e) {
throw new Error('module not found')
}
}
}else {
return p
}
}
// 加载模块本身
Module.prototype.load = function () {
// 解析文件后缀名 isboyjc.js -> .js
let extname = path.extname(this.filename);
// 调用对应后缀文件加载方法
Module._extensions[extname](this);
};
// 后缀名为js的加载方法
Module._extensions['.js'] = function (module) {
// 读文件
let content = fs.readFileSync(module.filename, 'utf8');
// 形成闭包函数字符串 'function(exports,require,module){ test.js文件内容 }'
let script = Module.wrapper[0] + content + Module.wrapper[1];
// 创建沙箱环境,运行并返回结果
let fn = vm.runInThisContext(script);
// 执行闭包函数,将被闭包函数包裹的加载内容
fn.call(module, module.exports, req, module)
};
// 仿require方法, 实现加载模块
function req(path) {
// 根据输入的路径 转换绝对路径
let filename = Module._resolveFilename(path);
// 查看缓存是否存在,存在直接返回缓存
if(Module._cache[filename]){
return Module._cache[filename].exports;
}
// 通过文件名创建一个Module实例
let module = new Module(filename);
// 加载文件,执行对应加载方法
module.load();
// 入缓存
Module._cache[filename] = module;
return module.exports
}
let str = req('./test');
console.log(str);
-
webpack 打包后代码
/*
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
* This devtool is neither made for production nor for readable output files.
* It uses "eval()" calls to create a separate source file in the browser devtools.
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
* or disable the default devtool with "devtool: false".
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
*/
/******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
eval("/* module decorator */ module = __webpack_require__.nmd(module);\nconst num = __webpack_require__(/*! ./num.js */ \"./src/num.js\")\n \nconsole.log(num.a) // 1\nconsole.log(num.b) // 2\nconsole.log(num.add(num.a, num.b)) // 3\n\nconsole.log(module)\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?");
/***/ }),
/***/ "./src/num.js":
/*!********************!*\
!*** ./src/num.js ***!
\********************/
/***/ ((module) => {
eval("const a = 1\nconst b = 2\nconst add = function (){\n return a + b\n}\n\n// 导出\nmodule.exports.a = a\nmodule.exports.b = b\nmodule.exports.add = add\n\n//# sourceURL=webpack://webpack-demo/./src/num.js?");
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ id: moduleId,
/******/ loaded: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/node module decorator */
/******/ (() => {
/******/ __webpack_require__.nmd = (module) => {
/******/ module.paths = [];
/******/ if (!module.children) module.children = [];
/******/ return module;
/******/ };
/******/ })();
/******/
/************************************************************************/
/******/
/******/ // startup
/******/ // Load entry module and return exports
/******/ // This entry module is referenced by other modules so it can't be inlined
/******/ var __webpack_exports__ = __webpack_require__("./src/index.js");
/******/
/******/ })()
;
AMD规范
-
AMD(异步模块定义)是专门为浏览器环境设计的,它定义了一套异步加载标准来解决同步的问题。
作者介绍

青城派
V1