一文彻底搞懂JS前端5大模块化规范及其区别
小职 2021-03-31 来源 :博客园 阅读 503 评论 0

摘要:本文主要介绍了web前端Javascript框架5大模块化规范及其区别,通过具体的内容向大家展现,希望对大家Javascript语言的学习有所帮助。

本文主要介绍了web前端Javascript框架5大模块化规范及其区别,通过具体的内容向大家展现,希望对大家Javascript语言的学习有所帮助。

一文彻底搞懂JS前端5大模块化规范及其区别

目录

码文不易,转载请带上本文链接,感谢~ https://www.cnblogs.com/echoyya/p/14577243.html

CommonJS规范(同步加载模块)

AMD(Asynchronous Module Definition)

CMD(Common Module Definition)

UMD(Universal Module Definition)

问题回归:"require"与"import"的区别

在开发以及面试中,总是会遇到有关模块化相关的问题,始终不是很明白,不得要领,例如以下问题,回答起来也是模棱两可,希望通过这篇文章,能够让大家了解十之一二,首先抛出问题:

 

导出模块时使用 module.exports/exports 或者 export/export default ;

有时加载一个模块会使用 require 奇怪的是也可以使用 import ??它们之间有何区别呢?

于是有了菜鸟解惑的搜喽过程。。。。。。

 

模块化规范:即为 JavaScript 提供一种模块编写、模块依赖和模块运行的方案。

 

Script 标签

其实最原始的 JavaScript 文件加载方式,就是Script 标签,如果把每一个文件看做是一个模块,那么他们的接口通常是暴露在全局作用域下,也就是定义在 window 对象中,不同模块的接口调用都是一个作用域中,一些复杂的框架,会使用命名空间的概念来组织这些模块的接口。

 

缺点:

 

污染全局作用域

开发人员必须主观解决模块和代码库的依赖关系

文件只能按照script标签的书写顺序进行加载

在大型项目中各种资源难以管理,长期积累的问题导致代码库混乱不堪

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到 <script> 标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。

 

如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器 允许脚本异步加载 。

 

<script src="path/to/myModule.js" defer></script>

<script src="path/to/myModule.js" async></script>

<script> 标签添加 defer 或 async 属性,脚本就会 异步加载 。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。

 

defer :要等到整个页面在内存中正常渲染结束,才会执行;多个脚本时,按顺序执行

 

async :一旦下载完,渲染引擎就会中断渲染,执行这个脚本再继续渲染。多个脚本时,不能保证按执行顺序

 

总结一句话: defer 是“ 渲染完再执行 ”, async 是“ 下载完就执行 ”。

 

CommonJS规范(同步加载模块)

Node.js 是目前 CommonJS 规范最热门的一个实现

通过 require 方法 同步加载 所依赖的模块,通过 exports 或 module.exports 导出需要暴露的数据。

CommonJS 规范包括了模块(modules)、包(packages)、系统(system)、二进制(binary)、控制台(console)、编码(encodings)、文件系统(filesystems)、套接字(sockets)、单元测试(unit testing)等部分。

创建模块

在 Node.js 中,创建一个模块非常简单, 一个文件就是一个模块

 

// module.js 模块

var name = "Echoyya";

 

// todo something...

exports.name = name

加载模块

使用require函数 加载模块(即被依赖模块的 module.exports对象)。

 

按路径加载模块

通过查找 node_modules 目录加载模块

加载缓存:Node.js 是根据实际文件名缓存,而不是 require() 提供的参数缓存的,如 require('express') 和 require('./node_modules/express') 加载两次,也不会重复加载,尽管两次参数不同,解析到的文件却是同一个。

核心模块拥有最高的加载优先级,换言之如果有模块与其命名冲突,Node.js 总是会加载核心模块。

导出模块

exports.属性 = 值

exports.方法 = 函数

 

Node.js 为每个模块提供一个 exports 变量,指向 module.exports。相对于在每个模块头部,有一行这样的命令: var exports = module.exports;

exports 对象 和 module.exports 对象,指同一个内存空间, module.exports对象才是真正的 暴露对象

而 exports对象 是 module.exports对象的引用 ,不能改变指向,只能添加属性和方法,若直接改变exports 的指向,等于切断了 exports 与 module.exports 的联系,返回空对象

console.log(module.exports === exports); // true

另外的用法:

 

// singleobjct.js

 

function Hello() {

    var name;

    this.setName = function (thyName) {

        name = thyName;

    };

    this.sayHello = function () {

        console.log('Hello ' + name);

    };

}

 

exports.Hello = Hello;

此时获取 Hello 对象 require('./singleobject').Hello ,略显冗余,可以用下面方法简化。

 

