全面理解JS模块的标准(AMD、CMD、COMMON.JS 、 UMD、ESM)

本文介绍了JavaScript模块规范的发展历程,包括AMD、CMD、CommonJS、UMD和ESM。AMD(异步模块定义)如require.js,CMD(通用模块定义)如sea.js,CommonJS主要在Node.js中使用,UMD为了兼容多种环境,而ESM(ECMAScript模块)成为现代浏览器和Node.js的标准。文章通过示例解释了各种规范的使用方式,并指出目前ESM是最流行的选择。

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

在前端的世界演变里,有着几种JS的模块规范,从出现的顺序来说就是:

①amd
②cmd
③common.js
④ umd
⑤ esm

现在Vue框架里面都是遵守esm规范,不得不说esm是目前最好最流行的一种js规范了

一、amd - 浏览器中的js模块化解决方案
AMD 全称是 Async Module Definition -中文: 异步模块化定义

require.js是AMD模块规范的一个具体实现

核心是通过define方法对无序的代码进行有序的模块化定义,通过require方法实现代码的模块化加载

1、通过define方法定义模块
base.js

define(function (){
    var control = {};
    return control;
});

control.js

define(['jquery', 'jqmd5', 'cookie', 'base'], function (){
    var control = {};
 
    /**
     * 登录状态检测
     */
    control.cookie = function (){
        setTimeout(WK.LC.syncLoginState, 100);
    };
 
    /**
     * 模块调用及配置
     */
    control.template = function (){
        if($('.naver').length > 0) base.naver();
 
        if(CATEGORY == 'login')
        {
            if(MODEL == 'index'){
                // 登录页
                require(['login'], function (Login){
                    Login.form();
                });
            };
 
            if(MODEL == 'register' || MODEL == 'check'){
                // 注册页
                require(['register'], function (Register){
                    Register.form(MODEL);
                });
            };
        };
 
        if(CATEGORY == 'goods')
        {
            // 详情页
            if(MODEL == 'index'){
                require(['detail'], function (Detail){
                    // Detail.form();
                });
            };
        };
    };
 
    return control;
});

2、通过require方法加载模块(异步加载)
注意:参数里面有define声明的模块

require(['control'], function (Control){
    Control.cookie();
    Control.template();
});

二、cmd - 类似amd的用于浏览器中的js模块规范
CMD 全称是 Common Module Definition -中文: 通用模块化定义

sea.js是CMD模块规范的一个具体实现 Sea.js 文档  Seajs github

在定义模块方面, CMD和AMD一样通过define函数来定义模块; 两者的主要区别在于对依赖的加载上, CMD中不需要在define的参数中直接声明需要用到的模块

1、通过define方法定义模块
calculator.js

define('calculator', function(require, exports) {
    // 通过require方法加载其他模块
    var math = require('math');
    exports.add = function(left, right) { return math.add(left, right) };
    exports.subtract = function(left, right) { return math.subtract(left, right) };
})

可以看到calculator模块所的依赖的math模块没有在define函数的参数中进行声明, 而是通过require(‘math’)来引入的

2、使用calculator模块

seajs.use(['calculator'], function(calculator) {
    console.log('1 + 1 = ' + calculator.add(1, 1));
    console.log('2 - 2 = ' + calculator.subtract(2, 1));
})


三、common.js -Node中使用的模块规范

通过exports和module.exports来暴露模块中的内容。
通过require来加载模块。

demo
1、通过exports 暴露模块接口
study.js

var hello = function () {
    console.log('hello studygd.com.');
}
exports.hello = hello;

main.js

const studygd = require('./study');
studygd.hello();


2、通过module.exports 暴露模块接口
定义math模块
math.js

module.exports = {
    add: function(left, right) {
        return left + right;
    },
    subtract: function(left, right) {
        return left - right;
    }
}

使用刚才定义的math模块, 并再定义一个calculator模块
calculator.js

const math = require('./math.js');
module.exports = {
    add: math.add
}


四、umd - 一种同时兼容了amd cmd common.js的规范


amd cmd 通常只能在浏览器中使用, commonjs只能在服务端(Node)**环境下使用, 这样子搞会导致我们基于其中某一种模块规范写的js模块无法在服务端和浏览器端进行复用.

umd解决了这个问题, 它兼容并包, 使得使用此规范写的 js模块既可以在浏览器环境下使用, 也可以在Node(服务端)环境中用

(function (root, factory) {
    if (typeof exports === 'object' && typeof module === 'object')
        // commonjs
        module.exports = factory()
    else if (typeof define === 'function' && define.amd)
        // amd、cmd
        define([], factory)
    else if (typeof exports === 'object')
        // commonjs
        exports['math'] = factory()
    else
        // 全局对象, 浏览器中是 window
        root['math'] = factory()
})(this, function() {
    return { add: function(left, right) { return left + right; } }
})


