模块就是将一个复杂的程序依据必定的规则(规范)封装成几个块(文件), 并进行组合在一块儿,块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通讯。node
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规范开发成本高,代码的阅读和书写比较困难,模块定义方式的语义不畅。
例json
//定义有依赖的模块 define(['module1', 'module2'], function(m1, m2){ return 模块 }) // 引入使用模块 require(['module1', 'module2'], function(m1, m2){ 使用m1/m2 })
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 在语言标准的层面上,实现了模块功能,并且实现得至关简单,彻底能够取代 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); }
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; }