模块化开发

模块化开发是当下最重要的前端开发范式之一

模块化演变过程

  • Stage1 文件划分方式
    具体的做法就是每个功能及其相关状态数据各自单独放到不同的文件中,约定每个文件就是一个独立的模块,使用某个模块就是将这个模块引入到页面中,然后直接调用模块中的成员(变量/函数)
    缺点也就十分明显了:

    • 污染了全局作用域
    • 命名冲突问题
    • 无法管理模块依赖关系



  • Stage2 命名空间方式
    每个模块只暴露一个全局对象,所有的模块成员都挂载到这个对象中,具体的做法就是在第一阶段基础之上,通过将每个模块包裹为一个全局对象的形式实现,有点类似于为模块内的成员添加了命名空间的感觉
    通过【命名空间】这一概念减少了命名冲突的可能,但是同样的,没有私有空间,所有的模块成员都可以在模块外部被访问或者是被修改,而且没有办法管理模块之间的依赖关系


  • Stage3 IIFE 立即执行函数表达式
    将每个模块成员都放在一个函数提供的私有作用域中,对于需要暴露给外部的成员,通过挂在到全局对象上的方式来实现,有了私有成员的概念,私有成员只能在模块成员内部通过闭包的形式访问


    需要暴露给外部的成员就使用这种挂载到全局作用域上面去实现
  • Stage4 模块化演变
    利用IIFE参数作用依赖声明使用,具体做法就是在第三阶段的基础上,利用立即执行函数的参数传递模块依赖项,使得每一个模块之间的关系变得更加明显
    例如使用jQuery,就使用立即调用函数接受jQuery的参数

    以上就是早期在没有工具和规范的情况下,对模块化的落地方式

模块化规范的出现

需要的内容就是:
模块化标准+模块加载器

CommonJS规范(node.js中的规范)

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

CommonJS是以同步模式加载模块
在浏览器中必然会导致效率低下

AMD(Asynchronous Module Definition)

异步模块定义规范

require.js

require.js实现了AMD规范,本身也是很强大的模块加载器

require.js定义一个模块,第一个参数就是模块的名字,第二个参数是数组,声明模块的依赖项,第三个参数是函数,函数内参数与依赖项一一对应,每一项是依赖项导出的成员,函数的作用是为当前模块提供一个私有的空间,如果需要向外部导出一些成员,可以通过return实现

自动加载一个模块,只是用来加载模块,其他参数作用与define类似

目前绝大多数第三方库都支持AMD规范

  • AMD使用起来相对复杂
  • 模块JS文件请求频繁,效率低下

Sea.js+CMD

这些以前的知识在目前来看也是很重要的一环

模块化标准规范(模块化的最佳实践)

  • 在node环境当中,会采用CommonJS规范
  • 在浏览器环境中,会采用一个叫做ES Modules规范

    现如今绝大多数浏览器都已经支持ES Modules,故而ES Modules的学习成为了重中之重

ES Modules

  • 通过script 添加type = module 的属性,就可以以ES Module的标砖执行其中的JS代码
    <script type="module">
        console.log("this is ES modules")
    </script>
  • ESM 会自动采用严格模式,忽略use strict
    (在非严格模式下,this指向的是window对象)
    <script type="module">
        console.log(this)
    </script>
  • 每个ES Module 都是运行在单独的私有作用域当中(第二个打印的foo就会报错undefined)

    <script type="module">
        var foo = 100
        console.log(foo)
    </script>

    <script type="module">
        console.log(foo)
    </script>
  • 在ESM中是通过CORS的方式请求外部JS模块的
  • ESM 的script标签会延迟执行脚本
    (延迟加载脚本,先渲染元素到页面上,一般的script标签就会等到脚本加载完成才会渲染元素)
    这个小特点与script标签的defer属性是一样的
    <script type= "module" src="demo.js"></script>
    <p>需要显示的内容</p>

ES Modules导入和导出

  • 可以导出变量,函数,类等等
export var name = 'foo module'

export function hello(){
    console.log("foo hello")
}

export class Person{

}
  • 也可以统一导出,比如:
export { name , hello , Person}
  • 在另一个模块js文件要导入
import { hello, name } from './module.js'
console.log(name)
hello()

重命名

 var name = 'foo module'

 function hello(){
    console.log("foo hello")
}

 class Person{

}

export { 
    name as fooName,
    hello as fooHello,
    Person as fooPerson
}

重命名之后导入时也要注意名字变化

import { fooHello, fooName } from './module.js'
console.log(fooName)
fooHello()

重命名特殊情况

将导出成员名称设置为default,这个成员就会被设置为当前模块的默认导出成员,在导入的时候就必须要进行重命名

export { 
    name as default,
    hello as fooHello,
    Person as fooPerson
}

重命名default才能调用

import { fooHello, default as fooName } from './module.js'

ESM 关于针对default的特殊处理

将name变量设置为默认导出

export default name;

在导入的时候可以通过直接import + 变量名的方式接受默认导出的成员,变量名称随意

// fooName这里是可以随意取名的
import fooName from './module.js'

ESM 导入导出的注意事项

  • export 后面跟上的花括弧包裹的不是字面量,是固定语法
  • 导入时的那些成员是分享的内存空间,是完全相同的引用关系
  • 导入的成员是只读的

ESM import用法

  • 导入时from关键字后面跟的是字符串,内部的内容路径必须要完整的文件名称,不能省略js后缀名,跟CommonJS完全相反
  • 也可以使用完整的url加载模块,也就是说可以使用CDN上面的模块,完整的
  • 如果说只执行某个模块的功能,不去提取模块中的成员的话,可以保持花括弧为空,或者直接import跟上字符串,这个特性在我们导入一些不需要外界控制的子功能模块时就非常有用了
