1716 字
约 5 分钟
11
S14-J06 · JS、Tomcat/Servlet 与前端工程化:从脚本到构建产物

适用读者:会写页面但不清楚"打包到底做了什么"的人;要解释 SPA 与后端分工的人。
前置:S14-J05、B05(工程化入门)、S02-B04(HTTP)。
覆盖旧文:4432(JavaScript 79k)、4433(XML/Tomcat/HTTP 26k)、4434(Servlet 70k)、4435(会话 59k)、4436(前端工程化 154k)、4437(微头条项目 85k)、4404(笔记 21k)→ 7 篇 / 约 52 万字符。
扩展点:E-S14-020(JS 的四类易错语义:提升、this、闭包、异步顺序)、E-S14-021(Servlet 容器与 SPA 的三种部署形态)、E-S14-022(构建产物的四步:解析→转换→打包→压缩,及各自的失效点)。
上一篇:S14-J05 · HTML/CSS 到布局:盒模型、Flex 与移动端适配
下一篇:补编第 3 篇 · Vue 2/3 笔记合并(待写)。


1. 一句话结论

前端的"魔法"只有两处真实机制:JS 的执行上下文与事件循环(决定代码何时跑、this 是谁),构建工具的模块图与产物(决定代码怎么变成文件)。其余都是约定与工具差异。

出处:A-4432(JS)、A-4434(Servlet)、A-4436(工程化)、A-4437(项目)。


2. JS 的四类易错语义(E-S14-020)

2.1 声明提升与 TDZ

console.log(a);   // undefined —— var 提升但未赋值
var a = 1;
console.log(b);   // ❌ ReferenceError —— let/const 有暂时性死区(TDZ)
let b = 2;

2.2 this 的四种绑定(按优先级)

// ① new 绑定 > ② 显式绑定(call/apply/bind)> ③ 隐式绑定(obj.fn())> ④ 默认(严格模式 undefined)
const obj = { v: 1, get() { return this.v; } };
const g = obj.get;
g();                      // undefined(默认绑定,非严格模式为 window)
obj.get();                // 1
g.call({ v: 2 });         // 2
const arrow = { v: 3, get: () => this.v };  // 箭头函数无自己的 this → 词法作用域

坑 1:箭头函数没有自己的 this,obj.get = () => this.v 取的是外层 this。

2.3 闭包:作用域而不是"函数里的函数"

function counter() { let n = 0; return () => ++n; }   // n 被闭包持有
const c = counter(); c(); c();                         // 1, 2
用途 例
私有状态 上例
循环 + 异步捕获 for (let i…)(let 每次迭代新绑定)vs var(共享,全都打印末值)
内存泄漏风险 闭包持有大对象 + 长生命周期引用

2.4 异步顺序:微任务优先

console.log('1');
setTimeout(() => console.log('4'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('2');
// 输出:1 2 3 4(微任务先于宏任务)

判据:事件循环 = 调用栈清空 → 清空微任务队列 → 取一个宏任务;await 之后的代码等价于微任务。


3. Servlet 容器与 SPA 的三种部署形态(E-S14-021)

形态 结构 适用 注意
同源同容器 前端产物打进 webapp/,与 Servlet 同端口 小项目、内网工具 刷新路由 404(需 history fallback)
同域反代(推荐) nginx 静态托管 + /api/ 反代后端 博客/中台(即本站形态) try_files $uri /index.html 兜底
跨域分离 独立域名 + CORS 前后端团队独立部署 预检、Cookie SameSite、凭据跨域
# 同域反代的正确兜底(SPA history 模式)
location / { try_files $uri $uri/ /index.html; }
location /api/ { proxy_pass http://127.0.0.1:8080/; }

Servlet 侧四条基础(4433/4434/4435 的压缩):

点 结论
生命周期 init → service(多次)→ destroy;默认单实例多线程,不要在 Servlet 里放可变实例字段
请求/响应 HttpServletRequest/Response,编码 setCharacterEncoding("UTF-8") 要在读参数前
会话 HttpSession 靠 JSESSIONID Cookie;分布式要共享(Redis)或改无状态 JWT
转发 vs 重定向 forward 服务端一次请求;sendRedirect 两次请求、地址栏变

4. 构建产物四步与失效点(E-S14-022)

① 解析(resolve):入口 → 依赖图(node_modules、别名、扩展名)
② 转换(transform):TS/JSX → JS,CSS 预处理 → CSS,语法降级(target)
③ 打包(bundle):拆 chunk、tree-shaking、代码分割
④ 压缩(minify):压缩 + gzip/brotli 预压缩 + 内容哈希命名
步骤 常见失效 症状
解析 别名/扩展名未配 Module not found(本地能跑因为缓存)
转换 target 过低/过高 老浏览器白屏 / 新语法报错
打包 未配 sideEffects tree-shaking 失效,包体虚大
压缩 只压 JS 不压 CSS/HTML 首屏仍慢
全部 未加内容哈希 用户拿到旧缓存("改了没生效")

打包分析的固定动作:

npx vite build --mode production
npx vite-bundle-visualizer          # 或 rollup-plugin-visualizer
# 关注三件事:① 最大的 3 个 chunk ② 是否重复打包同一库 ③ 是否把整库当默认导入

判据:"包体大"先查是否整库导入(import _ from 'lodash' vs import debounce from 'lodash/debounce'),再查重复依赖,最后才考虑换工具。


5. 工程化的四条落地约定

约定 做法 收益
目录分层 src/{pages,components,composables,stores,utils} 可预测,新人易上手
路径别名 @ → src 消除 ../../..
环境变量 import.meta.env.VITE_*(只暴露前缀变量) 避免密钥进产物
质量闸门 ESLint + Prettier + Vitest + CI 提交前挡低级错误(见 S14-X02 的 Biome)

6. 反例:五种"本地正常、线上异常"

现象 根因
刷新 404 SPA history 模式缺 fallback
接口 404 反代丢 /api 前缀(proxy_pass 末尾斜杠差异)
白屏无报错 target 过高,老浏览器不支持
改了没生效 产物名无内容哈希 + CDN 缓存
密钥泄露 环境变量未加 VITE_ 前缀却被打进代码 / 或被 console.log

7. 自测

  1. TDZ 是什么?var 与 let 的差别?
  2. this 四种绑定与优先级?箭头函数为何特殊?
  3. 事件循环里微任务与宏任务的顺序?
  4. SPA 三种部署形态?nginx 兜底怎么写?
  5. 构建四步与各自失效点?
答案
  1. let/const 在声明前不可访问(报错);var 提升为 undefined。
  2. new > 显式 > 隐式 > 默认;箭头函数无自身 this,取词法作用域。
  3. 栈清空后先清微任务队列,再取一个宏任务。
  4. 同容器 / 同域反代 / 跨域分离;try_files $uri $uri/ /index.html。
  5. 解析/转换/打包/压缩;模块找不到、语法不兼容、包体过大、缓存不更新。

8. 下一篇

补编第 3 篇 · Vue 2/3 笔记合并 —— Options 与 Composition 的真实差异、ref/reactive 的响应式边界、Vuex 与 Pinia 的取舍、路由守卫与项目结构。


本篇属于系列《S14 · 前端与可视化》。旧文来源:A-4432–4437、A-4404;扩展点 E-S14-020/021/022。

S14-J06 · JS、Tomcat/Servlet 与前端工程化:从脚本到构建产物
https://www.clxhxhhr.top/posts/5382/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。