主题
第 03 章 · JavaScript 入门
一句话开篇:HTML 是骨架、CSS 是装修,JavaScript 就是给房子装上"电闸、门铃、自动门"——让网页能"动"起来、能"听话"。
0. 生活类比(先建立直觉)
把网页比作一家奶茶店:
| 角色 | 网页对应 | 任务 |
|---|---|---|
| 招牌、菜单、桌椅(固定的) | HTML | "店里有什么" |
| 招牌颜色、桌布、灯光 | CSS | "店里长什么样" |
| 收银员、点单系统、自动门 | JavaScript | "店里能干什么" |
没有 JS 的网页:菜单是死的、按钮按了没反应、用户填完表单也提交不了。 有了 JS 的网页:点"加入购物车"会响应、输入手机号会校验、滚动到底部自动加载下一页。
JS 是浏览器里的"大脑":
┌─────────── 浏览器 ─────────────────┐
│ HTML(骨头) + CSS(皮肤) │
│ │
│ JavaScript(大脑、神经) │
│ - 监听用户操作(点击、输入、滚动) │
│ - 修改页面内容(加文字、改颜色) │
│ - 和服务器通信(取数据、发数据) │
└────────────────────────────────────┘1. 概念是什么
JavaScript(简称 JS)是一种动态、解释执行、单线程的脚本语言。
- 1995 年由 Brendan Eich 用 10 天发明(这件事本身就够离谱)。
- 标准化后叫 ECMAScript(ES),常见说"ES6"="ES2015"。
- 一开始只能在浏览器跑,后来 Node.js 让它能在服务器跑,现在还能在小程序、桌面应用、嵌入式跑——是真正的"全家桶"语言。
和 HTML/CSS 的关系:
HTML: <button id="btn">点我</button>
CSS: #btn { background: red; }
JavaScript: document.getElementById('btn').onclick = () => alert('Hi');3 件套各司其职,缺一不可。
2. 为什么需要它
只有 HTML + CSS 的页面是静态的——内容固定,用户做啥它都不响应。一旦加了 JS:
| 没 JS | 有 JS |
|---|---|
| 表单填完只能跳转刷新 | 输入时实时校验、不刷新提交 |
| 图片只能挨个点开看 | 轮播图自动切换 |
| 数据从服务器返回新的 HTML 整页刷新 | AJAX 局部更新(更快、更省流量) |
| 内容写死在 HTML 里 | 数据驱动、动态生成 |
| 静态展示 | SPA 单页应用、即时聊天、在线协作 |
简单说:JS 让网页从"画册"变成"应用"。
3. 怎么用(最小示例)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8" /></head>
<body>
<button id="btn">点我</button>
<p id="out"></p>
<script>
let count = 0;
document.getElementById('btn').addEventListener('click', () => {
count++;
document.getElementById('out').textContent = '已点击 ' + count + ' 次';
});
</script>
</body>
</html>15 行代码,浏览器双击打开,点击按钮就能看到计数。
4. 进阶要点
4.1 变量(let / const / var)
| 关键字 | 是否可重新赋值 | 作用域 | 是否提升 | 推荐度 |
|---|---|---|---|---|
const | ❌ 不可(首选) | 块级 {} | 提升但不可访问 | ⭐⭐⭐⭐⭐ |
let | ✅ 可 | 块级 {} | 提升但不可访问 | ⭐⭐⭐⭐ |
var | ✅ 可 | 函数级 / 全局 | 提升且为 undefined | ⭐(不推荐) |
js
const PI = 3.14; // 常量,不能改
let count = 0; // 可变变量
count = count + 1; // ✅
// PI = 3.15; // ❌ TypeError
var oldStyle = 1; // 老 JS 的写法,已不推荐口诀:默认 const,需要变才用 let,永远别用 var。
命名规则:
- 字母、数字、
_、$,不能数字开头。 - 区分大小写:
name和Name是两个变量。 - 习惯用 小驼峰:
userName、isLoggedIn。
4.2 数据类型
JS 有 8 种数据类型:7 种原始类型 + 1 种对象类型。
| 类型 | 例子 | 说明 |
|---|---|---|
number | 42, 3.14, NaN, Infinity | 数字(不分整数浮点) |
string | 'hi', "hi", `hi ${name}` | 字符串(三种引号) |
boolean | true, false | 真假 |
undefined | undefined | 声明了但没赋值 |
null | null | "我故意空着" |
bigint | 9007199254740993n | 超大整数(加 n) |
symbol | Symbol('id') | 唯一值(高级用法) |
object | {}, [], function, Date | 引用类型 |
用 typeof 查类型(注意几个坑):
js
typeof 42 // "number"
typeof "hi" // "string"
typeof true // "boolean"
typeof undefined // "undefined"
typeof null // "object" ⚠️ 历史 bug
typeof [] // "object" ⚠️ 数组也是 object
typeof function(){} // "function"字符串模板(反引号 + ${}):
js
const name = '张三';
const greet = `你好,${name}!今年 ${2026 - 2000} 岁`;
// "你好,张三!今年 26 岁"支持多行、自动换行:
js
const html = `
<div>
<p>${name}</p>
</div>
`;4.3 运算符
算术:+ - * / % **(% 取余,** 幂)
js
10 % 3 // 1
2 ** 10 // 1024比较:
| 写法 | 含义 | 是否类型转换 |
|---|---|---|
== | 宽松相等 | 是 ⚠️(坑) |
=== | 严格相等(推荐) | 否 |
!= / !== | 不等 | 同上 |
>, <, >=, <= | 大小比较 | — |
js
1 == '1' // true ⚠️
1 === '1' // false ✅
null == undefined // true ⚠️
NaN === NaN // false ⚠️ 这是 NaN 的奇葩铁律:永远用
===,除非你明确需要==。
逻辑:
js
true && false // false(与,全真才真)
true || false // true(或,有一真就真)
!true // false(非)短路求值(实用技巧):
js
const name = userInput || '匿名'; // 给默认值
isReady && doSomething(); // 条件执行
const a = obj?.user?.name ?? '未知'; // 可选链 + 空值合并| 运算符 | 说明 | 例子 |
|---|---|---|
?. | 可选链(前面是 null/undefined 就停) | obj?.a?.b |
?? | 空值合并(左侧是 null/undefined 才用右侧) | x ?? '默认' |
?? 和 || 区别:
js
0 || 'default' // 'default' ⚠️ 0 也被当成"假"
0 ?? 'default' // 0 ✅ 只有 null/undefined 才换4.4 流程控制
if / else:
js
const score = 85;
if (score >= 90) console.log('优秀');
else if (score >= 60) console.log('及格');
else console.log('不及格');三元表达式(更简洁):
js
const result = score >= 60 ? '及格' : '不及格';switch:
js
switch (day) {
case 1:
case 2:
case 3:
case 4:
case 5: console.log('工作日'); break;
case 6:
case 7: console.log('周末'); break;
default: console.log('错误');
}⚠️
case不写break会"穿透"到下一个 case。
循环:
js
// 1. 经典 for
for (let i = 0; i < 5; i++) console.log(i);
// 2. while
let n = 0;
while (n < 5) { console.log(n); n++; }
// 3. for...of —— 遍历数组、字符串等可迭代对象(推荐)
const arr = ['a', 'b', 'c'];
for (const item of arr) console.log(item);
// 4. for...in —— 遍历对象的 key(不推荐用于数组)
const obj = { a: 1, b: 2 };
for (const key in obj) console.log(key, obj[key]);
// 5. forEach —— 数组方法
arr.forEach((item, idx) => console.log(idx, item));| 循环 | 用途 | 能 break 吗 |
|---|---|---|
for | 万能 | ✅ |
while | 不知道循环次数 | ✅ |
for...of | 遍历可迭代值(数组、字符串、Set、Map) | ✅ |
for...in | 遍历对象 key | ✅ |
forEach | 遍历数组 | ❌ 不能 break(只能 return 退出当前回调) |
4.5 函数
3 种声明方式:
js
// 1. 函数声明(有提升)
function add(a, b) {
return a + b;
}
// 2. 函数表达式(赋值给变量)
const sub = function(a, b) {
return a - b;
};
// 3. 箭头函数(最常用)
const mul = (a, b) => a * b;
const square = x => x * x; // 单参数可省略括号
const greet = () => 'Hello'; // 无参数要写 ()
const log = msg => { console.log(msg); }; // 多行用 {}箭头函数 vs 普通函数:
| 维度 | 普通函数 | 箭头函数 |
|---|---|---|
this | 调用时决定 | 词法(继承外层) |
arguments | 有 | 无 |
| 能当构造函数 | 能(new) | 不能 |
| 简洁度 | 低 | 高 |
默认参数 / 剩余参数 / 解构:
js
function greet(name = '游客', ...rest) {
console.log(name, rest);
}
greet(); // 游客 []
greet('张三', 18, '北京'); // 张三 [18, '北京']
function intro({ name, age }) { // 参数解构
return `${name} ${age} 岁`;
}
intro({ name: '李四', age: 20 });4.6 作用域与闭包
作用域(变量能被访问的范围):
- 全局作用域:
<script>的最外层(浏览器中是window)。 - 函数作用域:函数内部。
- 块级作用域:
{}内(let/const才有,var没有)。
js
{
let x = 1;
const y = 2;
var z = 3;
}
// console.log(x); // ❌ ReferenceError
// console.log(y); // ❌
console.log(z); // 3 ⚠️ var 漏出来了闭包(神秘但常考):
闭包 = 函数 + 它定义时所处的环境。
通俗说:函数即使在外面被调用,也能记住"老家"的变量。
js
function makeCounter() {
let count = 0; // 这个 count 被"记住"
return function() {
return ++count;
};
}
const c1 = makeCounter();
c1(); // 1
c1(); // 2
c1(); // 3
const c2 = makeCounter(); // 全新的"老家"
c2(); // 1(独立计数)经典坑(var + 循环 + 异步):
js
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3 3 3 ⚠️(i 共用一个)
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0 1 2 ✅(每轮独立 i)4.7 数组(Array)
数组 = 有序的列表。
js
const arr = [1, 2, 3];
arr.length // 3
arr[0] // 1
arr[0] = 10; // 改值
arr.push(4); // 末尾加:[10,2,3,4],返回新长度
arr.pop(); // 末尾删:返回被删的 4
arr.unshift(0); // 头部加
arr.shift(); // 头部删常用方法(高频面试点):
| 方法 | 作用 | 是否改原数组 | 返回 |
|---|---|---|---|
push / pop | 末尾增/删 | 是 | 新长度 / 删的项 |
shift / unshift | 头部删/增 | 是 | 删的项 / 新长度 |
splice(i, n, ...items) | 任意位置增删 | 是 | 被删的数组 |
slice(start, end) | 截取(不含 end) | 否 | 新数组 |
concat | 合并 | 否 | 新数组 |
indexOf / includes | 查找 | 否 | 索引 / 布尔 |
forEach(cb) | 遍历 | 否 | undefined |
map(cb) | 映射成新数组 | 否 | 新数组 |
filter(cb) | 筛选 | 否 | 新数组 |
reduce(cb, init) | 累计 | 否 | 任意 |
find(cb) | 找第一个满足条件的项 | 否 | 项 / undefined |
some(cb) / every(cb) | 任一/全部满足 | 否 | 布尔 |
sort(cb) | 排序 | 是 ⚠️ | 数组 |
reverse() | 反转 | 是 ⚠️ | 数组 |
join('-') | 拼字符串 | 否 | 字符串 |
链式调用很美:
js
const total = [1, 2, 3, 4, 5]
.filter(x => x % 2 === 0) // [2, 4]
.map(x => x * 10) // [20, 40]
.reduce((sum, x) => sum + x, 0); // 60展开运算符 ...:
js
const a = [1, 2, 3];
const b = [...a, 4, 5]; // [1,2,3,4,5]
const max = Math.max(...a); // 3
const [first, ...rest] = a; // first=1, rest=[2,3]4.8 对象(Object)
对象 = 键值对集合(key-value)。
js
const user = {
name: '张三',
age: 18,
address: {
city: '北京',
zip: '100000'
},
greet() {
return `Hi, I'm ${this.name}`;
}
};
user.name // "张三"
user['name'] // 同上(key 是动态时用)
user.age = 19; // 改
user.gender = 'male'; // 加
delete user.age; // 删遍历对象:
js
Object.keys(user); // ['name', 'address', 'greet']
Object.values(user); // ['张三', {...}, ƒ]
Object.entries(user); // [['name','张三'], ...]
for (const [key, val] of Object.entries(user)) {
console.log(key, val);
}对象解构:
js
const { name, address: { city } } = user;
console.log(name, city); // 张三 北京
// 改名 + 默认值
const { name: n = '匿名', age = 0 } = user;展开拷贝 / 合并:
js
const copy = { ...user }; // 浅拷贝
const merged = { ...user, age: 99, vip: true }; // 合并并覆盖⚠️ 对象是引用类型:
a = b只是复制了引用,改 a 也会动 b。深拷贝要用structuredClone(obj)或 JSON 法。
js
const a = { x: 1 };
const b = a;
b.x = 100;
console.log(a.x); // 100 ⚠️4.9 类型转换(隐式 vs 显式)
js
// 显式
Number('42') // 42
String(42) // '42'
Boolean(0) // false
parseInt('42px') // 42
parseFloat('3.14abc') // 3.14
// 隐式(坑王)
'5' + 3 // '53' (字符串拼接)
'5' - 3 // 2 (减法触发数字转换)
'5' * '3' // 15
[] + [] // ''
[] + {} // '[object Object]'
true + 1 // 2Falsy 值(被当成 false 的 8 个):
false, 0, -0, 0n, '', null, undefined, NaN其他都是 truthy(包括 []、{}、'0'、'false')。
js
if ([]) console.log('1'); // 输出!空数组是 truthy ⚠️
if ('0') console.log('2'); // 输出!非空字符串是 truthy5. 实战案例
详见 examples/ 目录:
variables.html— 变量、数据类型、类型转换functions.html— 函数 3 种写法、闭包、回调array-object.html— 数组方法链 + 对象操作
完整 demo 见 demo/index.html:纯原生 JS 实现的 TodoList。
6. ⚠️ 易踩的坑
坑 1:== 和 === 混用
== 会类型转换后比较,规则极其复杂;=== 不转换,所见即所得。永远用 ===。
js
0 == false // true
'' == false // true
null == undefined // true
NaN === NaN // false(NaN 不等于自己)坑 2:浮点数精度
js
0.1 + 0.2 === 0.3 // false ⚠️ 输出 0.30000000000000004原因:二进制无法精确表示 0.1。涉及钱时用 整数(分) 或专门的库(decimal.js、big.js)。
坑 3:var 的变量提升与函数作用域
js
console.log(a); // undefined(不是报错!)
var a = 1;
// 等价于
var a;
console.log(a);
a = 1;let / const 也提升,但是处于"暂时性死区(TDZ)",访问会报错。这是它们更安全的原因。
坑 4:箭头函数的 this 不是动态的
js
const obj = {
name: '张三',
sayName: function() { console.log(this.name); }, // 张三
sayName2: () => { console.log(this.name); } // undefined ⚠️
};箭头函数不绑定自己的 this,会继承外层(这里是全局)。对象方法别用箭头函数。
坑 5:对象 / 数组是引用类型
js
const a = [1, 2, 3];
const b = a;
b.push(4);
console.log(a); // [1,2,3,4] ⚠️ a 也变了
// 浅拷贝
const c = [...a];
const d = a.slice();
// 深拷贝
const e = structuredClone(a);
const f = JSON.parse(JSON.stringify(a)); // ⚠️ 函数、Date、undefined 会丢坑 6:forEach 不能 break
js
[1, 2, 3].forEach(x => {
if (x === 2) break; // ❌ SyntaxError
});
// 解决:用 for...of 或 some
for (const x of [1, 2, 3]) {
if (x === 2) break;
}坑 7:sort 默认按字符串排序
js
[10, 1, 2].sort(); // [1, 10, 2] ⚠️ 按字符串排
[10, 1, 2].sort((a,b) => a-b); // [1, 2, 10] ✅坑 8:异步循环 + var
见 4.6 节闭包陷阱。永远用 let。
坑 9:分号陷阱
JS 自动插入分号(ASI)有时会出错,最经典:
js
function f() {
return
{
name: '张三'
};
}
f(); // undefined ⚠️ return 后自动加了分号对策:要返回的对象写在 return 同一行,或用 (。
坑 10:typeof null === 'object'
历史遗留 bug,已经无法修了。判断 null 用 === null。
7. 一句话总结
JavaScript 是浏览器里的"大脑"——记住"默认 const、永远 ===、forEach 不能 break、对象是引用、箭头函数没自己的 this",你就闯过了 80% 的入门坑。