import {} from './module.js'
import './module.js'
  • 需要导出的成员特别多,导入时都会用到他们,就可以用*全部提取出来,使用as关键字全部存在对象里面
import * as mod from './module.js'
console.log(mod)
  • 动态导入
import('./module.js').then(function (module) {
  console.log(module)
})
  • 默认成员和明明成员同时导出
var name = 'jack'
var age = 18

export { name, age }

console.log('module action')

export default 'default export'

import abc, { name, age } from './module.js'
console.log(name, age, abc)

ESM 直接导出导入成员

  • 具体的做法就是将import关键词修改为export,所有的导入成员会作为当前模块的导出成员,在当前作用域下,也就不再可以访问这些成员了。
    一般用于index文件,把散落的模块通过这种方式组织到一起,导出,方便外部使用

avatar.js:

export var Avatar = 'Avatar Component'

button.js:

var Button = 'Button Component'
export default Button

index.js:

export { default as Button } from './button.js'
export { Avatar } from './avatar.js'

app.js(导入):

import { Button, Avatar } from './components/index.js'

console.log(Button)
console.log(Avatar)

avatar和button都是暴露了组件,index.js则是将这两个组件导入,并且导出,作为一个桥梁的作用

ESM in Browser(Ployfill兼容方案)

  • 让浏览器支持ESM 的绝大特性
  • 模块名字为Browser ESM Loader

https://github.com/ModuleLoader/browser-es-module-loader

针对NPM下的模块可以通过upkg这个网站的CDN服务来拿到所有的JS文件

https://unpkg.com/ + npm下的模块名

比如

https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js

/dist/表示目录下的文件

将对应的路径复制下浏览器地址用script标签引入就可

  • 引入IE所需要的promise,ployfill
 <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
  <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
  • nomodule属性
    解决了支持polyfill的浏览器不去加载标签内资源的问题

ESM in Node.js

  • 在Node当中直接使用ESM ,要做的有:
    • 第一,将文件的扩展名由 .js 改为 .mjs;


    • 第二,启动时需要额外添加 --experimental-modules 参数;

  • 也可以用ESM 载入原生模块
// // 此时我们也可以通过 esm 加载内置模块了
import fs from 'fs'
fs.writeFileSync('./foo.txt', 'es module working')
  • 也可以直接提取模块内的成员,内置模块兼容了ESM的提取成员的方式
import { writeFileSync } from 'fs'
writeFileSync('./bar.txt', 'es module working')
  • 对于第三方的NPM模块也可以通过ESM加载
    (比如第三方模块lodash)
import _ from 'lodash'
_.camelCase('ES Module')
  • 但是不能使用ESM的花括弧方式去载入第三方模块的成员
// // 不支持,因为第三方模块都是导出默认成员
import { camelCase } from 'lodash'
console.log(camelCase('ES Module'))

ESM in Node.js 与 CommonJS模块的交互

  • CommonJS模块始终只会导出一个默认成员
  • ESM 中是可以导入CommonJS模块的
  • 不能直接提取成员,import不是解构导出对象
  • 在CommonJS中通过require载入ESM 也是不可以的


ESM in Node.js与CommonJS的差异

在这之前先推荐使用nodemon工具,可以监听mjs文件的变化并且给出错误信息
先用npm 进行全局安装,再使用

  • ESM中没有模块全局成员了
  • require,module,exports自然是可以通过import和export代替
  • __filename 和 __dirname 通过 import 对象的 meta 属性获取
const currentUrl = import.meta.url
console.log(currentUrl)
  • 通过 url 模块的 fileURLToPath 方法转换为路径
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)
console.log(__filename)
console.log(__dirname)

Node的新版本更加支持ESM了

  • 在新版本中的package.json添加type属性表示module,所有的JS文件就可以默认以ESM支持了
  • 如果需要在 type=module 的情况下继续使用 CommonJS, 需要将文件扩展名修改为 .cjs

Babel兼容方案

  • 早期的node版本,可以使用Babel进行ESM的兼容
  • 主流的JavaScript编译器,可以将新特性的代码编译成当前环境支持的代码
    需要安装babel一系列依赖
yarn add @babel/node @babel/core @babel/core @babel/preset-env --dev
  • 检测babel命令:


  • 安装插件
yarn add @babel/plugin-transform-commonjs --dev
  • 建立一个.babelrc文件
{
  "plugins": [
    "@babel/plugin-transform-modules-commonjs"
  ]
}

  • 运行文件
 yarn babel-node .\index.js
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 模块化开发是一种思想,随着前端项目的日益庞大。为了使我们开发协作更加高效,互不影响。将编写的代码模块化,更利于协作...
    lowpoint阅读 672评论 0 2
  • 模块化开发 模块化只是一种思想 模块化演变过程 Stage 1 - 文件划分方式将功能与数据放置到不同的文件当中约...
    彪悍de文艺青年阅读 238评论 0 0
  • 前端模块化开发简介 历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖...
    荣儿飞阅读 4,235评论 0 6
  • 模块化是一种主流的代码组织方式,是一种思想,它将代码依据不同的功能分成不同的模块来提高开发效率,降低维护成本。 模...
    洲行阅读 362评论 0 1
  • 1. 前言 现在的前端开发, 通常是一个单页面应用,每一个视图通过异步的方式加载,这导致页面初始化和使用过程中会加...
    majun00阅读 721评论 0 2