资讯专栏INFORMATION COLUMN

谈谈Js前端模块化规范

NeverSayNever / 3161人阅读

摘要:依赖全部加载完成后,调用回调函数规范异步加载模块规范和很相似,简单,并与和的规范保持了很大的兼容性在规范中,一个模块就是一个文件。

抛出问题:

在开发中在导入模块时经常使用requireimport

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

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

于是有了菜鸟解惑的搜喽过程。。。。。。
追溯根源,来到Js模块化规范 1、CommonJS规范(同步加载模块)

允许模块通过require方法来同步加载所要依赖的其他模块,然后通过exports或module.exports来导出需要暴露的接口。

使用方式:

// 导入
require("module");
require("../app.js");
// 导出
exports.getStoreInfo = function() {};
module.exports = someValue;

优点:

简单容易使用

服务器端模块便于复用

缺点:

同步加载方式不适合在浏览器环境中使用,同步意味着阻塞加载,浏览器资源是异步加载的

不能非阻塞的并行加载多个模块

为什么浏览器不能使用同步加载,服务端可以?

因为模块都放在服务器端,对于服务端来说模块加载时

而对于浏览器端,因为模块都放在服务器端,加载的时间还取决于网速的快慢等因素,如果需要等很长时间,整个应用就会被阻塞。

因此,浏览器端的模块,不能采用"同步加载"(CommonJs),只能采用"异步加载"(AMD)。

参照CommonJs模块代表node.js的模块系统

AMD(异步加载模块)

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

使用实例:

// 定义
define("module", ["dep1", "dep2"], function(d1, d2) {...});
// 加载模块
require(["module", "../app"], function(module, app) {...});

加载模块require([module], callback);第一个参数[module],是一个数组,里面的成员就是要加载的模块;第二个参数callback是加载成功之后的回调函。

优点:

适合在浏览器环境中异步加载模块

可以并行加载多个模块

缺点:

提高了开发成本,代码的阅读和书写比较困难,模块定义方式的语义不顺畅

不符合通用的模块化思维方式,是一种妥协的实现

实现AMD规范代表require.js

RequireJS对模块的态度是预执行。由于 RequireJS 是执行的 AMD 规范, 因此所有的依赖模块都是先执行;即RequireJS是预先把依赖的模块执行,相当于把require提前了

RequireJS执行流程:

require函数检查依赖的模块,根据配置文件,获取js文件的实际路径

根据js文件实际路径,在dom中插入script节点,并绑定onload事件来获取该模块加载完成的通知。

依赖script全部加载完成后,调用回调函数

CMD规范(异步加载模块)

CMD规范和AMD很相似,简单,并与CommonJS和Node.js的 Modules 规范保持了很大的兼容性;在CMD规范中,一个模块就是一个文件。

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

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

require 是一个方法,接受模块标识作为唯一参数,用来获取其他模块提供的接口:require(id)

exports 是一个对象,用来向外提供模块接口

module 是一个对象,上面存储了与当前模块相关联的一些属性和方法

实例:

define(function(require, exports, module) {
  var a = require("./a");
  a.doSomething();
  // 依赖就近书写,什么时候用到什么时候引入
  var b = require("./b");
  b.doSomething();
});

优点:

依赖就近,延迟执行

可以很容易在 Node.js 中运行

缺点:

依赖 SPM 打包,模块的加载逻辑偏重

实现代表库sea.js:SeaJS对模块的态度是懒执行, SeaJS只会在真正需要使用(依赖)模块时才执行该模块

AMD 与 CMD 的区别

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从2.0开始,也改成了可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

AMD推崇依赖前置;CMD推崇依赖就近,只有在用到某个模块的时候再去require。

