Javascript 模块化开发

一 什么是模块化开发

模块化是最重要的前端开发范式之一。前端应用日益复杂,项目代码膨胀,我们需要更好的方式来组织和管理代码。模块化就是一种主流的代码组织方式,它通过把复杂代码划分为易于单独维护的模块,来提高开发效率,降低维护成本。

 

二 模块化开发的演进过程

1. Stage1 - 文件划分的方式

    index.html
    index.js
    index.css
    detail.html
    detail.js
    detail.css
    特点:会污染全局作用域、易产生命名冲突、无法管理模块间的依赖关系、完全依靠约定不适用于大型项目。

 

2. Stage2 - 命名空间的方式

    detail.js 中所有内容写在一个对象(如 detail)内,访问该对象内的成员通过 detail.xxx 的方式。

    特点:相比文件划分的方式,可减少部分命名冲突。

 

3. Stage3 - IIFE 立即执行函数提供私有空间

    detail.js 中所有内容写入一个立即执行函数,把其中需要对外暴露的成员挂载到全局对象。

    特点:可以实现私有成员。

 

4. Stage4 - 模块化规范的出现(模块化标准 + 模块化加载器)

   4.1 CommonJS 规范

        node环境的主流规范,由nodeJS 提出的同步加载模块规范,目前主要应用于node环境。

        特点:一个文件就是一个模块、每个模块有单独的作用域、通过 require 函数载入模块 、通过 module.exports 导出成员。

// CommonJS 规范示例

// module.js
function add(a, b) {
    return a + b;
} 
const foo = 'foo'

// 导出写法一
module.exports = {
    add, foo
}
// 导出写法二
exports.add = add
exports.foo = foo


// index.js
const tool = require('./module.js')

console.log(tool.add(1, 2)) // 3
console.log(tool.foo)       // foo


// 执行 node index.js

 

   4.2  AMD (Asynchronous Module Definition)规范

        为浏览器设计的异步加载模块规范,以 require.js 为代表,目前已经不再流行。

        特点:AMD中使用require 其内部就创建一个script标签 载入并执行。目前绝大多数第三方库都支持 AMD 规范。

// AMD 规范示例

// module1.js
// 用 define 定义模块及其依赖
define('module1', ['jquery', './module2'], function($, module2) {
    // 用 return 导出成员
    return { 
      start : function(){
        $('body').animate({ margin: '200px' })
        module2()
      }
    }
})


// index.js
// 用 require 载入模块
require(['./module1'], function(module1) {
  module1.start()
})

 

   4.3 CMD 规范

        淘宝推出的异步加载模块规范,以 sea.js 为代码,目前已经不再流行。   

        特点:写法上类似 CommonJS 规范。

// CMD 规范示例

// 用 define 定义模块
define(function(require, exports, module){

  // 通过require 引入依赖
  var $ = require('jquery') 

  // 通过 module.exports 或 exports.xxx = xxx 对外暴露成员
  module.exports = function() {
    console.log('module 2~')
    $('body').append('<p>module2</p>')
  }

})

 

   4.4 ES Modules 规范

        浏览器环境的主流规范,模块化的标准规范,浏览器中的模块化规范统一于ES Modules,是语言层面支持的模块化规范,目前最为流行,许多浏览器的最新版本已经直接支持 ES Modules 规范了。

        特点:

        a. 自动采用严格模式,忽略 'use strict'(this 为 undefined)

        b. 每个 ESM 模块都有单独的私有作用域

        c. ESM 通过 CORS 的方式去请求外部JS模块,需要外部JS模块支持跨域

        d. ESM 的 script 标签会延迟执行脚本(相当于给script标签添加了 defer 属性)

// ES Modules 规范示例

// index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ES Modules 规范</title>
    <!-- 添加 type=module 属性,就可以以 ES Module是标准执行其中js代码了 -->
    <script type="module" src="./index.js"></script>
</head>
<body>
    <h1>一个标题</h1>
    <p>一个段落。</p>
</body>
</html>


// index.js
// 导入成员
import { time, bar } from './tool.js'

console.log(time())
console.log(bar)


// tool.js
function time(){
    return new Date().toLocaleString()
}

const bar = 'bar'
// 导出成员
export { time, bar }

 

三 详解 ES Modules 模块化规范

1. ES Modules 导入和导出