// hello.js

function Hello() {

  var name;

  this.setName = function(thyName) {

    name = thyName;

  };

  this.sayHello = function() {

    console.log('Hello ' + name);

  };

}

module.exports = Hello;

就可以直接获得这个对象:

 

// gethello.js

var Hello = require('./hello');

hello = new Hello();

hello.setName('Yu');

hello.sayHello();

CommonJS 特点

同步加载 方式,适用于服务端,因为模块都放在服务器端,对于服务端来说模块加载较快,不适合在浏览器环境中使用,因为同步意味着阻塞加载。

所有代码都运行在模块作用域,不会污染全局作用域。

模块可以多次加载,但只会在第一次加载时运行一次,然后运行结果就被缓存了,以后再加载,就直接读取缓存结果。

模块加载的顺序,按照其在代码中出现的顺序。

AMD(Asynchronous Module Definition)

采用 异步方式 加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 推崇依赖前置

 

require.js 是目前 AMD 规范最热门的一个实现

 

AMD 也采用 require 语句加载模块,但是不同于 CommonJS,它要求两个参数: require([module], callback);

 

[module]:是一个数组,成员就是要加载的模块

 

callback:加载成功之后的回调函数;

 

require(['math'], function (math) {

  math.add(2, 3);

});

创建模块

模块必须采用 define() 函数来定义。

 

若一个模块不依赖其他模块。可以直接定义在 define() 函数中。

// math.js

 

define(function (){

 var add = function (x,y){

return x+y;

 };

 return {

add: add

 };

});

若这个模块还依赖其他模块,那么 define() 函数的第一个参数,必须是一个数组,指明该模块的依赖性。当 require() 函数加载test模块时,就会先加载 myLib.js 模块。

// test.js

 

define(['myLib'], function(myLib){

 function foo(){

myLib.doSomething();

 }

 return {

foo : foo

 };

});

加载规范模块

// main.js

 

require(['math'], function (math){

 alert(math.add(1,1));

});

加载非规范的模块

理论上 require.js 加载的模块,必须是按照 AMD 规范 用 define() 函数定义的模块。但实际上,虽然已经有一部分流行的函数库(比如 jQuery )符合 AMD 规范,更多的库并不符合。那么require.js 如何能够加载非规范的模块呢?

 

这样的模块在用 require() 加载之前,要先用 require.config() 方法,定义它们的一些特征。

 

例如,underscore 和 backbone 这两个库,都没有采用 AMD 规范编写。如果要加载的话,必须先定义它们的特征。

 

require.config({

 shim: {

'underscore': {

 exports: '_'

},

'backbone': {

 deps: ['underscore', 'jquery'],

 exports: 'Backbone'

   }

 }

});

require.config()接受一个配置对象,这个对象有一个 shim 属性,专门用来配置不兼容的模块。每个模块要定义:

 

exports :输出的变量名,表示这个模块外部调用时的名称;

 

deps: 数组,表示该模块的依赖性。

 

如jQuery 的插件还可以这样定义:

 

shim: {

 'jquery.scroll': {

deps: ['jquery'],

exports: 'jQuery.fn.scroll'

 }

}

AMD特点

AMD允许输出的模块兼容CommonJS

异步并行加载,不阻塞 DOM 渲染。

推崇依赖前置 ,也就是提前执行(预执行),在模块使用之前就已经执行完毕。

CMD(Common Module Definition)

CMD 是通用模块加载,要解决的问题与 AMD 一样,只不过是对依赖模块的执行时机不同 , 推崇就近依赖 。

sea.js 是 CMD 规范的一个实现代表库

定义模块使用全局函数 define ,接收一个 factory 参数,可以是一个函数,也可以是一个对象或字符串;

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

 

require :函数用来获取其他模块提供的接口 require(模块标识ID)

exports : 对象用来向外提供模块接口

module :对象,存储了与当前模块相关联的属性和方法

// 定义 a.js 模块

define(function(require, exports, module) {

 

  var $ = require('jquery.js')

  

  exports.price= 200;   

});

 

// b.js 加载模块

const a = require('./a.js')

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

 

define({"foo": "bar"});

3. 通过字符串模板定义模块:

 

define('I am a template.My name is {{name}}.');

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()

   // ...  

})

UMD(Universal Module Definition)

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 {};

});

ES6模块化

ES6 模块的设计思想,是尽量的静态化,使得 编译时 就能确定模块的依赖关系,以及输入和输出的变量。

 

ES6 中, import 引用模块,使用 export 导出模块。通过 babel 项目将还未被宿主环境(各浏览器、Node.js)直接支持的 ES6 模块 编译为 ES5 的 CommonJS。因此 Babel 实际上是将不被支持的 import/export 翻译成目前已被支持的 require/exports 。

 

