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);
    });
​
})();
​
相关推荐
web Rookie26 分钟前
JS类型检测大全:从零基础到高级应用
开发语言·前端·javascript
Au_ust34 分钟前
css:基础
前端·css
帅帅哥的兜兜34 分钟前
css基础:底部固定,导航栏浮动在顶部
前端·css·css3
yi碗汤园37 分钟前
【一文了解】C#基础-集合
开发语言·前端·unity·c#
就是个名称38 分钟前
购物车-多元素组合动画css
前端·css
编程一生1 小时前
回调数据丢了?
运维·服务器·前端
丶21361 小时前
【鉴权】深入了解 Cookie:Web 开发中的客户端存储小数据
前端·安全·web
Missmiaomiao2 小时前
npm install慢
前端·npm·node.js
程序员爱技术4 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会5 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器