青城派

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",
  getNamefunction (){
    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',
  loadedfalse,
  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',
      loadedtrue,
      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(modulemodule.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](modulemodule.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