// AMD
define(["./a", "./b"], function(a, b) {  // 依赖必须一开始就写好  
   a.doSomething()    
   // 此处略去 100 行    
   b.doSomething()    
   ...
});
// CMD
define(function(require, exports, module) {
   var a = require("./a")   
   a.doSomething()   
   // 此处略去 100 行   
   var b = require("./b") 
   // 依赖可以就近书写   
   b.doSomething()
   // ... 
});
UMD

UMD是AMD和CommonJS的糅合

AMD 以浏览器第一原则发展异步加载模块。

CommonJS 模块以服务器第一原则发展,选择同步加载,它的模块无需包装。

UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式;在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

(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 () {
    //module ...
});
ES6模块化

ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块设计思想:尽量的静态化、使得编译时就能确定模块的依赖关系,以及输入和输出的变量(CommonJS和AMD模块,都只能在运行时确定这些东西)。

使用方式:

// 导入
import "/app";
import React from “react”;
import { Component } from “react”;
// 导出
export function multiply() {...};
export var year = 2018;
export default ...
...

优点:

容易进行静态分析

面向未来的 EcmaScript 标准

缺点:

原生浏览器端还没有实现该标准

全新的命令字,新版的 Node.js才支持。

回到问题“require与import的区别”

require使用与CommonJs规范,import使用于Es6模块规范;所以两者的区别实质是两种规范的区别;

CommonJS:

对于基本数据类型,属于复制。即会被模块缓存;同时,在另一个模块可以对该模块输出的变量重新赋值。

对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。

当使用require命令加载某个模块时,就会运行整个模块的代码。

当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。

ES6模块

ES6模块中的值属于【动态只读引用】。

对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。

对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。

循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。

最后:require/exports 是必要通用且必须的;因为事实上,目前你编写的 import/export 最终都是编译为 require/exports 来执行的。

参考

AMD与CMD规范详解

CommonJS模块和ES6模块的区别

Js模块规范

理解Js模块化

Require与import的区别

“积跬步、行千里”—— 不时的分享更新中~,喜欢留下个赞哦!

往期经典:

团队合作必备的Git操作

一个页面从输入URL到加载显示完成,发生了什么?

相关好文推荐:

网络篇—浏览器缓存(一)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/96894.html

相关文章

  • 谈谈Js前端块化规范

    摘要:依赖全部加载完成后,调用回调函数规范异步加载模块规范和很相似,简单,并与和的规范保持了很大的兼容性在规范中,一个模块就是一个文件。 抛出问题: 在开发中在导入模块时经常使用require和import; 导出模块时使用module.exports/exports或者export/export default; 有时候为了引用一个模块会使用require奇怪的是也可以使用import?...

    Steve_Wang_ 评论0 收藏0
  • 谈谈前端工程化 js加载

    摘要:当年的加载在没有前端工程化之前,基本上是我们是代码一把梭,把所需要的库和自己的代码堆砌在一起,然后自上往下的引用就可以了。而且对于前后端的技术要求较高,所以对于项目未必是最有效的方案。 当年的 js 加载 在没有 前端工程化之前,基本上是我们是代码一把梭,把所需要的库和自己的代码堆砌在一起,然后自上往下的引用就可以了。 那个时代我们没有公用的cdn,也没有什么特别好的方法来优化加载j...

    paulli3 评论0 收藏0
  • 前端资源系列(4)-前端学习资源分享&前端面试资源汇总

    摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...

    princekin 评论0 收藏0
  • 前端面试之Js

    摘要:作为构造函数使用,绑定到新创建的对象。内部实现类和类的继承构造函数构造函数调用父类构造函数参考请尽可能详尽的解释的工作原理的原理简单来说通过对象来向服务器发异步请求,从服务器获得数据,然后用来操作而更新页面。 1 . 请解释事件代理 (event delegation) 当需要对很多元素添加事件的时,可以通过将事件添加到它们的父节点通过委托来触发处理函数。其中利用到了浏览器的事件冒泡机...

    anyway 评论0 收藏0

发表评论

0条评论

NeverSayNever

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<