// 导入

import Vue from 'vue'

import App from './App'

 

 

// 导出

function v1() { ... }

function v2() { ... }

 

export {

  v1 as streamV1,

  v2 as streamV2,

  v2 as streamLatestVersion

};

export function multiply() {...};

export var year = 2018;

export default ...

模块化规范大总结

一文彻底搞懂JS前端5大模块化规范及其区别


问题回归:"require"与"import"的区别

说了这么多,还是要回到文章一开始提到的问题,"require"与"import"两种引入模块方式,到底有神马区别,大致可以分为以下几个方面(可能总结的也不是很全面):

 

写法上的区别

require/exports 的用法只有以下三种简单的写法:

 

const fs = require('fs')

exports.fs = fs

module.exports = fs

import/export 的写法就多种多样:

 

import fs from 'fs'

import {default as fs} from 'fs'

import * as fs from 'fs'

import {readFile} from 'fs'

import {readFile as read} from 'fs'

import fs, {readFile} from 'fs'

 

export default fs

export const fs

export function readFile

export {readFile, read}

export * from 'fs'

输入值的区别

require 输入的变量,基本类型数据是赋值,引用类型为浅拷贝,可修改

 

import 输入的变量都是只读的,如果输入 a 是一个对象,允许改写对象属性。

 

import {a} from './xxx.js'

 

a = {}; // Syntax Error : 'a' is read-only;

 

a.foo = 'hello'; // 合法操作

执行顺序

require :不具有提升效果,到底加载哪一个模块,只有运行时才知道。

 

const path = './' + fileName;

const myModual = require(path);

import :具有提升效果,会提升到整个模块的头部,首先执行。 import 的执行早于 foo 的调用。本质就是 import 命令是编译阶段执行的,在代码运行之前。

 

foo();

 

import { foo } from 'my_module';

import() 函数:ES2020提案引入,支持动态加载模块。 import() 函数接受一个参数,指定所要加载的模块的位置,参数格式同 import 命令,两者区别主要是 import() 为动态加载。可用于 按需加载 、 条件加载 、 动态的模块路径 等。

 

它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块,返回一个 Promise 对象。 import() 加载模块成功以后,该模块会作为一个对象,当作 then 方法的参数。可以使用对象解构赋值,获取输出接口。

 

// 按需加载

button.addEventListener('click', event => {

  import('./dialogBox.js')

  .then({export1, export2} => {   // export1和export2都是dialogBox.js的输出接口,解构获得

    // do something...

  })

  .catch(error => {})

});

 

// 条件加载

if (condition) {

  import('moduleA').then(...);

} else {

  import('moduleB').then(...);

}

 

 

// 动态的模块路径

import(f()).then(...);    // 根据函数f的返回结果,加载不同的模块。

使用表达式和变量

require :很显然是可以使用表达式和变量的

 

let a = require('./a.js')

a.add()

 

let b = require('./b.js')

b.getSum()

import 静态执行,不能使用表达式和变量,因为这些都是只有在运行时才能得到结果的语法结构。

 

// 报错

import { 'f' + 'oo' } from 'my_module';

 

// 报错

let module = 'my_module';

import { foo } from module;

 

// 报错

if (x === 1) {

  import { foo } from 'module1';

} else {

  import { foo } from 'module2';

}

而require/exports 和 import/export 本质上的区别,实际上也就是CommonJS规范与ES6模块化的区别

 

它们有三个重大差异。

 

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

 

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

 

CommonJS 模块的 require() 是 同步 加载模块,ES6 模块的 import 命令是 异步 加载,有一个独立的模块依赖的解析阶段。

 

导致第二个差异是因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成

 

CommonJS: 运行时加载

 

只能在运行时确定模块的依赖关系,以及输入和输出的变量,一个模块就是一个对象,输入时必须查找对象属性。

// CommonJS模块

let { stat, exists, readfile } = require('fs');

 

// 等同于

let _fs = require('fs');

let stat = _fs.stat;

let exists = _fs.exists;

let readfile = _fs.readfile;

ES6: 编译时加载 或者静态加载

 

ES6 模块不是对象 ,而是通过 export 命令显式指定输出的代码,再通过 import 命令输入。

可以在编译时就完成模块加载,引用时只加载需要的方法,其他方法不加载。效率要比 CommonJS 模块的加载方式高。

import { stat, exists, readFile } from 'fs';


我是小职,记得找我

✅ 解锁高薪工作

✅ 免费获取学习教程,开发工具,代码大全,参考书籍

一文彻底搞懂JS前端5大模块化规范及其区别

本文由 @小职 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程