*其实只要你看过jq源码,你就会觉得 上面的这段代码很熟悉,是的,jq源码里面就是采用了umd规范去做兼容,所以jq可以说是umd规范的一种代表

附:jq源码大体框架

( function( global, factory ) {

    "use strict";

    if ( typeof module === "object" && typeof module.exports === "object" ) {

        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

    //这里编写jquery主体代码...

    // AMD
    if ( typeof define === "function" && define.amd ) {
        define( "jquery", [], function() {
            return jQuery;
        } );
    }

    var
        // Map over jQuery in case of overwrite
        _jQuery = window.jQuery,

        // Map over the $ in case of overwrite
        _$ = window.$;

    jQuery.noConflict = function( deep ) {
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }

        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }

        return jQuery;
    };

    if ( !noGlobal ) {
        window.jQuery = window.$ = jQuery;
    }

    return jQuery;
} );


五、esm - ES6模块规范

使用import导入模块,通过export导出模块

math.js

export { add: (left, right) => left + right; }


在calculator.js导入

import { add } from './math.js';
 
console.log('1 + 1 = ' + add(1, 1));


总结
amd, cmd曾经是个很流行的js模块化产物,记得自己出来工作不久,去面试的时候,也被问过这个两个有什么不一样(上面已经答案),到底使用哪个好等问题,那时自己也是模糊不清,具体回答的不是很清楚,也许是当时那个公司在使用这些规范才考察我,不过现在amd、cmd感觉基本上是没有公司项目在使用了, 现在常用的模块规范一般就是es6模块和commonjs(只用于node)了, node中也已经提供了实验性的es模块支持.

随着微软放弃IE ,现代浏览器对es的import和export的支持也已经很不错了(除了IE其他主流浏览器都支持了)
————————————————
版权声明:本文为优快云博主「春风得意之时」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/u012513962/article/details/124187656

