2023.30 amd umd iife cjs es有啥区别?

大家好,我是wo不是黄蓉,今年学习目标从源码共读开始,希望能跟着若川大佬学习源码的思路学到更多的东西。

网上很多关于模块化的资料,但是每次提起还有遇到package.json中的打包配置还是理不清,那些之前搞不懂的知识点迟早还是要还的,每次学习也是一个查漏补缺的过程。加油!!!

理完这些大概理解,为什么会有webpackrollup,vite这些打包工具了,模块是大佬们为了解决前期代码分割问题提出的解决方案。前期是通过拆分一个js到多个文件实现的模块化,但是文件多了之后无法保证命名问题,大佬们就剔除了模块化的方案。

模块化的好处:

  • 解决命名冲突问题
  • 解决变量查找作用域的层级,减少性能消耗
  • 代码逻辑更清晰,将功能和模块化绑定更易维护
  • 利用模块化还能更方便的对现有库进行二次封装不会影响原来库

打包源代码 index.js

javascript 复制代码
import { add } from './func.mjs'
console.log('hello world')
​
function sayHi() {
    let name = 'jane'
​
    console.log(name + 'hello world', add(1, 2))
}
​
sayHi()
​
window.addEventListener('error', function (env) {
    console.log(env)
})
​

func.mjs

css 复制代码
export function add(a, b) {
    return a + b
}

使用rollup打包结果

amd 异步模块定义

异步模块定义,采用异步方式加载模块,类似于回调函数 。使用define定义模块,不支持原生Js,需要借助库来使用。

使用参考这边的例子,为什么需要在script标签中加一个data-main属性,只有在使用require.js的时候才这么用,表示入口文件是哪个。

适合在浏览器环境中异步加载模块,可以并行加载多个模块

打包工具配置打包格式可以将所有依赖项打包成一个文件,生成一个入口文件,因此查看打包好的配置为umd格式的文件时看不到后面几个参数,只有第一个参数,就是一个函数。

第一个参数:模块名字

第二个参数:模块依赖项数组

第三个参数:函数的参数与前面依赖项一一对应,每一项分别为依赖项模块的导出成员。

javascript 复制代码
define((function () { 'use strict';
​
    function add(a, b) {
        return a + b
    }
​
    console.log('hello world');
​
    function sayHi() {
        let name = 'jane';
​
        console.log(name + 'hello world', add(1, 2));
    }
​
    sayHi();
​
    window.addEventListener('error', function (env) {
        console.log(env);
    });
​
}));
​

umd通用模块化规范

amd 和commonjs的结合,判断是否支持node.js模块,然后使用Node.js模块模式,再判断是否支持AMD,使用AMD加载模块

umd源码范式推演,可以参考这篇文章

通过传参方式导出一个模块,Umd功能根据使用要求生产模块,它的职责定位是模块工厂,我们定义一个factory方法,每当执行时,返回一个模块

javascript 复制代码
(function(factory){
    //通过形参访问工厂方法,如果不着地那个挂载对象,就默认挂载到全局对象
    window.attr = factory()
})(function(){})

指定挂载对象

javascript 复制代码
(function(root,factory){
    root.attr = factory();
}(self !== undefined ? self : this, function(){
​
}));

适配amd

javascript 复制代码
(function(factory){
    root.attr = factory();
    if(typeof define === 'function' && define.amd){
        define(factory)
    }
}(self !== undefined ? self : this, function(){
​
}));

适配commonjs

javascript 复制代码
(function(factory){
    root.attr = factory();
    if(typeof exports === 'object' && typeof define !== 'function'){
        
        module.exports = factory()
    }
}(self !== undefined ? self : this, function(){
​
}));

cjs commonjs

cjs语法基本和js一致,只不过是运行在node环境的代码,node也支持js语法,官网上介绍说Node.js是一个开源、跨平台的 JavaScript 运行时环境 ,本身更是支持文件读取、http请求服务等操作,比起js只能操作浏览器对象,node可一操作一些操作系统层面的东西。

关于npm配置在node和浏览器环境配区别问题,在es modules章节讲

javascript 复制代码
'use strict';
​
function add(a, b) {
    return a + b
}
​
console.log('hello world');
​
function sayHi() {
    let name = 'jane';
​
    console.log(name + 'hello world', add(1, 2));
}
​
sayHi();
​
window.addEventListener('error', function (env) {
    console.log(env);
});
​

es

可以用编译阶段就确定模块的依赖关系,commonjs和amd模块,只能在运行时确定。

现在浏览器已经支持用script标签引入模块或脚本,需要在script中添加type="module",这样就可以解析import/export语法

打包类型配置为es也就是esModulejs,就不做过多介绍了。

以下属性配置是一些非标准的属性,在npm官网package.json配置项中找不到

ruby 复制代码
{
    "type":"",//声明npm包遵循的模块化规范,默认:commonjs,可设置值:commonjs/module
    "module":"",//如果npm包导出的是ESM规范的包,可以使用module来定义入口文件     
}

如果在npm package.json中设置type:'module'

例:修改type=module后表示使用es module规范

如果在npm package.json中设置module:'index.mjs'表示定义npm包的ESM规范的入口文件

main字段,定义入口文件,客户端和服务端都可以使用

browser字段,官网上描述,如果是在客户端使用,则为了更加语义化应该使用browser字段代替main字段

如果package.json中同时指定了main、module、browser这三个字段,则有优先级

package.json中Browser modules main字段优先级对比

大致理解就是会先判断是否有module,因为module设置了就表示使用es module规范

如果有module就判断browser main的配置其实兜底逻辑

涉及到的文件加载顺序问题

文件加载优先级mjs > js mjs设置为.mjs结尾的文件可以在node环境下原生执行ESM规范的脚本文件

npm包怎么根据在不同环境下加载npm包不同的入口文件?

  • 使用process对象检测
arduino 复制代码
if (typeof process !== 'undefined') {
    console.log('运行环境是Node.js')
} else {
    console.log('运行环境不是Node.js')
}
  • 使用window对象检测
javascript 复制代码
if (typeof window !== 'undefined') {
    console.log('运行环境是浏览器')
} else {
    console.log('运行环境不是浏览器')
}

打包结果

javascript 复制代码
function add(a, b) {
    return a + b
}
​
console.log('hello world');
​
function sayHi() {
    let name = 'jane';
​
    console.log(name + 'hello world', add(1, 2));
}
​
sayHi();
​
window.addEventListener('error', function (env) {
    console.log(env);
});
​

iife立即执行函数

立即执行函数,可以利用立即执行函数创建闭包解决变量作用域问题

还可以利用立即执行函数避免全局变量命名冲突问题

因为创建了一个独立的作用域,因此变量查找作用域时可以减少对作用域的查找

javascript 复制代码
(function () {
    'use strict';
​
    function add(a, b) {
        return a + b
    }
​
    console.log('hello world');
​
    function sayHi() {
        let name = 'jane';
​
        console.log(name + 'hello world', add(1, 2));
    }
​
    sayHi();
​
    window.addEventListener('error', function (env) {
        console.log(env);
    });
​
})();
​
相关推荐
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte3 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc