js模块化以及手撕commonjs代码

4次阅读

共计 3438 个字符,预计需要花费 9 分钟才能阅读完成。

js 语言模块化

模块就是将一个复杂的程序依据一定的规则 (规范) 封装成几个块 (文件), 并进行组合在一起,块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法) 与外部其它模块通信。

模块化的好处

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离, 按需加载
  • 更高复用性
  • 高可维护性

模块化规范

commonjs

CommonJS 规范主要用于服务端编程,加载模块是同步的,这并不适合在浏览器环境
例:

//module1.js
module.exports = {
  msg: 'module1',
  foo() {console.log(this.msg)
  }
}

// app.js 文件
// 引入 module1 模块
let module1 = require('./modules/module1')
module1.foo() //module1

AMD 规范

AMD 规范在浏览器环境中异步加载模块,而且可以并行加载多个模块。不过,AMD 规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不顺畅。

// 定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){return 模块})

// 引入使用模块
require(['module1', 'module2'], function(m1, m2){使用 m1/m2})

CMD 规范

CMD 规范与 AMD 规范很相似,都用于浏览器编程,依赖就近,延迟执行,可以很容易在 Node.js 中运行。不过,依赖 SPM 打包,模块的加载逻辑偏重

// 定义有依赖的模块
define(function(require, exports, module){// 引入依赖模块(同步)
  var module2 = require('./module2')
  // 引入依赖模块(异步)
    require.async('./module3', function (m3) {})
  // 暴露模块
  exports.xxx = value
})
define(function (require) {var m1 = require('./module1')
  var m4 = require('./module4')
  m1.show()
  m4.show()})

ES6 模块

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案 **。

/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {return a + b;};
export {basicNum, add};
/** 引用模块 **/
import {basicNum, add} from './math';
function test(ele) {ele.textContent = add(99 + basicNum);
}

实现 commonjs

const vm = require('vm')
const fs = require('fs')
const path = require('path')
function Module (id) {
    this.id = id
    this.exports = {}}
Module.wrap = function (script) {let wrapper = ['(function(exports,require,module,dirname,filename){', '})'];
    return wrapper[0] + script + wrapper[1]
}
Module._resolveFilename = function(filename) {
    // 1. 先查看你传入的文件是否是正常的, 存不存在如果存在 就加载这个文件
    // 2. 如果文件不存在,尝试添加.js .json 后缀
    let filePath = path.resolve(__dirname, filename);
    let exists = fs.existsSync(filePath);
    if (exists) return filePath;
    let keys = Object.keys(Module._extensions)
    for (let i = 0; i < keys.length; i++) {let concatPath = filePath + keys[i]; // 尝试添加后缀
        if (fs.existsSync(concatPath)) return concatPath
    }
    throw new Error('module not found');
}
Module._extensions = {}
Module._extensions['.js'] = function(module) {let content = fs.readFileSync(module.id, 'utf8');
    let wrapperStr = Module.wrap(content); // 要让内容包装函数(模块化的解决方案就是包装一层函数)let fn = vm.runInThisContext(wrapperStr);
    let thisValue = module.exports; // {}
    // module 里有一个属性 等于 exports
    // module.exports 和 exports
    // *****************************************
    // let exports = module.exports = {}
    // exports.a = 'hello'
    // return module.exports
    // ***********************************
    console.log(fn.toString())
    fn.call(thisValue,module.exports,req,module,path.dirname(module.id),module.id);
}
Module._extensions['.json'] = function(module) {let content = fs.readFileSync(module.id, 'utf8');
    // 手动将 json 的结果赋予给 module.exports 属性
    module.exports = JSON.parse(content)
}
// 自定义模块查找的问题 如果没有这个文件,我需要尝试去加载 .js 后缀再去尝试加载.json / .node
Module._resolveFilename = function(filename) {
    // 1. 先查看你传入的文件是否是正常的, 存不存在如果存在 就加载这个文件
    // 2. 如果文件不存在,尝试添加.js .json 后缀
    let filePath = path.resolve(__dirname, filename);
    let exists = fs.existsSync(filePath);
    if (exists) return filePath;
    let keys = Object.keys(Module._extensions)
    for (let i = 0; i < keys.length; i++) {let concatPath = filePath + keys[i]; // 尝试添加后缀
        if (fs.existsSync(concatPath)) return concatPath
    }
    throw new Error('module not found');
}
Module.prototype.load = function() {
    // 我要加载当前的模块  当前模块就是 this
    let extname = path.extname(this.id);
    Module._extensions[extname](this); // 设计模式 策略模式
}
Module._cache = {}
function req (id) {const filename = Module._resolveFilename(id); // 转化成绝对路径
    // 模块有没有被缓存..
    if(Module._cache[filename]){ // 多次引用 返回原来的 exports 对象
        return Module._cache[filename].exports;
    }
    let module = new Module(filename); // 根据路径来创建模块
    // 缓存模块
    module.load(); // 核心就是加载这个模块 让模块中的内容执行,赋予给 module.exports
    // 最终返回的是 module.exports
    Module._cache[filename] = module
    return module.exports;
}
正文完
 0