JavaScript常用模块化规范

1.概述

1.1 什么是模块化

  • 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件),并进行组合在一起
  • 块的内部数据/实现是私有的,只是向外部暴露一些接口(方法)与其他外部模块通信

1.2 模块化的好处

  • 避免命名冲突(减少命名空间污染)
  • 更好的分离,按需加载
  • 更高复用性
  • 高可维护性

2.模块化规范

2.1 命名空间模式

命名空间模式是一种将代码组织为对象的方法,其中每个对象都代表一个模块。这种方法可以使代码更易于组织和维护,但它并没有提供真正的模块化支持。

//main.js
var myModule = {
  name: "My Module",
  sayHello: function() {
    console.log("Hello from " + this.name);
  }
};

//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./main.js"></script>
    <script>
      myModule.sayHello(); //Hello from My Module
    </script>
  </body>
</html>

在上面的示例中,我们创建了一个名为myModule的对象,它有一个名为sayHello的方法。这种方法可以将代码组织为对象,使代码更易于组织和维护。

2.2 IIFE

IIFE(Immediately Invoked Function Expression)模式是一种将代码包装在立即调用的函数表达式中的方法。这种方法可以创建私有作用域,使代码更易于维护和重用。

//main.js
var myModule = (function() {
  var name = "My Module";
  function sayHello() {
    console.log("Hello from " + name);
  }
  return {
    sayHello: sayHello
  };
})();

//index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script src="./main.js"></script>
    <script>
      myModule.sayHello(); //Hello from My Module
    </script>
  </body>
</html>

在上面的示例中,我们创建了一个立即调用的函数表达式,它返回一个包含一个名为sayHello的方法的对象。这种方法可以创建私有作用域,使代码更易于维护和重用。

2.3 CommonJS

CommonJS模块是一种使用require()函数来加载模块的方法。这种方法在Node.js中得到广泛应用,但在浏览器中需要使用工具进行转换。 模块的加载是运行时同步加载的。

2.3.1 服务端实现:Node.js

// module1.js
// module.exports = value 暴露一个对象
module.exports = {
  msg: 'module1',
  foo () {
    console.log(this.msg)
  }
}

// module2.js
// 暴露一个函数 module.exports = function(){}
module.exports = function () {
  console.log('module2')
}

// module3.js
// exports.xxx = value
exports.foo = function () {
  console.log('foo() module3')
}
exports.bar= function () {
  console.log('bar() module3')
}

// 将其他模块汇集到主模块
//main.js
let module1 = require("./module1.js");
let module2 = require("./module2.js");
let module3 = require("./module3.js");

module1.foo();
module2();
module3.foo();
module3.bar();

在这里插入图片描述

2.3.2 浏览器端实现:Browserify

Browserify,也称为浏览器端的打包工具(ES6也使用到)。

  • 全局安装npm install browserify -g
  • 项目中安装npm install browserify --save-dev

打包命令:browserify 入口文件 -o 打包文件:
browserify ./src/main.js -o ./dist/build.js
在这里插入图片描述
想要运行在浏览器端,要有一个入口的html1文件。创建index.html,并引入上述打包生成的build.js文件 <script src="./dist/build.js"></script>
在这里插入图片描述

2.4 AMD

专门用于浏览器端,模块的加载是异步的。 这种方法使用define()函数来定义模块,使用require()函数来加载模块。

// module1.js
define(function() {
  var name = "My Module";
  function sayHello() {
    console.log("Hello from " + name);
  }
  return {
    sayHello: sayHello
  };
});

// app.js
require(["module1"], function(myModule) {
  myModule.sayHello();
});

AMD特点:

  • 异步并行加载,不阻塞 DOM 渲染。
  • 推崇依赖前置,也就是提前执行(预执行),在模块使用之前就已经执行完毕。

2.5 CMD

专门用于浏览器端,模块的加载是异步的,模块使用时才会加载执行。 CMD 是通用模块加载,要解决的问题与 AMD 一样,只不过是对依赖模块的执行时机不同 ,推崇就近依赖

具体的使用方法可以上网查询了解了解。定义模块使用全局函数define,接收一个 factory 参数,可以是一个函数,也可以是一个对象或字符串。

2.5.1 factory是函数时

