当前主流 JS 模块化方案

  • 无模块化

  • CommonJS 规范,nodejs 实现的规范

  • AMD 规范,requirejs 实现的规范

  • CMD 规范,seajs 实现的规范, seajs 与 requirejs 实现原理有很多相似的地方 u ES Modules,当前 js 标准模块化方案

注意:cjs、amd、cmd、 ES Modules 都是指规范,所以可能对应有多种实现,下面就对各个模块化方案做简单说明。

无模块化

刀耕火种年代的前端代码

无模块化带来的问题的问题

  • 污染全局作用域

  • 不便于拆分逻辑,维护成本高

  • 依赖关系不明显

  • 复用性差

CommonJS 规范

  • CommonJS 是由 node 实现的一套规范,关于 CommonJS 的提出可参考CommonJS 规范https://zhaoda.net/webpack-handbook/commonjs.html

  • require 源码解读可参考 require() 源码解读http://www.ruanyifeng.com/blog/2015/05/require.html

  • 模块包装相当于执行如下代码, compiledWrapper 是调用 node 封装的 V8 原生创建函数的方法返回的一个函数

function compiledWrapper(exports, require, module, __filename, __dirname) {  // 插入文件中的代码  // 返回导出对象  return module.exports}compiledWrapper.call(exports, exports, require, module, filename, dirname)

CommonJS 模块输出的是一个值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。如下有两个文件,执行命令node index.js,会有什么结果?

// lib.jslet counter = 3function incCounter() {  counter++}module.exports = {  counter,  incCounter}
// index.jsconst mod = require('./lib')// 此处输出值?console.log(mod.counter)mod.incCounter()// 此处输出值?console.log(mod.counter)

输出值分别为:3,3

require 函数第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象,下次加载会直接从缓存中取数据。以下是一个循环引用的例子,请问执行node main.js后会输出什么?

// a.jsconsole.log('a starting')exports.done = falseconst b = require('./b.js')console.log('in a, b.done = %j', b.done)exports.done = trueconsole.log('a done')
// b.jsconsole.log('b starting')exports.done = falseconst a = require('./a.js')console.log('in b, a.done = %j', a.done)exports.done = trueconsole.log('b done')
// main.jsconsole.log('main starting')const a = require('./a.js')const b = require('./b.js')console.log('in main, a.done = %j, b.done = %j', a.done, b.done)

输出结果如下:

main startinga startingb startingin b, a.done = falseb donein a, b.done = truea donein main, a.done = true, b.done = true

AMD 规范

  • AMD 是 Asynchronous Module Definition 的简写,即异步模块定义

  • AMD 规范的完整定义可参考 https://github.com/amdjs/amdjs-api/wiki/AMD

  • requirejs 是在浏览器中运行的,所有一些基础库需要先配置,以方便其他库调用,可以理解为 CommonJS 中的 node_modules 下的包。业务模块也可定义在其中,可认为是路径别名。paths 中的路径不能包含扩展名。

requirejs路径配置,后面再具体讲解里面参数的作用

require.config({  paths: {    // 如果第一个加载失败就会加载第二个    jquery: ['lib/jquery.min', 'lib/jquery'],    lodash: 'lib/lodash.min',    main: './mian' // 入口文件  }})

定义模块

/*** 定义模块,当依赖加载完成后执行回调* 回调可返回值,返回值会被导出到外部使用* @param {String} id 模块名称,可省略* @param {Array} dependencies 依赖的模块* @param {Function} factory 回调函数*/define(id?, dependencies?, factory);define(['jquery'], function($) {  $('body').css({ background: 'red' })  // 导出log函数  return (...args) => console.log('自定义log', ...args)})

依赖可以使用上面的config中定义paths中的key,这样就可以缩短路径,同时也便于第三方库加载依赖,例如jQuery插件打包为AMD格式的文件,引入jQuery会使用define(['jquery'], function (){ })的形式,而不用关心jQuery的真实路径。

加载模块

/** * 加载模块 * @param {Array} deps 要加载的模块 * @param {Function} callback 加载成功回调,回调参数为加载模块导出对象 * @param {Function} errback 加载失败回调 */requirejs(deps, callback, errback)require(['main'], log => {  log('我成功加载了‘)  // do something...,也可以在这里继续require其他js文件})

requirejs 使用示例可参考原文Github中的源码

CMD 规范

  • CMD 是 Common Module Definition 的简写,即通用模块定义

  • CMD 规范的完整定义可参考https://github.com/seajs/seajs/issues/242

  • CMD 的主要代表是 seajs。CMD 推崇依赖就近,AMD 推崇依赖前置。即 AMD 在定义模块的时候就必须把依赖包含进来,CMD 是在使用的时候再 require 对应的依赖

  • 当前主流的库对 CMD 支持不是很友好,都需要额外的修改才能工作

  • AMD 与 CMD 写法对比如下

// CMD// 代码写起来有同步require的感觉define((require, exports, module) => {  const $ = require('jquery‘)  $('title').text('hello')})// AMD// 明显的异步风格define(['jquery'], $ => {  $('title').text('hello')})

seajs 中 require 书写约定

  1. 正确拼写 require

// 错误!define(function(req) {  // ...}) // 正确!define(function(require) {  // ...})

使用直接量

// 错误!require(myModule) // 错误!require('my-' + 'module') // 错误!require('MY-MODULE'.toLowerCase()) // 正确!require('my-module')

不要修改 require

// 错误 - 重命名 "require"!var req = require,  mod = req('./mod') // 错误 - 重定义 "require"!require = function() {} // 错误 - 重定义 "require" 为函数参数!function F(require) {} // 错误 - 在内嵌作用域内重定义了 "require"!function F() {  var require = function() {}}

seajs 隐藏坑

如下代码输出`$`为 null

function func(require, exports, module) {  const $ = require('jquery‘)  console.log($)}func.toString = () => '() => {}'define(func)

seajs 对于 require 和 define 函数的特殊要求是由于seajs 原理导致的,seajs 的执行流程大致如下

seajs 使用示例可参考原文Github中的源码

ES Modules

  • ES Modules 是 ECMAScript modules 的简写,也可写为 ESM。ES Modules 是 js 官方推出的标准

  • ES Modules 相比于其他模块规范是一个静态化的模块解决方案,其他模块化方案都是运行时才能确定输出内容,而 ES Modules 是编译时就确定了的。其他模块化方案导入文件都是整个导入模块,而 ES Modules 可以只导入需要的部分

  • ES Modules 会自动采用严格模式,不需要像 ES5 一样在头部加上”use strict”

  • ES Modules 可运行在服务端(node)和浏览器。目前主流浏览器都已经支持 ES Modules,node 使用 ES Modules 需要在执行时加上--experimental-modules,且要求编写的 js 文件必须以.mjs 为后缀

  • ES Modules 导出的是一个值得引用,即在模块内改变了导出值,那么下一次使用也会得到新的值

如下有两个文件,执行命令node --experimental-modules index.mjs,会有什么结果?

// lib.mjsexport let counter = 3export function incCounter() {  counter++}
// index.mjsimport * as mod from './lib’// 此处输出值?console.log(mod.counter)mod.incCounter()// 此处输出值?console.log(mod.counter)

输出结果为:3,4

循环引用

如下代码,请问执行`node --experimental-modules main.mjs`后会输出什么内容

// a.mjsimport { bar } from './b.mjs'console.log('a.mjs')console.log(bar)export let foo = 'foo'
// b.mjsimport { foo } from './a.mjs'console.log('b.mjs')console.log(foo)export let bar = 'bar'
// main.mjsimport './a.mjs'
  • 在所有的模块规范中都存在循环依赖问题,解决依赖循环的方式都相似,几乎都采用惰性导入的方式来解决。

  • 如下两个文件存在循环引用,当执行 node --experimental-modules a.mjs 时,会报错说 b 未定义,这就是由于循环依赖导致的,如果不使用 b 则不会报错,修改方案如下。其他的模块循环引用也可按照此方法进行修改。

  • CommonJS 也可以使用先导出自身,再引入其他模块的方式来避免。同时也可以把 require 放入到函数体中,即在调用的时候才去加载依赖

相关链接

  • AMD 和 CMD 的区别有哪些?- 玉伯的回答 - 知乎(https://www.zhihu.com/question/20351507/answer/14859415)

  • https://github.com/seajs/seajs/issues/277

  • https://github.com/seajs/seajs/issues/242

模块pdf2image.dll加载失败_Webpack 原理从前端模块化开始相关推荐

  1. 计算机中丢失swr.dll,win10系统提示模块initpki.dll加载失败如何解决

    有不少用户在使用电脑的过程中,发现出现了模块initpki.dll加载失败的提示,遇到这样的问题该怎么办呢,本教程就给大家讲解一下 1.打开搜索,输入:powershell ,在windows pow ...

  2. 计算机中丢失swr.dll,win10电脑中模块initpki.dll加载失败提示0x80004005错误代码如何解决...

    有不少win10系统用户反映说碰到这样一个故障,就是模块initpki.dll加载失败,并提示0x80004005错误代码,该怎么解决呢,接下来就随系统城小编一起来看看具体的操作步骤吧. 1.打开搜索 ...

  3. 模块“XXX.dll”加载失败

    具体问题:模块"XXX.dll"加载失败 请确保该二进制存储在指定的路径中,或者调试它以检查该二进制或相关的.DLL文件是否有问题  找不到指定的模块. 1.在安装C++软件的时候 ...

  4. win10 电脑中模块initpki.dll加载失败提示0x80004005错误代码如何解决

    win10 电脑中模块initpki.dll加载失败提示0x80004005错误代码如何解决 有不少win10系统用户反映说碰到这样一个故障,就是模块initpki.dll加载失败,并提示0x8000 ...

  5. 模块d3d12.dll加载失败_语音控制模块

    说道作品的互动性,有什么能比语音控制更加炫酷呢? 今天介绍一款免编程非特定人声, 串口 语音识别模块LD3320 接线图: 调试:TXD/RXD通过串口模块连接电脑,将麦克风连接模块.打开上位机串口调 ...

  6. 模块msvcp140.dll加载失败怎么解决

    参考:https://zhidao.baidu.com/question/1050601731729664579.html

  7. initpki.dll加载失败 找不到指定的模块的解决办法

    有用户在更新Win10系统时,收到提示"模块'initpki.dll'加载失败.请确保该二进制存储在指定的路径中,或者调试它以检查该二进制或相关的DLL文件是否有问题.找不到指定的程序.&q ...

  8. 计算机中丢失swr.dll,initpki.dll加载失败找不到指定的模块0x80004005错误代码怎么办win10...

    如今越来越多的小伙伴都已经装上了win10系统,使用过程中难免也会碰到各种故障,例如有不少 具体步骤如下: 1.打开搜索,输入:powershell ,在windows powershell 上单击右 ...

  9. 调试实战 —— dll 加载失败之 Debug Release争锋篇

    缘起 最近,项目里遇到一个 dll 加载不上的问题.实际项目比较复杂,但是解决后,又是这么的简单,合情合理.本文是我使用示例工程模拟的,实际项目中另有玄机,但问题的本质是一样的.本文从行文上与 < ...

最新文章

  1. sql server 替换有反斜杠的字符串_SQL注入思维导图
  2. sh 脚本执行sql文件传参数
  3. 和为K的组合(01背包)
  4. python db2查询_如何将DB2查询转换为python脚本
  5. hdu-1862-EXCEL排序
  6. 实现可拖拽,拉伸,吸附功能的甘特图(时间/任务表)
  7. 如何实现一个遵从设计原则的积分兑换系统2
  8. NTFS权限和文件详解
  9. Mybatis整合Spring框架时所需的依赖
  10. 三维立体动画制作技巧
  11. 简洁的旅行青蛙个人主页纯静态HTML
  12. MSDC 4.3 接口规范(20)
  13. 让机器人懂感情:表情符与深度学习
  14. 微信小程序 实现报表(表格)双指缩放功能
  15. vpb vs2008 编译
  16. 学习JSP——在虚拟机中建立一个JSP网页,通过Tomcat服务器发布,并通过虚拟机的宿主机浏览器访问虚拟机上所发布的JSP网页
  17. 英尺 厘米_我们如何在80小时内建立33,000英尺高的社区?
  18. 屏蔽网页中一些快捷键
  19. 联通大数据5000台规模集群故障自愈实践
  20. PROE基本操作1(查看组件尺寸)

热门文章

  1. java ee jsp_EE JSP:Servlet的反向外套
  2. 使用ArchUnit验证代码和体系结构约束
  3. Eclipse系列的隐藏宝藏-2019年版
  4. Pub / Sub本地模拟器
  5. Kogito,ergo规则—第2部分:规则的全面执行模型
  6. 雅加达EE:干净的板岩
  7. Java 8中的可重复注释
  8. Java 9:ServiceLoader
  9. mvvm 自动绑定_ZK的实际应用:MVVM –表单绑定
  10. shader weaver_Oracle通过邀请Weaver和Chin推动JavaFX向前发展