简单地聊一聊 JavaScript 模块化(2)

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

  • CommonJS
  • AMD
  • ESM

上一次我们介绍了常用,也是未来 JavaScript 包管理的一个方向,

  • 暴露方式有 exportexport default,命名导出和默认导出
  • 导入方式有import from 方式

这里我们就看到了矛盾。ES module本质上只有命名导出,默认导出只是名为default的命名导出的语法糖。

在 Node 14 中,现在支持旧式的 CommonJS(CJS)和 新式的 ESM 脚本(又名MJS)包管理。CJS 中暴露和导入使用 require()module.exports;ESM 脚本使用 importexport

ESM 和 CJS 是完全不同的动物。从表面上看,ESM 与 CJS 非常相似,但他们的实现却不能再不同了。一个是蜜蜂,另一个是杀人的大黄蜂。

从 ESM 调用 CJS,反之亦然是可以的,但很麻烦。

这里有一些规则,我将在下面详细解释。

  • 不能 require() 方式导入 ESM 格式的脚本;只能导入 ESM 格式脚本,像这样 import {foo} from 'foo'

  • CJS 脚本不能使用像上面这样的静态 import 语句。

  • CJS 脚本可以使用异步的动态 import() 来加载 ESM 模块文件,但与同步的 require 相比,这很麻烦。

  • ESM 模块的文件可以 import CJS 的文件,但只能使用默认导入语法 import _ from 'lodash',不支持命名导入语法 import {shuffle} from 'lodash',如果 CJS 文件在使用命名导出,这就很麻烦了。

  • ESM 可以同 require() 导入 CJS 文件,即使有命名的导出,也尽量避免麻烦,因为需要更多的模板,而且最糟糕的是,像 WebpackRollup这样的打包工具也不知道如何与使用require()的 ESM文件一起工作。

  • CJS 是默认的,如果需要选择加入 ESM 模式。可以通过将你的 JS 文件名称从 .js重命名为.mjs来选择加入 ESM 模式。还需要可以在 package.json 中,设置 "type"。"module",然后就可以通过将 JS 文件从 .js 改成 .cjs 来选择退出 ESM。(甚至可以通过在package.json中加入一行{"type": "module"} package.json文件来进行调整)。)

These rules are painful. Worse, for many users, especially newbies to Node, these rules are incomprehensible. (Fear not, I’ll explain them all here in this article.)

对于许多用户,特别是对那些 Node 的新手,上面这些规则是无法理解的,不过随后给出更多介绍

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div class="container">
        <ul id="tuts">

        </ul>
        <div>
            <input type="text" id="input">
            <button id="submit">添加</button>
        </div>
    </div>
    <script src="tut.js"></script>
    <script src="dom.js"></script>
</body>

</html>
复制代码

tut.js

let tuts = ["machine learning","deep learning","meta learning"];

function getTuts(){
    return tuts;
}
复制代码

dom.js

function addTutsToDom(title){
    const node = document.createElement('li');
    const text = document.createTextNode(title);

    node.appendChild(text);

    document.getElementById("tuts").appendChild(node);
}

document.getElementById("submit")
    .addEventListener("click",function(){
        const input = document.getElementById("input")
        addTutsToDom(input.value)
    })


let list = window.getTuts();
console.log(list)
for(let i = 0; i < list.length; i++){
    addTutsToDom(list[i]);
}
复制代码

屏幕快照 2021-10-05 下午3.27.40.png

这样做定义函数 getTuts 都是全局对象 window 下的方法,所以我们程序都在一个作用域下,程序结构没有层次感。现在主要问题是这些定义变量和方法都是在 window 下,稍不留神就会出现命名冲突的问题。大家可能想我们可以事先将变量和方法定义好,设计好,看是可行但是这样做的隐患可想而知,而且我们协同开发不仅是在团队内部,公司内部,之前提到了 leverage ,这个范围可能是跨区域或者世界范围内的合作,所以我们从本质上来解决而是是否补救,这样成本会很高。

那么我们先定义对象,然后将我们数据都挂载到这个对象下。

let App = {}
复制代码
function domWrapper(){

    function addTutsToDom(title){
        const node = document.createElement('li');
        const text = document.createTextNode(title);
    
        node.appendChild(text);
    
        document.getElementById("tuts").appendChild(node);
    }
    
    document.getElementById("submit")
        .addEventListener("click",function(){
            const input = document.getElementById("input")
            addTutsToDom(input.value)
        })
    
    
    // let list = window.getTuts();
    let list = App.getTuts();
    console.log(list)
    for(let i = 0; i < list.length; i++){
        addTutsToDom(list[i]);
    }
}

domWrapper();
复制代码
function tutsWrapper(){
    let tuts = ["machine learning","deep learning","meta learning"];
    function getTuts(){
        return tuts;
    }

    App.getTuts = getTuts
}

tutsWrapper();
复制代码

不过这样做,tutsWrapperdomWrapper 都还是挂载在 window 这个全局对象,接下来我们来继续解决这个问题,引入立即执行函数,立即执行函数第一次接触就感觉很神奇,回想起当初自己确确实实是一个小白,虽然现在还是一个门外汉,但是回想起一路走来的确不容易。

(function(){

})()
复制代码

好处是我们不需要定义一个 tutsWrapper 函数,用这个函数包裹逻辑,然后调用这个函数。

(function(){
    let tuts = ["machine learning","deep learning","meta learning"];
    function getTuts(){
        return tuts;
    }

    App.getTuts = getTuts
})();
复制代码

CommonJS

我们都知道 Nodejs 就是基于 CommonJs 模块化管理,每一个文件都可以当作一个模块,

  • 服务器端,模块的加载时运行时间同步加载的
  • 浏览器端,浏览器引擎并不支持 require 语法,所以模块需要提前编译打包处理,Browsrify 工具支持

方法

  • 暴露方式 module.exports = value 或者 exports.something = value
  • 引入模块 require(somethingModule)

猜你喜欢

转载自juejin.im/post/7015539243535581197