factory是函数时,有三个参数,function(require, exports, module):

  • require:函数用来获取其他模块提供的接口require(模块标识ID)
  • exports:对象用来向外提供模块接口
  • module :对象,存储了与当前模块相关联的属性和方法
// 定义 a.js 模块,同时可引入其他依赖模块,及导出本模块
define(function(require, exports, module) {
  var $ = require('jquery.js')
  exports.price= 200;  
});

2.5.2 factory为对象、字符串时

factory为对象、字符串时,表示模块的接口就是该对象、字符串。比如可以定义一个 JSON 数据模块。

 // 定义 foo.js
 define({"foo": "bar"});

 // 导入使用
define(function(require, exports, module) {

  var obj = require('foo.js')

  console.log(obj)   // {foo: "bar"}
});

2.6 ES6模块

ES6模块是一种模块化规范,是ECMAScript 6标准的一部分。这种方法使用import和export关键字来导入和导出模块。

// module1.js
var name = "My Module";
function sayHello() {
  console.log("Hello from " + name);
}
export { sayHello };

// app.js
import { sayHello } from "./module1";
sayHello();

3.模块化规范总结

3.1 模块化规范总结图

在这里插入图片描述

3.2 AMD与CMD的区别

  • AMD 是提前执行,CMD 是延迟执行。
  • AMD 是依赖前置,CMD 是依赖就近。
// AMD 
define(['./a', './b'], function(a, b) {  // 在定义模块时 就要声明其依赖的模块
    a.doSomething()
    // ....
    b.doSomething()
    // ....
})

// CMD
define(function(require, exports, module) {
   var a = require('./a')
   a.doSomething()
   // ... 

   var b = require('./b') // 可以在用到某个模块时 再去require
   b.doSomething()
   // ... 
})

3.3 CommonJs和ESM的区别

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

现在我们来看第一条,来举个例子:

// a.js
let val = 1;
const setVal = (newVal) => {
  val = newVal
}
module.exports = {
  val,
  setVal
}// b.js
const { val, setVal } = require('./a.js')
console.log(val);// 1​
setVal(101);
console.log(val);// 1

我们可以这样子理解:

const myModule = {
  exports: {}
}
let val = 1;
const setVal = (newVal) => {
  val = newVal
}
​
myModule.exports = {
  val,
  setVal
}//复制解构是浅拷贝
const { val: useVal, setVal: useSetVal } = myModule.exports
console.log(useVal);
useSetVal(101)
console.log(useVal);

这里我们就可以理解什么叫值的拷贝了。我们的val和模块里的val是不一样的所以使用setVal修改没有效果

在es module中就不是输出对象的拷贝了,而是值的引用

// a.js
import { foo } from './b';
console.log(foo);
setTimeout(() => {
  console.log(foo);
  import('./b').then(({ foo }) => {
    console.log(foo);
  });
}, 1000);// b.js
export let foo = 1;
setTimeout(() => {
  foo = 2;
}, 500);
// 执行:babel-node a.js
// 执行结果:
// 1
// 2
// 2

上述场景用CommonJS写:

// a.js
const { foo } = require("./b");
console.log("a:" + foo); //1
setTimeout(() => {
  console.log("a:" + foo); //1
}, 1000);// b.js
let foo = 1;
module.exports = { foo };
setTimeout(() => {
  foo = 2;
  console.log("b:" + foo);
}, 500);

// 执行:node a.js
// 执行结果:
// a:1
// b:2
// a:1

详细情况见我写的文章https://blog.csdn.net/fageaaa/article/details/145796172第13节。

3.4 关于UMD

UMD是AMD和CommonJS的糅合。UMD的实现很简单:

  • 先判断是否支持Node.js模块(exports是否存在),存在则使用Node.js模块模式。
  • 再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
  • 前两个都不存在,则将模块公开到全局(window或global)。
(function (window, factory) {
    if (typeof exports === 'object') {
        module.exports = factory();
    } else if (typeof define === 'function' && define.amd) {
        define([],factory);
    } else {
        window.eventUtil = factory();
    }
})(this, function () {
  return {};
});

严格来说,UMD 并不属于一套模块规范,它主要用来处理 CommonJS、AMD、CMD 的差异兼容,是模块代码能在前面不同的模块环境下都能正常运行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值