前端面试复习4---JS 模块化基本认识
JS 模块化基本认识
背景
- js 本身定位: 开始只是简单的页面设 + 基本的表单提交,前后端不分离
- 并无模块化或者命名空间的概念
JS 的模块化需求日益增长
幼年期:无模块化(委婉的辩解),不同功能的 js 分成多个 js 文件
- 开始需要在页面中增加一些不同的 js:动画、表单、格式化工具
- 多种 js 文件被分在不同的文件中
- 不同的文件又被同一个模板所引用
- 文件分离是最基础的模块化
// 例如:test.html 页面引用多个 js 文件
<script src="jquery.js"></script>
<script src="main.js"></script>
<script src="tools.js"></script>
面试问题:script 标签两个参数: async & defer
<script src="jquery.js" async></script>
默认模式:
download-execute
------parse------ ------parse------
defer 模式(推迟执行模式):
download execute
------parse--------------------parse------
async 模式(异步下载模式):
download-execute
------parse--------------- ------parse------
总结:
- parse 与 execute 是互斥的,解析的时候不执行,执行的时候不解析
- 默认模式:解析到标签,立刻 pending,并且下载执行
- defer 模式:解析到标签开始异步下载,解析完成后开始执行
- asyn 模式:解析到标签开始异步下载,下载完成后立刻执行并阻塞渲染,执行完成后,继续渲染
- defer, asyn 参数要在 IE9+ 的浏览器才有效
- 缺点:全局的变量和方法容易被覆盖和污染,不利于大型项目的开发以及多人团队共建
成长期:模块化的雏形-IIFE(语法侧的优化)
- IIFE: Immediately Invoked Function Expression (立即调用函数表达式)
- 立即执行函数利用的其实就是作用域的把控
面试问题1:利用 IIFE 创建一个最简单的模块,和在外部调用模块内部的函数
const module = (() => {
let count = 0;
return {
increase: () => ++count;
reset: () => {
count = 0;
}
}
})();
module.increase();
module.reset();
面试问题2:有额外依赖的时候,如何优化 IIFE 的相关代码?
答:通过传参,例如下面的代码 iifeModule 模块依赖了 module1, module2 这两个模块,在立即执行的时候把 module1, module2 传进去通过 ependencyModule1, dependencyModule2 接收,这样在 iifeModule 这个模块内部就可以调用这两个模块了
const iifeModule = ((dependencyModule1, dependencyModule2) => {
let count = 0;
const obj = {
increase: () => ++count;
reset: () => {
count = 0;
}
}
// dependencyModule1.xxx
// dependencyModule2.xxx
})(module1, module2);
面试问题3:了解早期 jQuery 依赖处理和模块加载方案吗?/了解传统 IIFE 是如何解决多方依赖的问题的吗?
答:IIFE + 传参调配。实际上传统框架应用了一种 revealing 模式 的写法。
revealing 模式 -> 揭示模式
return 的是能力 = 使用方传参(x) + 本身逻辑能力 + 依赖的能力
const iifeModule = ((dependencyModule1, dependencyModule2) => {
let count = 0
const increase = () => ++count // 3. 本身逻辑能力
const reset = (val) => {
count = 0 // 3. 本身逻辑能力
// 4. 依赖的能力
// dependencyModule1.xxx
// dependencyModule2.xxx
}
// 1. return 的是能力
return {
increase,
reset
}
})(module1, module2)
iifeModule.increase(1) // 2. 使用方传参(x)
iifeModule.reset()
可能被问到的问题
// 部分开源项目分别传入全局、指针、框架作为参数
(function(window, $, undefined) {
const _show = function() {
$("#app").val("hi zhaowa");
}
window.webShow = _show;
})(window, jQuery);
// window - 1. 全局作用域转成局部作用域,执行不用全局调用,提升效率 2. 编译时候,优化压缩(function(c){}(window))
// jQuery - 1. 内部可以独立定制复写,保障稳定 2. 防止全局污染
// undefined - 防止外部重写 undefined
成熟期
1. CommonJS
- node.js 制定,在服务端和客户端都可以使用
- 在客户端需要通过 browserify 编译打包
- 通过 module + export 去对外暴露接口
- 通过 require 来调用其他模块
- 优点:率先在服务端实现了从框架层面解决依赖、全局变量污染的问题
- 缺点:针对的是服务端,对于异步依赖没有很友好的处理和考虑
服务端使用 demo
|----/modules
|----/module1.js
|----/module2.js
|----/module3.js
|----/app.js
|----/package.json
// package.json 通过 npm init 生成或者手动创建
{
"name": "demo1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"uniq": "^1.0.1"
}
}
// app.js
const uniq = require('uniq')
const module1 = require('./modules/module1')
const module2 = require('./modules/module2')
const module3 = require('./modules/module3')
module1.fn()
module2()
module3.fn1()
module3.fn2()
const list = uniq(module3.list)
console.log(list)
// module1.js
module.exports = {
msg: '---module1---',
fn() {
console.log(this.msg)
}
}
// module2.js
module.exports = () => {
console.log('---module2---')
}
// module3.js
exports.fn1 = () => {
console.log('---module3-1---')
}
exports.fn2 = () => {
console.log('---module3-2---')
}
exports.list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1]
浏览器端使用 demo
|----/js
|----/src
|----app.js
|----/module1.js
|----/module2.js
|----/module3.js
|----/index.html
|----/package.json
// package.json 通过 npm init 生成或者手动创建
{
"name": "demo2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"browserify": "^17.0.0"
},
"dependencies": {
"uniq": "^1.0.1"
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./js/dist/bundle.js"></script>
</head>
<body>
</body>
</html>
// app.js
const uniq = require('uniq')
const module1 = require('./module1')
const module2 = require('./module2')
const module3 = require('./module3')
module1.fn()
module2()
module3.fn1()
module3.fn2()
const list = uniq(module3.list)
console.log(list)
// module1.js
module.exports = {
msg: '---module1---',
fn() {
console.log(this.msg)
}
}
// module2.js
module.exports = () => {
console.log('---module2---')
}
// module3.js
exports.fn1 = () => {
console.log('---module3-1---')
}
exports.fn2 = () => {
console.log('---module3-2---')
}
exports.list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 8, 7, 6, 5, 4, 3, 2, 1]
# 导读
## 安装 browserify
1. 首先全局安装 `npm install browserify -g`
2. 再项目内安装 `npm install browserify --save-dev`
## 打包
`browserify js/src/app.js -o js/dist/bundle.js`
2. AMD require.js
- 优点:解决了浏览器中异步加载模块,可以并行加载多个模块
- 缺点:会有引入成本,没有考虑按需加载
|----/js
|----/libs
|----angular.min.js
|----jquery-2.1.4.min.js
|----require.min.js
|----/modules
|----alerter.js
|----dataService.js
|----main.js
|----index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script data-main="js/main.js" src="js/libs/require.min.js"></script>
</body>
</html>
// main.js
(function () {
requirejs.config({
// baseUrl: 'js/', // 基本路径(出发点)
paths: { // 配置路径
dataService: './modules/dataService',
alerter: './modules/alerter',
jquery: './libs/jquery-2.1.4.min',
angular: './libs/angular.min'
},
shim: {
angular: {
exports: 'angular'
}
}
})
// 异步加载+回调方法
requirejs(['alerter', 'angular'], function (alerter, angular) {
alerter.showMsg()
console.log(angular)
})
})()
// alerter.js
define(['dataService', 'jquery'], function (dataService, $) {
const msg = 'alerter.js'
function showMsg() {
console.log(msg, dataService.getName())
}
$('body').css('background', 'pink')
return { showMsg }
});
// dataService.js
define(function () {
const name = 'dataService.js'
function getName() {
return name
}
return { getName }
});
兼容判断 AMD & CJS
UMD 的出现
(define('amdModule', [], (require, export1, module) => {
// 引入部分
const dependencyModule1 = require('./dependencyModule1');
const dependencyModule2 = require('./dependencyModule2');
// 核心逻辑
let count = 0;
const increase = () => ++count;
const reset = val => {
count = 0;
// dependencyModule1.xxx
// ...
}
export1.increase = increase;
export1.reset = reset;
}))(
// 目标:一次性去区分CJS和AMD
// 1. CJS factory
// 2. module module.exports
// 3. define
typeof module === "Object" && module.exports && typeof define !== "function" ? factory => module.exports = factory(require, exports, module) : define
)
3. CMD sea.js
- 优点:按需加载,依赖就近
- 缺点:依赖于打包,加载逻辑存在于每个模块中,扩大了模块的体积
- AMD 和 CMD 区别 => 依赖就近,按需加载
|----/js
|----/libs
|----sea.js
|----/modules
|----main.js
|----module1.js
|----module2.js
|----module3.js
|----module4.js
|----main.js
|----index.html
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./js/libs/sea.js"></script>
<script>
seajs.use('./js/modules/main.js')
</script>
</body>
</html>
// main.js
define(function (require) {
const module1 = require('./module1') // 按需加载,依赖就近(不需要提前加载)
console.log(module1.getModule1())
const module4 = require('./module4')
module4.showModule4()
})
// module1.js
define(function (require, exports, module) {
const msg = 'module1'
function getMsg() {
return msg
}
module.exports = { getModule1: getMsg }
})
// module2.js
define(function (require, exports, module) {
const msg = 'module2'
function showMsg() {
console.log(msg)
}
module.exports = showMsg
})
// module3.js
define(function (require, exports, module) {
const msg = 'module3'
function showMsg() {
console.log(msg)
}
exports.module3 = { showModule3: showMsg }
})
// module4.js
define(function (require, exports, module) {
const msg = 'module4'
// 同步引入
const module2 = require('./module2')
module2()
// 异步引入
require.async('./module3', function (m3) {
m3.module3.showModule3()
})
function showMsg() {
console.log(msg)
}
exports.showModule4 = showMsg
})
4. ES6 module
- 优点:通过一种最统一的形态整合了 Js 的模块化
- 缺点:本质上还是运行时做的依赖分析
|----/src
|----/js
|----main.js
|----module1.js
|----module2.js
|----module3.js
|----index.html
|----.babelrc
|----package.json
|----readme.md
<!-- readme.md -->
# 项目说明
## 在根目录定义 .babelrc 文件
1. 在 babel 插件工作之前会去读取 .babelrc 里面的内容,根据它里面的内容去干湖
## 编译
1. `babel src/js -d build/js`
2. `browserify build/js/main.js -o dist/js/bundle.js`
// package.json
{
"name": "chyco_es6_demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"babel-preset-es2015": "^6.24.1"
},
"dependencies": {
"jquery": "^1.12.4"
}
}
// .babelrc
{
"presets": ["es2015"]
}
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="../dist/js/bundle.js"></script>
</body>
</html>
// main.js
import $ from "jquery";
import { fn1, fn2, name } from "./module1";
import { fn1 as m2fn1, fn2 as m2fn2, name as m2name } from "./module2";
import module3 from "./module3";
fn1()
fn2()
console.log(name)
m2fn1()
m2fn2()
console.log(m2name)
module3()
$('body').css('background', 'pink')
// module1.js
// 分别暴露
export function fn1() {
console.log('module1 fn1')
}
export function fn2() {
console.log('module1 fn2')
}
export const name = 'module1'
// module2.js
// 统一暴露/常规暴露
function fn1() {
console.log('module2 fn1')
}
function fn2() {
console.log('module2 fn2')
}
const name = 'module2'
export { fn1, fn2, name }
// module3.js
export default () => {
console.log('module3')
}
面试问题:ESM 动态模块
考察:export promise
懒加载ES11原生解决方案
import('./esModule.js').then((dynamicEsModule) => {
dynamicEsModule.increase()
})
转载自:https://juejin.cn/post/7262323026770952247