Skip to content

JavaScript 基础 ​

S3-037必学入门约 90 分钟JDK 21企业开发基础 · 前端基础与 Vue

1. 本节定位 ​

《Linux 服务部署与运维》 讲的是部署;从本节起转向前端五节(HTML/CSS → JS → Ajax → Vue → Token)。

学 JS 最快的方法是"与 Java 对照" ✓ ——你已经会一门语言,只需要理解"哪里像、哪里根本不像"。

2. 学习目标 ​

  • 说出 JS 与 Java 的三个根本差异(类型系统、并发模型、== 语义)★★;
  • 用 let / const 声明变量,知道为什么不用 var ★;
  • 会用箭头函数,并知道它与普通函数的 this 差别 ★★;
  • 会用数组方法链(filter / map / reduce)★;
  • 理解"单线程 + 事件循环"的异步模型 ★★★;
  • 会用 async/await ★★;
  • 知道 ESM 与 CommonJS 两种模块体系。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

前后端分离后(《JSP 与 JSTL(技术演进理解)》),前端是一个独立的工程——而后端工程师至少要能看懂前端代码、能写简单的调试脚本 ✓

更重要的是:Node.js 让 JS 也能写服务端——全栈的最后一公里 ✓

5. 核心概念 ​

5.1 JS 与 Java 的"像与不像" ​

维度JavaJavaScript风险
类型系统静态强类型(编译期检查)动态弱类型(运行期才报错)✗★★★
==值比较会做类型转换 ✗★★★
并发多线程(Thread)单线程 + 事件循环 ★★★
类真正的 class基于原型(class 是语法糖)⚠️
数组方法Stream(filter/map)filter/map(很像)✓—
入口main 方法事件驱动(无固定入口)⚠️

★ 本节实测(node 直接运行):

text
===== 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)✓

5.2 变量:let 与 const,不要 var ​

varletconst
作用域函数 ✗块 ✓块 ✓
重复声明允许 ✗禁止 ✓禁止 ✓
重新赋值允许允许❌ 禁止
建议✗ 不用需要"重新赋值"时默认用 ★
javascript
const rate = 0.8;        // 引用不变(★ 对象内容仍可改)
const user = { name: 'A' };
user.name = 'B';          // ✓ 没问题(const 只锁"绑定",不锁"内容")

5.3 函数与箭头函数 ★★ ​

javascript
// 普通函数:有自己的 this
function normal() { return this; }

// 箭头函数:没有自己的 this,"继承外层的" ★★
const arrow = () => this;

箭头函数为什么受欢迎:this 不再"取决于谁调用" ——回调里不再丢 this ✓

javascript
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 极像):

javascript
const names = orders
  .filter(o => o.amount > 10000)
  .map(o => o.orderNo)
  .join(', ');

5.4 == 与 === ★★★ ​

JS 第一坑:

javascript
1 == '1'              // true  ✗('1' 被转成 1)
1 === '1'             // false ✓(类型不同)
null == undefined     // true
null === undefined    // false
NaN === NaN           // false ⚠️(NaN 与任何值都不等,用 Number.isNaN)

规则:一律用 === ✓(== 只在 x == null 这种"同时判 null 与 undefined"的场景偶有使用)

动态类型的代价:很多错误要到运行时才暴露 ✗

javascript
const order = { quantity: 2 };
order.quantity.toUpperCase();      // ✗ 运行时才报错(quantity 是数字)

这就是 TypeScript 存在的理由(给 JS 加静态类型)——大型前端项目的标配 ✓

5.5 异步模型:单线程 + 事件循环 ★★★ ​

JS 是单线程的——同一时刻只执行一段代码 ✓

那"异步"怎么实现:靠事件循环(Event Loop) ✓

text
调用栈(同步代码)
    ↓ 清空后
任务队列(setTimeout / 回调 / Promise 的 then)
    ↓ 取一个
执行回调
    ↓ 循环

实测的输出顺序:

javascript
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 节的输出也在最后)✓

对后端工程师的意义:

JavaJS
等待数据库线程阻塞等待(其他请求用别的线程)不阻塞——发起请求后先处理别的,回调再处理 ✓
模型"每个请求一个线程""一个线程 + 事件队列"
要注意线程安全(《线程与同步基础》)不要在回调里做重活(会阻塞所有请求)✗

5.6 异步的三种写法(演进) ​

javascript
// ① 回调(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 只等待"那一个" ——两个独立的请求应该并发:

javascript
// ✗ 串行:总耗时 = a + b
const a = await fetchA();
const b = await fetchB();

// ✓ 并发:总耗时 = max(a, b)
const [a, b] = await Promise.all([fetchA(), fetchB()]);

5.7 模块化:ESM 与 CommonJS ​

CommonJS(CJS)ESM
导出module.exports = ...export / export default
导入require('...')import ... from '...'
使用场景Node.js 传统浏览器标准 / 现代 Node ✓
加载运行时(同步)编译时(静态分析,支持 tree-shaking)✓

⚠️ 两者不能直接混用 ⚠️ ——Node 新版支持在 CJS 里 import()(动态导入) ✓

6. 递进示例 ​

6.1 最小示例 ​

javascript
const sum = [1, 2, 3].reduce((a, b) => a + b, 0);
console.log(sum);        // 6

6.2 业务示例:把接口数据转成展示数据 ​

javascript
const 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 元' } ]

6.3 反例:用 var + 回调地狱 ​

javascript
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 ✓

7. 完整可运行代码 ​

文件:labs/frontend-demo/modern-js.js

运行:

powershell
node labs/frontend-demo/modern-js.js

预期输出(见 5.1 节,7 段全部成功)✓

★ 最重要的观察:第 6 节(async/await)的输出排在第 7 节之后 ✓ ——同步代码先执行完,异步回调才执行 ✓

8. Debug 观察点 ​

  1. console.log(typeof x):随时查看类型(动态类型的排错基本手段)✓;
  2. JSON.stringify(obj, null, 2):带缩进打印对象 ✓;
  3. Node 断点:node --inspect-brk + Chrome DevTools(前端也有断点调试)✓;
  4. == 与 === 对比:跑 5.4 的例子,亲手确认 ✓。

9. 常见错误 ​

#错误说明
1== 类型转换 ✗用 === ✓
2回调里 this 丢失用箭头函数 ✓
3修改 const 对象以为会报错const 只锁绑定,内容可变 ⚠️
4串行 await 并发的请求用 Promise.all ✓
5undefined 上取属性 ✗TypeError ——用 ?. 可选链 ✓
6var 的函数作用域用 let/const ✓

10. 动手实验 ​

  1. 跑第 7 节的命令,核对 7 段输出(重点:第 6 节为什么在最后)✓;
  2. 验证 ==:跑 1 == '1'、null == undefined 等,记录结果 ✓;
  3. 验证 const 对象可变:const a = {}; a.x = 1; 观察不报错;a = {} 报错 ✓;
  4. 验证可选链:null?.foo 观察返回 undefined(不报错)✓;
  5. 写一个"并发 vs 串行"对比:两个 setTimeout(100ms),分别用串行 await 与 Promise.all,对比总耗时 ✓。

扩展任务:用 Node 写一个"订单统计"脚本:读一个 JSON 文件(订单数组),输出"总金额、按状态分组的数量、最大的一笔"——用数组方法链与解构 ✓。

11. 练习题 ​

  1. 判断:null == undefined 是 false。(对 / 错)
  2. 填空:JS 的并发模型是 ______;比较值与类型用 ______。
  3. 简答:为什么箭头函数能解决"回调里 this 丢失"的问题?
  4. (代码阅读)const [a, , c] = [1, 2, 3] 之后 a、c 是什么?
  5. (编程)把 [{name:'A',tags:['x','y']},...] 转成"标签 → 出现次数"的统计对象(用 flatMap + reduce)。

12. 参考答案(要点) ​

  1. 错。null == undefined 是 true(特例)✗——这也是要用 === 的理由之一 ✓
  2. 单线程 + 事件循环;=== ✓
  3. 箭头函数没有自己的 this ——它使用"定义时所处作用域"的 this ✓ 所以不会随调用者变化 ✓

其余要点见上面的分析框架("像与不像"对照表 是解题的钥匙)✓

13. 掌握标准 ​

  • [ ] 能说出三个根本差异(类型系统 / 并发模型 / ==)★★;
  • [ ] 默认用 const,不用 var;
  • [ ] 会用箭头函数与数组方法链;
  • [ ] 理解"单线程 + 事件循环"(能解释"为什么异步输出在最后")★★★;
  • [ ] 会用 async/await,知道 Promise.all 的并发价值;
  • [ ] 知道 ESM 与 CommonJS 的区别。

14. 扩展资源 ​

15. 下一步学习 ​

《Ajax 与前后端交互》:前端怎么把"数据请求"发出去。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引