现代化项目
- 要求前端有独立的项目
- 采用数据驱动开发的方式增强可维护性
- 复杂项目结构必须进行模块化管理
- 重复规律性工作必须采取自动化工具实现
模块化的演进过程
-
文件划分方式
web
最原始的模块系统,每个功能及相关状态数据单独放在不同js
文件中,约定每个文件是一个独立的模块├── module-a.js ├── module-b.js └── index.html
缺点:
- 模块直接在全局工作,污染全局作用域
- 没有私有空间,模块内成员可以在外部被访问或修改
- 模块增多,容易产生命名冲突
- 无法管理模块与模块之间依赖关系
- 维护过程中很难分辨每个成员所属的模块
-
命名空间方式
约定每个模块只暴露一个全局对象,将每个模块包裹到一个全局对象
window.moduleA = { method1() { console.log('a') } }
只解决了命名冲突问题,其他问题依旧存在
-
IIFE
立即执行函数为模块提供私有空间,将每个模块放在立即执行函数形成的私有作用域,需暴露的成员,通过全局对象挂载
(function() { const name = 'a' function method1 () { console.log('a') } window.moduleA = { method1 } })()
解决全局污染和命名冲突问题
-
IIFE
依赖参数使用
IIFE
参数作为依赖声明使用 使模块之间依赖关系更明显(function ($) { // 通过参数明显表明这个模块的依赖 var name = 'module-a' function method1 () { console.log(name + '#method1') $('body').animate({ margin: '200px' }) } window.moduleA = { method1: method1 } })(jQuery)
模块加载问题
时间久、项目大时难维护
更为理想的页面引入一个JS
文件入口,用到的模块通过代码控制、按需加载
模块化标准规范
-
nodejs
中遵循commonJS
规范来组织模块- 约定一个文件就是一个模块
- 每个模块有单独的作用域
- 通过module.exports导出成员
- 通过require函数载入模块
- 浏览器环境中遵循ES Modules(ES6)规范
差异:CommonJs以同步模式加载模块,启动时加载模块,而执行当中不需要再加载,浏览器端使用会导致效率低下;早期浏览器使用AMD(Asynchronous Module Definition)规范,推出Require.js实现AMD规范
-
定义模块
// 定义一个模块 通过define函数去定义 // param 模块名称,后期加载模块使用 数组,模块依赖项 函数,依赖项的导出成员一一对应 // 模块需要导出成员,通过retuen方式实现 define('module1', ['jquery', './module2'], function($, module2) { return { start: function () { $('body').animate({ margin: '200px' }) module2() } } })
-
载入模块
// 载入一个模块 require(['module1'], function (module1) { module1.start() })
AMD缺陷:
- AMD使用起来相对复杂
- 模块JS文件请求频繁
ES Module特性
ES Module模块需要使用http serve模式运行,直接运行html文件会导致跨域,browser-sync --files *./.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>ES Module - 模块的特性</title>
</head>
<body>
<!-- 通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了 -->
<script type="module">
console.log('this is es module') // this is a module
</script>
<!-- 1. ESM 自动采用严格模式,忽略 'use strict' -->
<script type="module">
console.log(this) // undefined 非严格模式下是Window对象
</script>
<!-- 2. 每个 ES Module 都是运行在单独的私有作用域中 -->
<script type="module">
var foo = 100
console.log(foo)
</script>
<script type="module">
console.log(foo) // foo is not defined
</script>
<!-- 3. ESM 是通过 CORS 的方式请求外部 JS 模块的,引入的文件必须支持CORS -->
<script
type="module"
src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"
></script>
<!-- 4. ESM 的 script 标签会延迟执行脚本,等待网页渲染完成之后再去执行脚本,相当于defer属性 -->
<script type="module" src="demo.js"></script>
<p>需要显示的内容</p>
</body>
</html>
ES Module导入和导出
-
模块内的成员都存在与私有作用域中,外部访问需要暴露
var name = 'foo module' function hello() { console.log('hello') } class Person {} // 重命名 name as fooName // export { // name as default, // hello as fooHello // } // 模块默认导出 // export default name // import name from './module.js' 引入 // var obj = { name, hello, Person } // 可以导出变量、函数、类 放在最后 比较直观 export { name, hello, Person }
-
导入导出注意事项
// 导出 // 这里的 `{ name, hello }` 不是一个对象字面量, // 它只是语法上的规则而已 export { name, age } // export name // 错误的用法 // export 'foo' // 同样错误的用法 setTimeout(function () { name = 'ben' }, 1000) // 导入 // CommonJS 中是先将模块整体导入为一个对象,然后从对象中结构出需要的成员 // const { name, age } = require('./module.js') // ES Module 中 { } 是固定语法,就是直接提取模块导出成员 import { name, age } from './module.js' console.log(name, age) // 导入成员并不是复制一个副本,而是直接导入模块成员的引用地址 // 也就是说 import 得到的变量与 export 导入的变量在内存中是同一块空间。 // 一旦模块中成员修改了,这里也会同时修改, setTimeout(function () { console.log(name, age) }, 1500) // 导入模块成员变量是只读的 // name = 'tom' // 报错 // 但是需要注意如果导入的是一个对象,对象的属性读写不受影响 // name.xxx = 'xxx' // 正常 // 1.需要填写完整名称 import { name } from './module.js' // 2.不能省略.js或index.js import { lowercase } from './utils/index.js' // 3.不能省略./ 否则ES Module认为再加载第三方模块 import { name } from 'module.js' import { name } from './module.js' import { name } from '/04-import/module.js' import { name } from 'http://localhost:3000/04-import/module.js' // 4.加载模块 并不提取 import './module.js' // 5.导入全部 通过mod.xx拿到其中变量 import * as mod from './module.js' console.log(mod.xxx) // 6.动态导入模块 Promise对象 import('./module.js').then(function (module) { console.log(module) }) // 7.同时导出默认成员命名成员 import abc, { name, age } from './module.js' console.log(name, age, abc)
-
导入导出成员
// import { Button } from './button.js' // import { Avatar } from './avatar.js' // export { Button, Avatar } // default导出必须重命名 会作为当前index的默认导出 export { default as Button } from './button.js' export { Avatar } from './avatar.js'
-
Polyfill兼容方案
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>ES Module 浏览器环境 Polyfill</title> </head> <body> <!-- Promise polyfill --> <!-- nomodule 在支持ES Module的浏览器回执行两次 nomodule只会在不支持ES Module的浏览器使用 --> <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> <!-- es-module-loader读取代码 通过babel去转换 --> <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script> <script type="module"> import { foo } from './module.js' console.log(foo) </script> </body> </html>
网友评论