背景
JS 最开始的定位为:简单的页面设计 - 简单动画 + 基本的表格提交(1995年网景耗时2周开发出来的)
并无模块化或命名空间的概念
后面前端发展越来越复杂,就对 JS 提出了“模块”的要求
模块化的阶段
幼年期:无模块化
通过多个 JS 文件来处理,写多个<script src="xxx"/> 来强行分隔
<!DOCTYPE html>
<html>
<head>
<title>xxx</title>
</head>
<body>
<script src="jquery.js"></script>
<script src="tool.js"></script>
<script src="main.js"></script>
</body>
</html>
存在的问题:
- 污染全局作用域
-
- 因为都在同一个 HTML 里面引入,将污染全局作用域,存在变量名冲突等问题
- 不利于大型项目开发与多人团队共建
成长期:雏形-IIFE
IIFE:立即执行函数(语法侧优化),是模块化的基石
优势:作用域的封装,因为是函数所以有自己的作用域
(function(_w){
console.log(_w) // 打印出来的是:传入的 Window 对象
})(window)
使用 IIFE 实现一个简单的模块
const module = (function(){
let count = 0
// 主流程功能
const add = () => count++
const reset = () => count = 0
const get = () => count
// 对外暴露接口
const returnApi = {
add,
reset,
get
}
return returnApi
})()
// module 为 {add: ƒ, reset: ƒ, get: ƒ}
成熟期:模块化爆发
CJS - CommonJS
来自于服务端(Nodejs)定义的模块化加载和导出规范(同步加载)
// 导出 exports
// tool.js
exports.add = (num) => num + 1
exports.reset = () => 0
// 加载 require
// main.js
const tool = require('./tool.js')
tool.add(5)
tool.reset()
其中通过require导入,通过exports或module.exports导出
其中的关系为:
require引入的是module.exports导出的
正常情况下exports === module.exports // true
异常情况
// 导出 exports
// tool.js
exports.add = (num) => num + 1
exports.reset = () => 0
module.exports = {
log(num){
console.log(num)
}
}
// 加载 require
// main.js
const tool = require('./tool.js')
tool.add(5) // 报错
tool.reset() // 报错
tool.log(77) // 正常
优点:
- 同步加载,易于理解代码执行
缺点:
- 同步加载大量文件时,会阻塞
- 因为是同步加载,所以无法按需异步加载
- 没有语言层面的支持: CommonJS 不是 JavaScript 的语言层面的特性,而是一种规范。相比之下,ES6 模块是语言层面的特性,得到了更好的集成和支持。
AMD - Asynchronous Module Definition(异步模块定义)
针对浏览器端的模块加载规范,支持异步加载,不阻塞页面渲染
著名的框架为 require.js
// 定义模块
define('moduleA',['dep1', 'dep2'], function(dep1, dep2){
return {
log(){
dep1.a++
dep2.b++
console.log(dep1.a)
console.log(dep2.b)
}
}
})
// 使用模块
require(['moduleA'], function(moduleA) {
moduleA.log()
})
优点:
- 异步加载
- 提高了模块化开发
缺点:
- 语法繁琐
- 需要显示声明依赖
UMD - Universal Module Definition(通用模块定义)
UMD 是兼容多种模式(CJS/AMD 等)的模块加载器
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['dependency'], factory);
} else if (typeof exports === 'object') {
// CommonJS
module.exports = factory(require('dependency'));
} else {
// 浏览器全局变量
root.MyModule = factory(root.Dependency);
}
}(this, function (Dependency) {
// 模块的实际定义
function MyModule() {
// ...
}
// ...
return MyModule;
}));
CMD - Common Module Definition(同步模块加载)
强调模块的加载与使用是同时的
著名框架 sea.js
define('module1', (require, exports, module) => {
let $ = require('jquery.js') // 模块加载
// $('.name').style.xxx 模块使用
const getDom = (selector) => $(selector)
module.exports = { getDom } // 模块 API 的暴露
})
优点:
- 对依赖的加载可控,能达到“按需加载”,依赖就近
缺点:
- 不支持异步加载
新时代:官方支持
ESM - ES6 module
ES6 提供的模块加载机制
// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// main.js
import { add, subtract } from './math';
console.log(add(3, 4)); // 输出 7
console.log(subtract(8, 5)); // 输出 3
- 导出: 使用
export关键字导出模块的功能。 - 导入: 使用
import关键字导入其他模块的功能。 - 命名空间导入: 使用
import * as aliasName from 'module';来导入整个模块的命名空间。 - 默认导出: 使用
export default来指定一个模块的默认导出,可以使用import moduleName from 'module';进行导入。 - 动态导入: 使用
import()来动态加载模块,返回一个 Promise
优点:
- 模块文件有自己的作用域,不会污染全局
- 默认使用严格模式
缺点:
- 兼容性,低版本浏览器不一定兼容
模块化的目的
- 隔离逻辑与作用域
- 扩展协同的方便度
最终形成万物皆模块,作为前端工程化的基石
其他知识
严格模式
作用:使 JS 更安全,减少不确定性
使用:'use strict'
特点:
- 禁止使用未声明的变量: 在严格模式下,如果使用未声明的变量,将抛出 ReferenceError。
- this 的值为 undefined: 在严格模式下,如果函数不是作为对象的方法调用,this 的值将为 undefined。
调用栈
使用new Error().stack可以获取
function logCaller() {
console.log(new Error().stack);
}
function outer() {
logCaller();
}
outer();
// 打印结果:
// Error
// at logCaller (<anonymous>:2:17)
// at outer (<anonymous>:6:5)
// at <anonymous>:9:1
组件库搭建所需
面试题
1. script 标签的参数:async、defer
都是用于控制脚本执行时机
- 加载行为:
-
- async 和 defer 都不会阻塞页面渲染,允许页面继续加载。
- 执行时机:
-
- async:脚本加载完成后立即执行,与页面加载和其他脚本执行顺序无关。
- defer:按照它们在页面上出现的顺序执行,但会在文档解析完成后、DOMContentLoaded 事件触发前执行。
- 依赖关系:
-
- async:适用于相互独立、无依赖关系的脚本。
- defer:适用于有顺序依赖关系的脚本。
2. JQuery 源码-依赖处理
IFEE + 传参调配
(function(window, undefined ) {
// 用一个函数域包起来,就是所谓的沙箱
// 在这里边var定义的变量,属于这个函数域内的局部变量,避免污染全局
// 把当前沙箱需要的外部变量通过函数参数引入进来
// 只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数
"use strict";
window.jQuery = window.$ = jQuery;
})( window );
3. 一行代码如何兼容 AMD、CJS?
AMD 关键:define
CJS 关键:module.exports
(factory => {
})(typeof module === 'objetc' && module.exports && typeof define === 'undefined' ?
cjsFactory : amdFactory)