四.JS模块化开发
JS模块化开发
1.认识模块化开发
事实上模块化开发最终的目的是将程序划分成一个个小的结构;
这个结构中编写属于自己的逻辑代码,有自己的作用域,定义变量名词时不会影响到其他的结构;
这个结构可以将自己希望暴露的变量、函数、对象等导出给其结构使用;[导出]
也可以通过某种方式,导入另外结构中的变量、函数、对象等;[导入]
上面说提到的结构,就是模块;按照这种结构划分开发程序的过程,就是模块化开发的过程;
无论你多么喜欢JavaScript,以及它现在发展的有多好,它都有很多的缺陷:
比如var定义的变量作用域问题;
比如JavaScript的面向对象并不能像常规面向对象语言一样使用class;
比如JavaScript没有模块化的问题;
对于早期的JavaScript没有模块化来说,确确实实带来了很多的问题;
模块化的历史 :
在网页开发的早期,Brendan Eich开发JavaScript仅仅作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的:
这个时候我们只需要讲JavaScript代码写到<script>标签中即可;
并没有必要放到多个文件中来编写;甚至流行:通常来说 JavaScript 程序的长度只有一行。
但是随着前端和JavaScript的快速发展,JavaScript代码变得越来越复杂了:
ajax的出现,前后端开发分离,意味着后端返回数据后,我们需要通过JavaScript进行前端页面的渲染;
SPA的出现,前端页面变得更加复杂:包括前端路由、状态管理等等一系列复杂的需求需要通过JavaScript来实现;
包括Node的实现,JavaScript编写复杂的后端程序,没有模块化是致命的硬伤;
所以,模块化已经是JavaScript一个非常迫切的需求:
但是JavaScript本身,直到ES6(2015)才推出了自己的模块化方案;
在此之前,为了让JavaScript支持模块化,涌现出了很多不同的模块化规范:AMD、CMD、CommonJS等
2.CommonJS(模块化方案[社区推出,还有人用])和node
CommonJS 是一个规范
导出 : exports
导入 : require
例子 :
导出 : a.js let name = 'yjx' funciton foo(){ } exports.name = name exports.foo = foo 导出 : const aa = require('./a.js') console.log(aa.name) console.log(aa.foo)
优化 :
const {name,foo} = require('./a.js')
console.log(name)
console.log(foo)
Node,Browserify,webpack 都有实现过CommonJS的规范. 浏览器没有实现过(所以不能用)
进阶 :
其实我们通常用的是 module.exports , 而不是exports . 在CommonJS中是没有module.exports的,但是node加了module类
所以 : require('') ==> 指向的是module.export 而不是export
默认情况下 : module.exports = exports
正式中,我们采用的代码是这样的 : [都是采用module.export的用法]
导出 : a.js let name = 'yjx' funciton foo(){ } module.exports = { name, foo } 导出: const {name,foo} = require('./a.js') console.log(name) console.log(foo)
require的查找过程 : 导入格式如下:require(X)
1:X是一个Node核心模块,比如path、http 直接返回核心模块,并且停止查找
2:X是以 ./ 或 ../ 或 /(根目录)开头的
第一步:将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
3:直接是一个X(没有路径),并且X不是一个核心模块 [axios]
那么就会通过 node_modules 文件夹查找了
[ npm i 下载的都存在这个文件夹,会找当前目录的node_modules,没有,再找上层..上上层..直到根目录,如果没有找到,那么报错:not found]
模块的加载过程 :
CommonJS的缺点
CommonJS加载模块是同步的:
同步的意味着只有等到对应的模块加载完毕,当前模块中的内容才能被运行;
这个在服务器不会有什么问题,因为服务器加载的js文件都是本地文件,加载速度非常快;
如果将它应用于浏览器呢?
浏览器加载js文件需要先从服务器将文件下载下来,之后再加载运行;
那么采用同步的就意味着后续的js代码都无法正常运行,即使是一些简单的DOM操作;
所以在浏览器中,我们通常不使用CommonJS规范:
当然在webpack中使用CommonJS是另外一回事; 因为它会将我们的代码转成浏览器可以直接执行的代码;
在早期为了可以在浏览器中使用模块化,通常会采用AMD或CMD(使用很少),现在基本用ES Modules:
但是目前一方面现代的浏览器已经支持ES Modules,另一方面借助于webpack等工具可以实现对CommonJS或者ES Module代码的转换;
AMD和CMD已经使用非常少了;
3.AMD和CMD(了解,模块化规范[社区推出,已经不用了])
AMD主要是应用于浏览器的一种模块化规范:
AMD是Asynchronous Module Definition(异步模块定义)的缩写;
它采用的是异步加载模块;
事实上AMD的规范还要早于CommonJS,但是CommonJS目前依然在被使用,而AMD使用的较少了;
我们提到过,规范只是定义代码的应该如何去编写,只有有了具体的实现才能被应用: AMD实现的比较常用的库是require.js和curl.js;
CMD规范也是应用于浏览器的一种模块化规范:
CMD 是Common Module Definition(通用模块定义)的缩写;
它也采用的也是异步加载模块,但是它将CommonJS的优点吸收了过来;
但是目前CMD使用也非常少了;
CMD也有自己比较优秀的实现方案:SeaJS
4.ESModule用法详解(ES6标准推出来的模块化方案 重点)
采用ESModule的,默认会加上严格模式
简单导出导入
导出 : a.js export const name = "why" export const age = 18 export function sayHello() { console.log("sayHello") } 导入 import // 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js import { name, age, sayHello } from "./a.js" console.log(name) console.log(age) sayHello()
简单导入导出 -- 优化导出
导出 : a.js export const name = "why" export const age = 18 export function sayHello() { console.log("sayHello") } 导入 import // 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js import { name as fname, age as fage, sayHello as fsayHello } from "./a.js" console.log(fname) console.log(fage) fsayHello()
进阶导出和导入 :
导出 : a.js const name = "why" const age = 18 function sayHello() { console.log("sayHello") } // 导出 export export { name, age, sayHello } 导入 import // 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js import { name, age, sayHello } from "./a.js" console.log(name) console.log(age) sayHello()
进阶导出和导入 --- 优化导出
导出 : a.js const name = "why" const age = 18 function sayHello() { console.log("sayHello") } // 导出 export export { name as fname, age as fage, sayHello as fsayHello } 导入 import // 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js import { fname, fage, fsayHello } from "./a.js" console.log(fname) console.log(fage) fsayHello()
进阶导出和导入 --- 优化导入
导出 : a.js const name = "why" const age = 18 function sayHello() { console.log("sayHello") } // 导出 export export { name , age, sayHello } 导入 import // 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js import * as a from "./a.js" console.log(a.name) console.log(a.age) a.sayHello()
同时导入和同时导出的优化写法
// 优化一:export { formatCount, formatDate } from './format.js'export { parseLyric } from './parse.js' // 优化二:export * from './format.js'export * from './parse.js'
默认导出 :
前面我们学习的导出功能都是有名字的导出(named exports):
在导出export时指定了名字;
在导入import时需要知道具体的名字;
还有一种导出叫做默认导出(default export)
默认导出export时可以不需要指定名字;
在导入时不需要使用 {},并且可以自己来指定名字;
它也方便我们和现有的CommonJS等规范相互操作;
注意:在一个模块中,只能有一个默认导出(default export);
导出 : function parseLyric() { return ["歌词"] } export default parseLyric 引入 : import parseLyric from "./parse_lyric.js"
导出 : export default function() { return ["新歌词"] } 导入 : import parseLyric from "./parse_lyric.js"
通过import加载一个模块,是不可以在其放到逻辑代码中的
但是某些情况下,我们确确实实希望动态的来加载某一个模块 :
这个时候我们需要使用 import() 函数来动态加载
import函数返回一个Promise,可以通过then获取结果
let flag = true if (flag) { import("./foo.js").then(res => { console.log(res.name, res.age) }) console.log("------") }
本文来自博客园,作者:杨建鑫,转载请注明原文链接:https://www.cnblogs.com/qd-lbxx/p/16560211.html