1.1 注意事项:

    a. 导出成员 export { time, bar }  - export {} 是固定语法,不是对象字面量语法,表示导出成员 time, bar。

    b. 导出默认成员 export default { name, age } - export default xxx 只导出一个成员,此处 { name, age }是作为一个对象导出。

    c. 导入成员 import { time, bar } - import {} 是固定语法,不是解构,表示导入成员 time, bar。

    d. 导入的是数据的内存地址,并非拷贝一份的数据,无论基本类型还是引用类型。

    e. 导入的成员是只读的,不可修改。

 

1.2 导入模块的三种方式:

import { name } from './module.js'      // 相对路径 路径需完整
import { name } from '/test/module.js'  // 绝对路径 第一个/表示根目录
import { name } from 'http://xxxx/xxxx/module.js' // 完整url 可导入CDN资源

1.3 执行模块而不导入成员

import {} from './module.js
import './module.js'

1.4 导入全部成员

import * as mod from './module.js'

1.5 动态的导入(加载)模块

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

1.6 同时导出 导入默认成员和命名成员

// tool.js 同时导出默认成员 和 命名成员
function time(){
    return new Date().toLocaleString()
}

const bar = 'bar'

const foo = {
    id: 123,
    name: 'foo123'
}

export { time, bar }
export default foo


// index.js 同时导入默认成员 和 命名成员
import { time, bar, default as foo } from './tool.js' // 写法一
import foo, { time, bar } from './tool.js'            // 写法二

console.log(time())
console.log(bar)
console.log(foo)

1.7 直接导出 导入的成员

export { time, foo } from './tool.js'

 

2. ES Modules 浏览器环境 polyfill

由于ES Modules规范提出于2014年,故IE和一些老旧浏览器仍不支持,可以使用如下三个polyfill,让它们支持 ES Modules规范,但这种做法不可以用于生产环境(因为效率低)。

    babel-browser-build.js
    browser-es-module-loader.
    polyfill.min.js

<!-- nomodule 表示对于不支持 ES Modules 的浏览器才执行 -->
<script nomodule src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
<script nomodule src="https://unpkg.com/browse/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
<script nomodule src="https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"></script>
    

 

3. ES Modules 在 node 中的应用

node环境的主流模块化规范虽然是CommonJS,但是在 node8.5 版本之后,node环境也开始逐步的支持 ES Modules 规范了(以实验特性执行)。

使用方式:

    方式一:

    a. 修改 xxx.js 文件后缀名为 mjs

    b. 执行文件时使用 node --experimental-modules xxx.mjs

    方式二:

    a. 在 package.json 文件中设置 type:"module",则默认所有js文件以 ESM 模块规范执行

    b. 如果项目中存在CommonJS规范的文件,则修改其后缀为 .cjs 则其可按 CommonJS的方式执行

注意事项:

    a. ES Modules 规范的文件中,可以导入 CommonJS 模块

    b. CommonJS 规范的文件中,不能导入 ES Modules 模块

    c. CommonJS 始终只会导出一个默认成员

 

4. node中 ES Modules 与 CommonJS 的全局变量

   a. CommonJS 中可正常使用如下全局变量

// CommonJS 中可正常使用如下全局变量
// 加载模块函数
console.log('require', require)

// 模块对象
console.log('module', module)

// 导出对象别名
console.log('exports', exports)

// 当前文件的绝对路径
console.log('__filename', __filename)

// 当前文件的绝对目录
console.log('__dirname', __dirname)

   b. ES Modules 中则没有上述 CommoJS 中的全局变量,用如下方式得到 __filename 和 __dirname 

// ESM 中 没有 CommonJS 中的那些模块全局成员
import { fileURLToPath } from 'url'
import { dirname } from 'path'

const __filename = fileURLToPath(import['meta'].url)
console.log('__filename', __filename)

const __dirname = dirname(__filename)
console.log('__dirname', __dirname)

console.log('import: ', import.meta.url)

老版本node(版本 < 8.5) 中,可用 Babel 来兼容 ES Modules:

步骤:
yarn add @babel/node @babel/core @babel/preset-env --dev
yarn babel-node index.js --presets=@babel/preset-env

或者在根目录下 添加 .babelrc 文件:
内容:
{
    "presets": ["@babel/preset-env"]
}

内容或者:
{
    "plugins": [
        "@babel/plugin-transform-modules-commonjs"
    ]
}

本文 完。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值