主题
界面
主题
《Linux 服务部署与运维》 讲的是部署;从本节起转向前端五节(HTML/CSS → JS → Ajax → Vue → Token)。
学 JS 最快的方法是"与 Java 对照" ✓ ——你已经会一门语言,只需要理解"哪里像、哪里根本不像"。
== 语义)★★;let / const 声明变量,知道为什么不用 var ★;this 差别 ★★;filter / map / reduce)★;async/await ★★;前后端分离后(《JSP 与 JSTL(技术演进理解)》),前端是一个独立的工程——而后端工程师至少要能看懂前端代码、能写简单的调试脚本 ✓
更重要的是:Node.js 让 JS 也能写服务端——全栈的最后一公里 ✓
| 维度 | Java | JavaScript | 风险 |
|---|---|---|---|
| 类型系统 | 静态强类型(编译期检查) | 动态弱类型(运行期才报错)✗ | ★★★ |
== | 值比较 | 会做类型转换 ✗ | ★★★ |
| 并发 | 多线程(Thread) | 单线程 + 事件循环 ★ | ★★ |
| 类 | 真正的 class | 基于原型(class 是语法糖) | ⚠️ |
| 数组方法 | Stream(filter/map) | filter/map(很像)✓ | — |
| 入口 | main 方法 | 事件驱动(无固定入口) | ⚠️ |
★ 本节实测(node 直接运行):
===== 1. 数组方法链 =====
6, 8, 10, 18 ← filter(n>2) → map(n*2)
===== 2. 解构与剩余参数 =====
first = 3 | rest = 1, 4, 1, 5, 9
===== 3. 类与私有字段 =====
你好,张三 ← #name 是 ES2022 的私有字段
===== 4. 可选链与空值合并 =====
结果 = 未填写 ← user?.profile?.age ?? '未填写'
===== 5. 对象简写与展开 =====
{"key":"status","value":"PAID","meta":{"source":"api"}}
===== 6. async/await =====
===== 7. 模块化 =====
async/await: await 拿到结果★ 注意第 6 与第 7 的输出顺序:"async/await: ..." 排在了最后 ✓✓
因为它是异步的——同步代码(1~5、7)先执行完,异步回调才执行 ✓
这就是 JS"单线程 + 事件循环"模型的直接体现(5.5)✓
let 与 const,不要 var var | let | const | |
|---|---|---|---|
| 作用域 | 函数 ✗ | 块 ✓ | 块 ✓ |
| 重复声明 | 允许 ✗ | 禁止 ✓ | 禁止 ✓ |
| 重新赋值 | 允许 | 允许 | ❌ 禁止 |
| 建议 | ✗ 不用 | 需要"重新赋值"时 | 默认用 ★ |
const rate = 0.8; // 引用不变(★ 对象内容仍可改)
const user = { name: 'A' };
user.name = 'B'; // ✓ 没问题(const 只锁"绑定",不锁"内容")// 普通函数:有自己的 this
function normal() { return this; }
// 箭头函数:没有自己的 this,"继承外层的" ★★
const arrow = () => this;箭头函数为什么受欢迎:this 不再"取决于谁调用" ——回调里不再丢 this ✓
class Cart {
items = [];
// ✗ 普通函数作为回调:this 丢失
bad() { this.items.forEach(function (item) { console.log(this); }); }
// ✓ 箭头函数:this 指向 Cart 实例
good() { this.items.forEach(item => console.log(this)); }
}数组方法链(与 Java Stream 极像):
const names = orders
.filter(o => o.amount > 10000)
.map(o => o.orderNo)
.join(', ');== 与 === ★★★ JS 第一坑:
1 == '1' // true ✗('1' 被转成 1)
1 === '1' // false ✓(类型不同)
null == undefined // true
null === undefined // false
NaN === NaN // false ⚠️(NaN 与任何值都不等,用 Number.isNaN)规则:一律用 === ✓(== 只在 x == null 这种"同时判 null 与 undefined"的场景偶有使用)
动态类型的代价:很多错误要到运行时才暴露 ✗
const order = { quantity: 2 };
order.quantity.toUpperCase(); // ✗ 运行时才报错(quantity 是数字)这就是 TypeScript 存在的理由(给 JS 加静态类型)——大型前端项目的标配 ✓
JS 是单线程的——同一时刻只执行一段代码 ✓
那"异步"怎么实现:靠事件循环(Event Loop) ✓
调用栈(同步代码)
↓ 清空后
任务队列(setTimeout / 回调 / Promise 的 then)
↓ 取一个
执行回调
↓ 循环实测的输出顺序:
console.log('A');
setTimeout(() => console.log('B'), 0); // ← 延迟 0ms 也要排队 ✗
Promise.resolve().then(() => console.log('C'));
console.log('D');
// 输出:A D C B
// ① 同步先跑(A、D)② 微任务(C)③ 宏任务(B)→ 为什么"异步结果要等到最后"(本节实测第 6 节的输出也在最后)✓
对后端工程师的意义:
| Java | JS | |
|---|---|---|
| 等待数据库 | 线程阻塞等待(其他请求用别的线程) | 不阻塞——发起请求后先处理别的,回调再处理 ✓ |
| 模型 | "每个请求一个线程" | "一个线程 + 事件队列" |
| 要注意 | 线程安全(《线程与同步基础》) | 不要在回调里做重活(会阻塞所有请求)✗ |
// ① 回调(Callback)—— 回调地狱 ✗
getUser(id, (err, user) => {
getOrders(user, (err, orders) => { /* 又一层 */ });
});
// ② Promise 链
getUser(id)
.then(user => getOrders(user))
.then(orders => render(orders))
.catch(handleError);
// ③ async/await ★(最像同步代码)
async function load() {
try {
const user = await getUser(id);
const orders = await getOrders(user);
render(orders);
} catch (e) { handleError(e); } // ✓ 用 try/catch 处理错误
}async/await 的价值:用"同步的写法"表达"异步的逻辑" ✓ ——错误处理也回归 try/catch ✓
⚠️ 一个易错点:await 只等待"那一个" ——两个独立的请求应该并发:
// ✗ 串行:总耗时 = a + b
const a = await fetchA();
const b = await fetchB();
// ✓ 并发:总耗时 = max(a, b)
const [a, b] = await Promise.all([fetchA(), fetchB()]);| CommonJS(CJS) | ESM | |
|---|---|---|
| 导出 | module.exports = ... | export / export default |
| 导入 | require('...') | import ... from '...' |
| 使用场景 | Node.js 传统 | 浏览器标准 / 现代 Node ✓ |
| 加载 | 运行时(同步) | 编译时(静态分析,支持 tree-shaking)✓ |
⚠️ 两者不能直接混用 ⚠️ ——Node 新版支持在 CJS 里 import()(动态导入) ✓
const sum = [1, 2, 3].reduce((a, b) => a + b, 0);
console.log(sum); // 6const orders = [
{ orderNo: 'ORD-1', amountInCents: 39800, status: 'PAID' },
{ orderNo: 'ORD-2', amountInCents: 19900, status: 'CREATED' },
];
const rows = orders
.filter(o => o.status === 'PAID')
.map(o => ({ 订单号: o.orderNo, 金额: (o.amountInCents / 100).toFixed(2) + ' 元' }));
console.log(rows);
// [ { '订单号': 'ORD-1', '金额': '398.00 元' } ]var + 回调地狱 var self = this;
getUser(id, function (err, user) {
if (err) { self.showError(err); return; }
getOrders(user, function (err2, orders) {
if (err2) { self.showError(err2); return; }
// ✗ 嵌套越深,可读性越差
});
});修:const + async/await ✓
文件:labs/frontend-demo/modern-js.js
运行:
node labs/frontend-demo/modern-js.js预期输出(见 5.1 节,7 段全部成功)✓
★ 最重要的观察:第 6 节(async/await)的输出排在第 7 节之后 ✓ ——同步代码先执行完,异步回调才执行 ✓
console.log(typeof x):随时查看类型(动态类型的排错基本手段)✓;JSON.stringify(obj, null, 2):带缩进打印对象 ✓;node --inspect-brk + Chrome DevTools(前端也有断点调试)✓;== 与 === 对比:跑 5.4 的例子,亲手确认 ✓。| # | 错误 | 说明 |
|---|---|---|
| 1 | == 类型转换 ✗ | 用 === ✓ |
| 2 | 回调里 this 丢失 | 用箭头函数 ✓ |
| 3 | 修改 const 对象以为会报错 | const 只锁绑定,内容可变 ⚠️ |
| 4 | 串行 await 并发的请求 | 用 Promise.all ✓ |
| 5 | undefined 上取属性 ✗ | TypeError ——用 ?. 可选链 ✓ |
| 6 | var 的函数作用域 | 用 let/const ✓ |
==:跑 1 == '1'、null == undefined 等,记录结果 ✓;const 对象可变:const a = {}; a.x = 1; 观察不报错;a = {} 报错 ✓;null?.foo 观察返回 undefined(不报错)✓;setTimeout(100ms),分别用串行 await 与 Promise.all,对比总耗时 ✓。扩展任务:用 Node 写一个"订单统计"脚本:读一个 JSON 文件(订单数组),输出"总金额、按状态分组的数量、最大的一笔"——用数组方法链与解构 ✓。
null == undefined 是 false。(对 / 错)this 丢失"的问题?const [a, , c] = [1, 2, 3] 之后 a、c 是什么?[{name:'A',tags:['x','y']},...] 转成"标签 → 出现次数"的统计对象(用 flatMap + reduce)。null == undefined 是 true(特例)✗——这也是要用 === 的理由之一 ✓=== ✓this ——它使用"定义时所处作用域"的 this ✓ 所以不会随调用者变化 ✓其余要点见上面的分析框架("像与不像"对照表 是解题的钥匙)✓
==)★★;const,不用 var;async/await,知道 Promise.all 的并发价值;《Ajax 与前后端交互》:前端怎么把"数据请求"发出去。