前端模块化中的各种规则及理解

本文介绍了JavaScript模块化的三种主要规则:CommonJS、AMD和ES6模块。CommonJS主要用于服务器端,是同步加载,每个文件视为一个模块。AMD适用于浏览器端,如requireJS,实现异步加载。ES6模块则引入了import和export关键字,提供静态导入和导出。文章还探讨了模块通讯规则和模块加载过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载地址

  • 模块作用域
    • 模块之间不需要考虑全局命名空间冲突的问题
  • 模块之间的通讯规则
    • 首先各个模块之间是相互依赖,相互关联的,例如cpu需要读取内存中的数据来进行计算,然后把计算结果交给了我们的操作系统
    • 既然相互关联,那么模块之间肯定可以相互通讯
    • 模块之间的相互通讯,也就意味着存在输入和输出

模块通讯规则

ES6出现之前就已经出现了js的模块加载规则,最主要的是AMD、CMD、commonjs;而commonjs主要应用于服务器端(nodejs)实现同步加载 AMD主要应用于浏览器端(requirejs)为异步加载 ,同事还有CMD规范,为同步加载规范seajs

commonjs规范

commonjs规定每个文件就是一个模块,有自己的作用域,在一个文件里面定义的变量、函数、类,都是私有的,对其他文件不可见。
require模块导出

commonjs是个同步加载 (所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间)

模块的加载过程
  • 假设a文件夹下有个a.js我们要解析出一个绝对路径来
  • 我们写的路径可以没有后缀名
  • 得到一个加载路径,先看一下缓存中是否有文件存在存在就返回,不存在就创建一个
  • 得到对应的文件内容,加一个闭包,把内容塞进去,之后执行即可

简单实现commonjs

  • 想要实现就要了解都做了什么,首先来分析一下require的逻辑
    • 首先想要从a模块引入b模块一定要拿到b模块,拿到b模块要先拿到b模块的绝对路径
    • 因为require方法中的参数可能为不带扩展名的文件,所以要根据匹配不同的文件后缀名来匹配文件,不同的文件的处理方式不一样

1.首先拿到文件(用到了fs),最后正常情况下要返回这个模块

function require(filename){
    // 1.解析文件路径
    filename = Module._resolveFilename(filename)

    // 返回这个模块
    return module.exports
}

1.1实现路径的获取方法

Module._resolveFilename = function (filename){
    let filePath = path.resolve(__dirname,filename)
    // 看一下这个filePath对应的文件是否存在
    let isExists = fs.existsSync(filePath)
    if(isExists) return filePath;
    // 通过添加后缀名来拼接新路径(先假设有个后缀名的数组对象,因为每个后缀名会对应一个处理这种后缀的方法,所以为一个对象)
    let keys = Reflect.ownKeys(Module._extensions)
    keys.forEach(key=>{
        let newPath = key + filePath
        if(fs.existsSync(newPath)) return newPath;// 匹配路径存在就返回
    })
    throw new Error(Module not found)
}

拿到路径后根据路径看看这个模块是否被缓存,如果没有缓存就创建一个新的模块,如果缓存了就直接返回缓存中的内容。这样是为了防止重复执行模块中的代码

// 先定义个一个缓存对象
let Module._cache = {}
function require(filename){
    // 1.解析文件路径
    filename = Module._resolveFilename(filename)
    if(Module._cache[filename]){// 如果缓存中存在这个模块直接把这个模块的exports返回
        return Module._cache[filename].exports;
    }
    // 如果不存在就创建一个模块,并将这个模块塞到缓存中
    let module = new Module(filename);
    Module._cache[filename] = module

    // 返回这个模块
    return module.exports
}

现在模块已经有了,需要一个创建模块的构造函数,来实现这个构造函数

function Module(filename){
    this.id = filename//文件名称
    this.exports = {} // 输出对象
    this.path = path.dirname(filename);//父路径
}

接下来加载这个模块

let Module._cache = {}
function require(filename){
    // 1.解析文件路径
    filename = Module._resolveFilename(filename)
    if(Module._cache[filename]){// 如果缓存中存在这个模块直接把这个模块的exports返回
        return Module._cache[filename].exports;
    }
    // 如果不存在就创建一个模块,并将这个模块塞到缓存中
    let module = new Module(filename);
    Module._cache[filename] = module
    // 加载模块
    module.load()

    // 返回这个模块
    return module.exports
}
// load 是一个原型上的方法
Module.prototype.load = function(){
    // 通过文件名来找模块的后缀名匹配对应的策略
    let extension = path.extname(this.id)
    Module._extensions[extension](this)
}

接下来编写不同文件的策略

// 先定义一个对象用来放策略
Module._extensions = {}
// 当文件为json文件的时候读取文件内容直接返回
Module._extensions['.json] = function(module){
    let content = fs.readFileSync(module.id,'utf8')
    module.exports = JSON.parse(content)
}
// 写一个wrapper方法
Module.wrapper = function(content){
    return `(function(exports,module,require,__dirname,__filename){${content}})`
}
// 当文件为js文件的时候读取文件内容直接返回
Module._extensions['.js] = function(module){
    let content = fs.readFileSync(module.id,'utf8')
    // js文件不同于json不能直接传输,所以用个闭包包一下(jsonp)
    let str = Module.wrapper(content)
    // 将这个字符串转成函数
    let fn = vm.runInThisContext(str)
    // 在新的文件中释放出这个content
    fn.call(exports,exports,module,require,module.id,module.path)
}

结束

AMD 规范

  • AMD规范不同于commonjs,AMD跑在浏览器端,加载模块需要依赖网络请求,所以不能同步执行,只能异步加载模块

模块的定义语法

define([id], [dependencies], factory)
  • id:可选,字符串类型,定义模块标识,如果没有提供参数,默认为文件名
  • dependencies:可选,字符串数组,AMD 推崇依赖前置,即当前模块依赖的其他模块,模块依赖必须在真正执行具体的factory方法前解决
  • factory:必需,工厂方法,初始化模块需要执行的函数或对象。如果为函数,它只被执行一次。如果是对象,此对象会作为模块的输出值

模块的加载

require([dependencies], function(){})
  • dependencies:字符串数组,该模块的依赖

  • function:Function类型,所依赖的模块都加载成功之后,回调,依赖的模块会以参数的形式传入该函数,从而在回调函数内部就可以使用这些模块

  • 模块的定义种类

    • 无依赖模块
    // base.js
        define(function () {
            return {
                bas: function (sou, tar) {
                    // ...
                }
            }
        })
    
    • 有依赖模块
    // ui.js    数组中可放多个模块
        define(['base'], function (base) {
            return {
                initPage: function () {
                    // ...
                }
            }
        })
    
    • 数据对象模块
    // data.js
    define({
        users: [],
        numbers: []
    })
    
    • 具名模块
    define('index', ['data','base'], function(data, base) {
        // ...
    })
    
    • 包装模块
    define(function(require, exports, module) {
        var base = require('base');
        exports.show = function() {
            // todo with module base
        }
    })
    

es module

参照

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值