目录:
1 认识模块化开发
2 CommonJS和Node
3 require函数解析
4 AMD和CMD(了解)
5 ESModule用法详解
6 ESModule运行原理
模块化不是两个不同的js文件直接导入到某个页面中的,因为这两个文件只要有相同的变量或函数,两个js文件导入到某个页面的时候变量和函数都会出现冲突、覆盖。
真正的模块化就是4点:
早期是没有模块化开发的,解决办法是使用 '立即执行函数'(因为变量放在函数里面,函数有自己的作用域,这个变量名和其他函数里面的没有任何关系,不冲突)
使用 '立即执行函数' 有个缺点就是拿不到函数里面的变量,解决办法是把变量return出去。其他页面拿变量的方法就是 立即执行函数的名称.变量名称。
由于使用 '立即执行函数'的缺点是在js文件里面的立即执行函数的数量多且名称都不一样,很混乱。
怎么使用common.js规范?
1、在浏览器中默认是不能使用export的。在vue中可以是因为webpack打包。
2、在node执行js文件是可以使用export的,node有集成common.js
common.js在node里面实现的原理就是引用赋值。就是说export导出的是一个对象,导出的是对象在内存中的地址,require导入的也就是变量的地址了,不管是导出的文件修改值还是导入的文件修改值都会使得变量的值在内存中的值改变。
在开发的时候常用的写法是module.exports = { xxx:xxx , yyy:yyy } 这里xxx和yyy可以使用增强写法module.exports = { xxx , yyy }
module.exports={}的写法与exports.xxx=xxx 和module.exports.xxx=xxx 的不同之处是module.exports={}会在内存中新开辟一个地址来存储变量,也就是原来的文件里面的变量发生改变也不会改变这里的变量值,exports.xxx=xxx 和module.exports.xxx=xxx引用的是原来的内存地址。
举个例子:
exports.xxx=xxx 和module.exports.xxx=xxx指向的是下图的0*100的地址,
module.exports = { xxx:xxx , yyy:yyy }指向的是0*200的地址
const name = "foo" const age = 18 function sayHello() { console.log("sayHello") } // 1.在开发中使用的很少 // exports.name = name // exports.age = age // exports.sayHello = sayHello // 2.将模块中内容导出 // 结论: Node导出的本质是在导出module.exports对象 // module.exports.name = name // module.exports.age = age // module.exports.sayHello = sayHello // // console.log(exports.name, "----") // // console.log(exports.age, "----") // // console.log(exports.sayHello, "----") // console.log(exports === module.exports) // 3.开发中常见的写法 module.exports = { name, age, sayHello } // exports.name = "哈哈哈哈" // module.exports.name = "哈哈哈哈"
require是有自己的查找规则的:
1、(会自动加后缀)会先找文件,再去找文件夹,然后在找json文件,再再再找node文件
2、会去核心模块查找
3、会到当前文件夹的node_modules里面查找,如果没有就会去上层目录去找node_modules文件夹
// 1.根据路径导入自己编写模块 // const utils = require("./utils") // console.log(utils.formatDate()) // const foo = require("./foo") // 2.导入node提供给内置模块 // const path = require("path") // const http = require("http") // console.log(path, http) // 3.情况三: 名称不是路径, 也不是一个内置模块 // const why = require("why") // console.log(why) // const axios = require("axios") // console.log(axios) console.log(this)
common.js 模块的加载过程:
1、模块在被第一次引入时,模块中的js代码会被运行一次
2、模块被多次引入时,会缓存,最终只加载(运行)一次
3、如果模块里面需要导入其他模块,在运行的时候会以深度遍历的情况把最里层需要导入的文件先运行完。如下图的引用模块的顺序是:main -> aaa -> ccc -> ddd -> eee ->bbb
commonjs的缺点:
在浏览器中运行是会影响很大,因为是同步的,在模块中的需求模块没被记载完成之前,外层的代码都不会执行。但是在webpack里面就不会出现这种情况,因为项目打包之后会把所有这些模块代码打包到一个js文件里面(如果没有分包操作)。
以前在webpack里面模块化主要是commonjs。在浏览器中是amd和cmd,现在这两个已经没有使用了。
ES Module:
目前可以通过esmodule或者commonjs实现模块化。两者的区别是esmodule需要浏览器支持才能用。webpack是打包出来变成普通的js文件,基本上大多数地方都可以直接用。
默认是严格模式的。
esmodule的import导入的文件名字必须写全,不像commonjs一样可以省略后缀(因为commonjs会自动添加后缀),
ES Module的使用方法:
页面导入需要的js文件 <script src="xxx" type="module"></script>
不能直接通过打开页面文件的方式来打开,会出现跨域的错误,这个时候需要通过vscode右键打开菜单选择在一个服务上运行才不会报跨域。
通过直接打开这个页面(对应vscode的 Open In Default Browser):
通过服务打开这个页面(对应vscode的 Open With Live Server):
foo.js代码:
const name = "why" const age = 18 function sayHello() { console.log("sayHello") } // 导出 export 这里不是用了对象增强的写法,这里的name就是变量名称 export { name, age, sayHello }
main.js代码:
// 导入 import // 注意事项一: 在浏览器中直接使用esmodule时, 必须在文件后加上后缀名.js import { name, age, sayHello } from "./foo.js" // const name = "main" console.log(name) console.log(age) sayHello()
ES Module的导出导出方式有三种:
1、普通导出,导入的时候名称必须和导出的名称相同
2、导出时给个别变量取别名
3、直接在创建变量的时候导出变量
// 3.导出方式三: export const name = "why" export const age = 18 export function sayHello() { console.log("sayHello") } export class Person {} // console.log(name) // 1.导出方式一: // export { // name, // age, // sayHello // } // 2.导出方式二: 导出时给标识符起一个别名 // export { // name as fname, // age, // sayHello // }
// 1.导入方式一: // import { name, age, sayHello } from "./foo.js" // 2.导入方式二: 导入时给标识符起别名 // import { name as fname, age, sayHello } from "./foo.js" // 3.导入时可以给整个模块起别名 import * as foo from "./foo.js" const name = "main" console.log(name) console.log(foo.name) console.log(foo.age) foo.sayHello()
export和import结合使用:
这种情况应用的场景是 模块文件夹里面有很多模块,我们会创建一个index.js的文件来当做入口文件,在index.js文件里面写入 模块文件夹里面所有模块导出的内容。在之后的某个文件需要使用时就直接去index.js引用就可以了。
写法:export {xxx} from "xxxx"
format.js和parse.js是模块化的文件,utils是模块文件夹,index.js是入口文件,main.js是导入入口文件的文件
format.js代码:
export function formatCount() { return "200万" } export function formatDate() { return "2022-11-11" }
parse.js代码:
export function parseLyric(lyricString) { return ["歌词"] }
index.js代码:
import { formatCount, formatDate } from './format.js' import { parseLyric } from './parse.js' // export { // formatCount, // formatDate, // parseLyric // } // 优化一: (常用的) // export { formatCount, formatDate } from './format.js' // export { parseLyric } from './parse.js' // 优化二: (优点是方便快速导出所有导出的内容) //(缺点是不知道引入的内容有什么,需要回到文件去查看导出了什么) // export * from './format.js' // export * from './parse.js'
main.js代码:
// import { formatCount, formatDate } from "./utils/format.js" // import { parseLyric } from "./utils/parse.js" import { formatCount, formatDate, parseLyric } from './utils/index.js' console.log(formatCount()) console.log(formatDate()) console.log(parseLyric())
页面导入main.js文件:
默认导出 export default
注意:在一个模块中,只能有一个默认导出(default export),导入时可以任意给名称。
parse_lyric.js是默认导出的文件,main.js是导入模块的文件。
parse_lyric.js代码:
// 1.默认的导出: // // 1.1. 定义函数 // function parseLyric() { // return ["歌词"] // } // const name = "aaaa" // // export { // // parseLyric, // // name // // } // 1.2.默认导出 // export default parseLyric // 2.定义标识符直接作为默认导出 export default function() { return ["新歌词"] } // export default function() { // return ["歌词"] // } // 注意事项: 一个模块只能有一个默认导出
main.js代码:
// import { parseLyric } from "./parse_lyric.js" // 下面的名称parseLyric是不一定的,可以取别的名称,不会有影响, import parseLyric from "./parse_lyric.js" // 之后要引用的话必须和导入时的名称引用 console.log(parseLyric())
import函数的使用(是异步的):
模块的导入在文件里面是不可以放在逻辑代码里面的,只能放在文件顶部:
但是确实有情况需要我们判断完之后才引入模块的话,我们该怎么办?(有这种需求是因为加载不必要的js文件也一样需要消耗性能,文件大了,加载时间也长;还有一种应该是做
条件判断添加路由路径的时候)
foo.js代码:
export const name = "foo" export const age = 18 export function sayHello() { console.log("sayHello") } console.log(import.meta)
main.js代码:
import { name, age, sayHello } from "./foo.js" console.log(name, age) // 2.import函数的使用 // let flag = true // if (flag) { // // 不允许在逻辑代码中编写import导入声明语法, 只能写到js代码顶层 // // import { name, age, sayHello } from "./foo.js" // // console.log(name, age) // // 如果确实是逻辑成立时, 才需要导入某个模块 // // import函数返回的是promise // // const importPromise = import("./foo.js") // // importPromise.then(res => { // // console.log(res.name, res.age) // // }) // 下面这种是最常见的 import("./foo.js").then(res => { console.log(res.name, res.age) }) // console.log("------") // }
import.meta可以获取import的url地址
1