[Vue warn]: Error in v-on handler: "TypeError: Cannot read properties of undefined (reading 'flatMap')" found in ---> <ElButton> at packages/button/src/button.vue <ElDialog> at packages/dialog/src/component.vue <Order> at src/views/user/user/item.vue <AppMain> at src/layout/components/AppMain.vue <Layout> at src/layout/index.vue <App> at src/App.vue <Root> warn @ vue.runtime.esm.js:619 logError @ vue.runtime.esm.js:1884 globalHandleError @ vue.runtime.esm.js:1879 handleError @ vue.runtime.esm.js:1839 invokeWithErrorHandling @ vue.runtime.esm.js:1862 invoker @ vue.runtime.esm.js:2179 invokeWithErrorHandling @ vue.runtime.esm.js:1854 Vue.$emit @ vue.runtime.esm.js:3888 handleClick @ element-ui.common.js:9465 invokeWithErrorHandling @ vue.runtime.esm.js:1854 invoker @ vue.runtime.esm.js:2179 original._wrapper @ vue.runtime.esm.js:6917 vue.runtime.esm.js:1888 TypeError: Cannot read properties of undefined (reading 'flatMap') at VueComponent.orderByUserSku (item.vue:662:1) at click (item.vue:1404:1) at invokeWithErrorHandling (vue.runtime.esm.js:1854:1) at VueComponent.invoker (vue.runtime.esm.js:2179:1) at invokeWithErrorHandling (vue.runtime.esm.js:1854:1) at Vue.$emit (vue.runtime.esm.js:3888:1) at VueComponent.handleClick (element-ui.common.js:9465:1) at invokeWithErrorHandling (vue.runtime.esm.js:1854:1) at HTMLButtonElement.invoker (vue.runtime.esm.js:2179:1) at original._wrapper (vue.runtime.esm.js:6917:1) logError @ vue.runtime.esm.js:1888 globalHandleError @ vue.runtime.esm.js:1879 handleError @ vue.runtime.esm.js:1839 invokeWithErrorHandling @ vue.runtime.esm.js:1862 invoker @ vue.runtime.esm.js:2179 invokeWithErrorHandling @ vue.runtime.esm.js:1854 Vue.$emit @ vue.runtime.esm.js:3888 handleClick @ element-ui.common.js:9465 invokeWithErrorHandling @ vue.runtime.esm.js:1854 invoker @ vue.runtime.esm.js:2179 original._wrapper @ vue.runtime.esm.js:6917
03-26
<think>好的,我现在需要帮用户解决他们遇到的Vue错误。用户之前优化了代码,使用了flatMap,但现在出现了TypeError,提示无法读取未定义的flatMap属性。首先,我要理解这个错误的原因。 看起来错误发生在调用flatMap的时候,说明this.userProdList.array可能是undefined或者不是一个数组。用户之前可能假设this.userProdList.array一定存在并且是数组,但实际情况可能并非如此,尤其是在初始化或者数据未加载完成时。 接下来,我需要回忆之前的优化代码。用户之前将嵌套的forEach循环改成了flatMap、filter和map链式调用。优化后的代码确实更简洁,但如果array不存在,调用flatMap就会出错。 现在的问题是,如何确保array存在并且是一个数组。可能需要添加默认值或者进行类型检查。比如,使用可选链操作符?.或者提供一个空数组作为回退。 然后,考虑到flatMap的浏览器兼容性。虽然现代浏览器支持flatMap,但旧版本或某些环境可能不支持。用户提到错误发生在Vue和Element UI组件中,可能需要检查目标环境是否支持flatMap。如果不支持,需要改用其他方法,比如使用map和concat结合,或者引入polyfill。 另外,用户可能在点击按钮时触发这个错误,说明事件处理函数中有这段代码。需要检查事件触发时,相关数据是否已经正确加载,避免在数据未准备好时执行代码。 总结可能的原因:1. array未定义或不是数组;2. flatMap不被支持;3. 数据未正确初始化。 解决方案可能包括:添加默认值,使用可选链,替换flatMap为兼容写法,检查数据加载时机,或者添加polyfill。 最后,考虑用户的技术水平,需要给出清晰的步骤和示例代码,解释每个修改的原因,并提供不同情况的解决方案,比如兼容性处理和数据校验。</think>这个错误是由于代码中使用了`flatMap`方法,但目标环境(或数据源)不支持该方法导致的。我们可以从以下三个方向分析和解决: --- ### **问题定位分析** 1. **数据源未定义** 错误提示`Cannot read properties of undefined`表明`this.userProdList.array`可能是`undefined` 2. **浏览器兼容性问题** `flatMap`是ES2019新增方法,不支持IE及老版本浏览器 3. **数据初始化问题** 组件初始化时数据尚未加载完成 --- ### **分步解决方案** #### **第一步:确保数据源存在** 使用空数组作为默认值,防止`undefined`导致的错误: ```javascript const distributionSkuList = (this.userProdList?.array || []) .flatMap(element => element.skuList || []) .filter(item => item.skuCount > 0) .map(({ id, skuCount }) => ({ id, skuCount })); ``` *使用可选链操作符`?.`和默认值`|| []`双重保障* #### **第二步:兼容性处理** 如果需支持旧浏览器,改用`reduce`实现: ```javascript const distributionSkuList = (this.userProdList?.array || []).reduce((acc, element) => { const validItems = (element.skuList || []) .filter(item => item.skuCount > 0) .map(({ id, skuCount }) => ({ id, skuCount })); return acc.concat(validItems); }, []); ``` #### **第三步:添加Polyfill(可选)** 在入口文件添加`flatMap`的polyfill: ```javascript // 在main.js或入口文件顶部添加 if (!Array.prototype.flatMap) { Array.prototype.flatMap = function(callback) { return this.reduce((acc, x) => acc.concat(callback(x)), []); }; } ``` --- ### **错误触发链路分析** 根据错误堆栈定位到关键代码位置: ```text at VueComponent.orderByUserSku (item.vue:662:1) at click (item.vue:1404:1) ``` 说明问题出现在`item.vue`文件的662行,与按钮点击事件相关。需检查: 1. 点击事件触发时数据是否已加载完成 2. `this.userProdList`是否在组件初始化时正确赋值 --- ### **防御性编程建议** 1. **数据校验** 在关键节点添加类型校验: ```javascript // 在数据加载逻辑中添加 if (!Array.isArray(this.userProdList?.array)) { console.error('Invalid userProdList data structure'); return; } ``` 2. **异步数据加载处理** 如果数据是异步获取的,使用`v-if`控制按钮可用性: ```vue <el-button :disabled="!isDataLoaded" @click="handleClick" >提交</el-button> ``` --- ### **各方案对比** | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | 默认值+可选链 | 简洁安全 | 不解决兼容性问题 | 现代浏览器环境 | | reduce实现 | 完全兼容 | 代码稍显冗长 | 需要支持旧浏览器的项目 | | Polyfill | 一劳永逸 | 污染全局原型 | 多位置需要flatMap的项目 | 根据项目实际情况选择最适合的解决方案。推荐优先使用`reduce`方案,在保证兼容性的同时保持较好的可读性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值