ES6之前没有模块化的解决方案
/***
* 没有模块化带来的问题
* 1. 命名冲突
* 使用立即执行函数调用带来的问题
* 1. 必须记住每个模块中返回对象的命名
* 2. 代码写起来混乱不堪,因为每个文件中的代码都需要包裹再一个匿名函数中编写
* 3. 再没有合适的规范情况下,有可能会任意命名,出现模块名称相同的情况
* */
// A js文件
const moduleA = (function () {
var name = 'coder',
var age = 19,
var isFlag = true
return {
name,
age,
isFlag
}
})()
// B js文件
const moduleB = (function () {
var name = 'coder',
var age = 19,
return {
name,
age
}
})()
// C js文件
(function () {
if (moduleA.isFlag) {
console.log('没有模块化之前的解决方案', moduleA.name);
}
})()
模块化的历史
在网页开发的早期,Brendan Eich 开发JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或者动画实现等,那个时候代码还算很少的
这个时候我们只需要将js代码写到<script>标签中即可;
并没有必要放到多个文件中来编写,甚至流行:通常来说js程序的长度只有一行。
但是随着前端和js的快速发展,js代码变得越来越复杂
- ajax的出现,前后端开发分离,意味着后端返回数据后,我们需要通过js进行前端页面的渲染
- SPA的出现,前端页面变得更加复杂:包括前端路由,状态管理等等一系列复杂的需求需要通过js来实现
- 包括Node的实现,js编写复杂的后端程序,没有模块化的致命的硬伤
所以,没有模块化已经是js一个非常迫切的需求
- 但是js本身,直到ES6才推出自己的模块化方案
- 在此之前,为了让js支持模块化,涌现了很多不同的模块化规范:AMD, CMD, CommonJS等
CommonJS规范和Node关系
CommonJS是一个规范, 最初提出来是在浏览器以外的地方使用,并且当时被命名为ServerJs,后来为了体现它的广泛性,修改CommonJS,平时我们也会简称为CJS
1. Node是CommonJS在服务器端一个具有代表性的实现
2. Browserify是CommonJS在浏览器中的一个实现
3. webpack打包工具具备对CommonJS的支持和转换
在node中每个js文件都是一个单独的模块
模块中包括CommonJS规范的核心变量: exports module.exports require
1. exports和module.exports 可以负责对模块中的内容进行导出
2. require 函数可以帮助我们导入其他模块(自定义模块,系统模块,第三方库模块)中的内容
CommonJS导入导出基本使用
// A.js 文件
var name = 'coder'
var age = 18
function sum(num1, num2) {
return num1 + num2
}
// 1.导出方案一 module.exports
// module.exports = {
// name,
// age,
// sum
// }
// 2.导出方案二 exports
// 源码做的事
// module.exports = {}
// exports = module.exports
exports.name = name
exports.age = age
exports.sum = sum
// 最终能导出的一定是module.exports这个对象
// exports = {
// age
// }
// B.js文件
// 使用另外一个模块导出的对象,那么就要进行导入 require
// 多个文件引用A.js 是引用的同一个对象
const {name, age, sum} = require('./A.js')
console.log(name);
console.log(age);
console.log(sum);
require细节
// require 是一个函数,可以帮助我们引入一个文件(模块)中导出的对象
// 导入格式如下: require(X)
// 当X 为不同值时,有不同的查找规则
// 情况一: 核心模块
const path = require('path')
const fs = require('fs')
// 情况二 X是以./ 或者../ 或者 /(根目录)开头的
const abc = require('./abc')
/**
* 第一步: 将X当做一个文件在对应的目录下查找
* 1. 如果没有后缀名,按照后缀名的格式查找对应的文件
* 2. 如果没有后缀名,会按照如下顺序:
* 1. 直接查找文件X
* 2. 直接查找X.js文件
* 3. 查找X.json文件
* 4. 查找X.node文件
* */
/**
* 第二步: 没有找到对应的文件,将X作为一个目录
* 查找目录下的index文件
* 1. 查找X/index.js文件
* 2. 查找X/index.json文件
* 3. 查找X/index.node文件
* */
// 如果没有找到,那么报错, not found
// 情况三: 直接是一个X(没有路径),并且X不是一个核心模块
// 就会去node_modules文件夹中去找index文件,当层没有,就会去上层找,以此类推
ES_Module基本使用
ES Module是js在ES6推出的模块化方案。
/**
* ES Module采用import export关键字
* import 负责从其他模块导入内容
* export 负责将模块内的内容导出
* 采用 ES Module将自动采用严格模式 use strict
* */
// a.js
// 第一种导出方法: export 声明语句
export const name = 'coder'
export const age = 18
// 第二种导出方式: export 导出 和声明分开
const name = 'coder'
const age = 19
function foo() {
console.log('foo, function');
}
// 后面的不是对象,而是一个语法
export {
name,
age,
foo
}
// 第三种方式: 第二种导出时起别名
export {
name as FName,
age as FAge,
foo as FFoo
}
// b.js
// 导入方式一: 普通的导入
import { name, age } from './a.js'
// 导入方式二: 起别名
import { name as FName, age as FAge} from './a.js'
// 导入方式三: 将导出的所有内容放到一个标识符中
import * as foo from './a.js'
import export结合使用
// 第一种方式导出
import { add, sub } from './utils/math.js'
import { priceFormat, timeFormat } from './utils/format.js'
// 做一个统一的出口
export {
add,
sub,
priceFormat,
timeFormat
}
// 第二种方式导出
export { add, sub } from './utils/math.js'
export { priceFormat, timeFormat } from './utils/format.js'
// 第三种方式导出
export * from './utils/math.js'
export * from './utils/format.js'
默认导入导出
const name = 'coder'
const foo = 'foo value'
export {
name,
age,
foo as default // 默认导出第一种写法
}
// 默认导出第二种写法
export default foo
// 注意: 默认导入只能有一个
// b.js文件
import foo from './a.js' // 默认导入
import函数
import { name, age } from './b.js'
console.log('上面这种导入是同步的,在这个模块没有解析下载完,是不会执行下面的代码的');
// import函数返回的结果是一个promise
import('./b.js').then(res => {
console.log('res:', res.name);
})
console.log('后面的代码会直接执行');
// ES11新增的特性
// meta属性本身也是一个对象: {url:'当前模块所在的路径'}
console.log(import.meta);