乐趣区

关于前端:前端模块化之路

概念

模块化的目标:每个模块都是一个独立的性能,从而大幅晋升了晋升代码可维护性与可复用性,同时躲避命名抵触。

晚期模块化形式

  • 命名空间形式(利用对象封装):将模块申明为一个对象,所有的模块成员挂载到此对象中。
  • IIFE(利用函数封装):将模块成员搁置在函数作用域中,此形式实现了模块公有成员的行为。对于须要裸露的成员能够间接作为函数返回值,亦可挂载到 window 下。

晚期模块化形式的弊病

  • 每个文件就是一个独立模块,通过在 HTML 中应用 script 标签引入的形式应用,一个一个同步加载,容易造成浏览器阻塞。
  • 晚期模块化形式无奈通晓各个模块的依赖关系,这可能会呈现因为不理解模块间的依赖关系导致加载先后顺序出错的问题。
  • 通过古代模块化标准能够解决以上问题。

模块化标准

CommonJS 标准

CommonJS 标准:实用于 Node.js 提出和应用的模块化标准。

  • CommonJS 外部实现是将每个模块文件包装为一个函数,CommonJS 提供的成员作为函数形参传入供模块应用。

    • module.exports(导出)、exports(指向 module.exports)、require(导入)、__filename(以后文件的绝对路径)、__dianame(以后文件的所在目录)均属于 CommonJS 提供的成员。
  • 一个文件就是一个模块,每个模块都有独自的作用域。
  • 通过应用 module.exports 导出模块成员。

    •  留神:当 module.exports 和 exports 指向不同的对象时,则最终以 module.exports 为准。
  • 通过应用 require 函数载入指定模块。
  • CommonJS 是以同步模式加载模块的 。即,在程序运行开始时就将所有模块顺次加载结束。这意味着如果 在浏览器端应用 CommonJS 标准,则会导致效率低下。而 Node.js 次要是面向服务端编程,因而较为实用

AMD 标准(require.js)与 CMD 标准(sea.js)

AMD(异步模块定义)标准采纳异步加载模块的形式来解决同步加载的问题 require.js 实现了 AMD 标准。

  • 外部通过动态创建 script 标签来载入指定模块。

每个模块通过 define 函数 来定义:

define(moduleName,[rely1,rely2],function(rely1,rely2){
  // 参数别离为 模块名、依赖项、模块体;返回值为要导出的模块成员
  return {fn:function(){rely1();
      rely2();}
  }
})

对于模块的载入,则应用 require 函数

require(['./module1','./module2','./module3'],function(module1,module2,module3){// ...})

特点

  • 绝大多数第三方模块都反对 AMD 标准;
  • 外部是通过动态创建 script 标签来载入指定模块的,导致模块 JavaScript 文件申请频繁;
  • 应用起来绝对较为简单。

CMD 标准(sea.js)
sea.js 相似于 require.js 和 CommonJS 标准的结合体。已被 require.js 所兼容,其定义模块的形式如下:

define(function(require,exports,module){const $ = require('jquery')
  module.exports=function(){// ...}
})

ES Mobules 标准

根底应用

在浏览器环境下 通过为 script 标签增加 type = module 属性,即可应用 ES Module 规范来执行其中的 JS 代码。

  • 目前 ESM 规范已反对古代浏览器(Chrome、Firefox 等),不反对 IE。
  • 通常不会间接在浏览器端应用 ESM,而是会通过配置 Babel 来应用,在开发阶段应用 ESM 语法,最终打包后会转化为浏览器兼容的模块规范。

ESM 规范下 Script 标签中代码的特点:

  • 主动启动严格模式;
  • 每个 ESM 模块都是运行在公有作用域中的;
  • ESM 规范的 Script 标签会提早执行脚本。即在文档渲染结束后执行(相当于默认增加了 defer 属性);
  • 对于 ESM 模块文件,倡议通过 .mjs 来命名后缀(官网约定,且在 Node.js 中 ESM 只反对 .mjs 后缀的脚本)。**

根底应用
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>
    <p> 这是一个段落 </p>
    <script type="module">
      var foo = 1
      console.log(foo) // 1
    </script>

    <script type="module">
      console.log(foo) // Uncaught ReferenceError: foo is not defined
    </script>

    <script type="module">
      import setColor from './index.mjs'
      setColor('p', 'red')
    </script>
  </body>
</html>

index.mjs

export default function setColor(elm, color) {document.querySelector(elm).style.color = color
}

后果

导出与导出语法

导出(应用 export 关键词。存在两种 export 导出形式)

最终导出的是成员的援用,并且是只读的(即不可再次复制。外部用 const 标记)。

命名导出

** 导出单个成员(在变量、函数或对象申明前减少 export 修饰符即可)`
`**

export var name = 'zs'
export function sayHi(){console.log('hello')
}
export var info = {a:1,b:2,c:3}

导出多个成员

export {
  name,
  sayHi,
  info as msg // 重命名后导出
}

注:这里并不是导出了一个对象,而是一种固定的、用于导出多个成员的语法。

默认导出

export default {name, sayHi} // 此处是导出了一个对象,对象蕴含 name 和 sayHi 这两个成员

默认导出与命名导出的区别

  • 默认导出在模块中只能应用一次,而命名导出在模块中能够屡次应用;
  • 默认导出的成员在执行导入操作时能够任意命名 ,而 命名导出的成员在执行导入操作时名称必须和导出时的名称保持一致;
  • 一般来说,如果模块只须要导出一个成员,或者将多个须要导出的成员作为某个对象的属性时,能够应用默认导出形式将须要导出的单个成员导出,或者导出一个蕴含多个成员的对象。(第三方模块个别都是只导出一个默认成员)

导入(应用 import 关键词)

根本用法

import {name as mName, sayHi, msg} from './module.js'
  • 注:此处不是对象解构语法,同样是一种固定的、用于导入多个成员的语法。
  • 门路必须是残缺文件名称,不可省略 .js 扩展名,并且不会默认读取文件夹的 index.js 文件(前期应用模块打包工具能够解决这个问题)。

一次导入所有成员

import * as mod from './module.js' // 此时所有导入的模块成员将作为 mod 的成员供应用

只执行模块,不导入成员

import './module.js'

import 关键字只能呈现在最外层的作用域。如果须要动静导入模块,则应用 import 函数即可

import('./module.js').then((module)=>{console.log(module)
})

导入默认成员的形式

import modMsg from './module.js'
// or
import {default as msg} from './module/js'

同时导入默认成员与命名成员的形式

import modMsg, {name, age, info} from "./module.js";
// or
import {name, age, info, default as modMsg} from "./module.js";

古代模块化标准的最佳实际

即,面向浏览器时,应用 ES Modules 模块化标准;面向 Node.js 端时,间接应用 CommonJS 标准即可。

退出移动版