第 1 章 1.1 JavaScript 历史与现代生态


🎯 开场:为什么你该学 JavaScript?
你有没有遇到过这种情况?在网上看到一个小工具:「一键把 Excel 表格转成 JSON」「自动抓取网页数据」「做个待办清单 App」——点进去发现人家用的是 JavaScript,评论区一堆人在问「怎么运行的」「求教程」。
你眼馋了,但你不会。
或者你试过看 JavaScript 教程,但满屏的 document.getElementById、addEventListener、callback hell……看到第三章就放弃了。
这就是你来对地方的原因。
学完这一章,你就能明白 JavaScript 这门语言到底是怎么来的、为什么它能跑在浏览器里也能跑在服务器上、以及你写的代码到底是怎么被执行的。
等等,你说你是 Python 教学博主?嗯,我注意到了。但这套课程是 JavaScript 系列——不过放心,编程思想是相通的。Python 教会你的「变量 = 容器」「函数 = 机器」这些概念,在 JavaScript 里照样好使。你只需要适应一下语法就行。
🧱 基础:JavaScript 的前世今生
1.1.1 一门被浏览器「逼」出来的语言
1995 年,网景公司(Netscape)需要一门脚本语言,让网页能动起来。他们只给了 Brendan Eich 10 天时间——
结果你猜怎么着?
他还真做出来了。一门语言,名字本来想叫「Mocha」,后来改成「LiveScript」,最后因为 Java 当时很火,干脆蹭热度改成「JavaScript」。
类比:就像你本来想给公众号取名「小王的技术笔记」,后来发现「程序员小王」流量更大,就改了。蹭热点这事儿,1995 年就有先例了。
所以 JavaScript 名字里有「Java」,但它跟 Java 是完全不同的两门语言。就像「汽车」和「汽车挂件」的关系。
1.1.2 ES6:一次脱胎换骨的升级
JavaScript 早年版本被吐槽了很久:「语法奇怪」「没有类」「回调地狱」……
2015 年,ECMAScript 6(简称 ES6)发布,这是 JavaScript 历史上最重要的一次升级。
举几个例子:
// 老的写法(ES5):声明一个变量
var 购物车总价 = 0;
// 新的写法(ES6):let 和 const
const 商品单价 = 99; // const 表示这个量不变
let 数量 = 3; // let 表示这个量会变
// ES5 的函数
function 计算总价(单价, 数量) {
return 单价 * 数量;
}
// ES6 的箭头函数
const 计算总价 = (单价, 数量) => 单价 * 数量;
// ES5 的字符串拼接
var 欢迎语 = "亲爱的" + 用户名 + ",您有" + 未读消息数 + "条新消息";
// ES6 的模板字符串
const 欢迎语 = `亲爱的${用户名},您有${未读消息数}条新消息`;
说白了:ES6 就是给 JavaScript 做了个「换肤」。功能一样,但看起来更舒服、写起来更少犯错。
1.1.3 V8 引擎:JavaScript 是怎么跑起来的?
你写了一段 JavaScript 代码,浏览器怎么知道该怎么执行?
答案是:V8 引擎。
V8 是 Google 开发的 JavaScript 引擎(就是 Chrome 用的那个)。它的工作原理是:
- 解析(Parsing):把你的代码转成抽象语法树(AST)
- 编译(Compilation):把 AST 转成机器码
- 执行(Execution):机器码直接在 CPU 上跑
重点来了——V8 是用 C++ 写的,但它能运行 JavaScript。这就像一个外国友人能听懂中文,不是因为他脑子里装了中文,而是他带了个翻译。
类比:V8 就像你的手机翻译 App。你说中文,它实时翻译成英文给老外听。V8 就是那个「实时翻译」,只不过翻译的是「代码」到「机器能懂的指令」。
1.1.4 浏览器 vs Node.js:两个跑 JavaScript 的地方
JavaScript 最早只能跑在浏览器里。但 2009 年,有人把 V8 引擎单独拎出来,做了个 Node.js——让 JavaScript 也能跑在服务器上。
浏览器里的 JavaScript:
你写的代码 → V8 引擎 → 操作 DOM(网页元素)、发起网络请求
Node.js 里的 JavaScript:
你写的代码 → V8 引擎 → 读写文件、操作数据库、创建服务器
| 浏览器 | Node.js | |
|---|---|---|
| 能访问网页吗? | ✅ DOM、BOM | ❌ 没有网页概念 |
| 能读写文件吗? | 受限(安全沙箱) | ✅ 完全可以 |
| 能创建服务器吗? | ❌ | ✅ |
| 谁在用? | 前端开发者 | 后端开发者、全栈 |
举个例子:你写一个「读取文件夹里所有文件名」的脚本,浏览器做不到(安全原因),但 Node.js 只需要 3 行:
const fs = require('fs'); // 引入文件系统模块
fs.readdir('./', (err, files) => { // 读取当前目录
console.log(files); // 打印所有文件名
});
1.1.5 TC39 提案:JavaScript 的「议会制度」
JavaScript 不是一个人拍脑袋定的。它有个「议会」——TC39 委员会,由各大公司(Google、Microsoft、Apple、Mozilla 等)的工程师组成。
新语法要进入 JavaScript,得走这个流程:
Stage 0(草案)→ Stage 1(提案)→ Stage 2(初稿)→ Stage 3(候选)→ Stage 4(定稿)
比如「可选链」obj?.prop 这个语法,2019 年还是 Stage 3,2020 年就进入 Stage 4,成为标准。
类比:就像一个法律草案要经过人大审议一样。TC39 就是 JavaScript 世界的「人大」。
🔥 实战:3 个小项目带你快速上手
项目 1:你的第一个 JavaScript 程序(5 分钟)
场景:你想在浏览器里输出一句话「Hello, World!」
完整代码(可以直接打开浏览器控制台粘贴运行):
// 在浏览器控制台里运行这段代码
const 我的第一句话 = "Hello, World!";
console.log(我的第一句话);
// 输出:Hello, World!
预期输出:
Hello, World!
一句话解释:
console.log()就像 Python 里的print(),把内容打印到控制台。
再试一个有意思的:
// 计算 1+2+...+100
let 总和 = 0;
for (let i = 1; i <= 100; i++) {
总和 += i;
}
console.log(`1 到 100 的和是:${总和}`);
// 输出:1 到 100 的和是:5050
项目 2:读取 JSON 数据并筛选(15 分钟)
场景:你有一份 JSON 格式的「班级成绩单」,需要找出不及格的同学。
数据(假设从文件或 API 读取):
const 成绩单 = [
{ 姓名: "张三", 数学: 92, 英语: 88 },
{ 姓名: "李四", 数学: 45, 英语: 67 },
{ 姓名: "王五", 数学: 78, 英语: 85 },
{ 姓名: "赵六", 数学: 33, 英语: 55 }
];
完整代码:
// 找出不及格(分数 < 60)的同学
const 不及格名单 = 成绩单.filter(学生 => 学生.数学 < 60 || 学生.英语 < 60);
console.log("不及格同学:");
不及格名单.forEach(学生 => {
console.log(`${学生.姓名} - 数学:${学生.数学},英语:${学生.英语}`);
});
// 输出:
// 不及格同学:
// 李四 - 数学:45,英语:67
// 赵六 - 数学:33,英语:55
一句话解释:filter() 就像一个「过滤器」,符合条件(返回 true)的留下来;forEach() 就像「遍历每一个人」。
项目 3:做个简易待办清单工具(15 分钟)
场景:你需要一个命令行工具,能添加待办事项、查看列表、删除已完成的事项。
完整代码(Node.js 环境运行):
// 待办清单工具
let 待办列表 = [];
// 添加待办
function 添加(事项) {
待办列表.push({ 内容: 事项, 完成: false });
console.log(`✅ 已添加:「${事项}」`);
}
// 查看列表
function 查看() {
if (待办列表.length === 0) {
console.log("📝 待办列表是空的");
return;
}
待办列表.forEach((item, 索引) => {
const 状态 = item.完成 ? "✓" : "○";
console.log(`${索引 + 1}. [${状态}] ${item.内容}`);
});
}
// 标记完成
function 完成(索引) {
if (索引 > 0 && 索引 <= 待办列表.length) {
待办列表[索引 - 1].完成 = true;
console.log(`✅ 已标记完成:「${待办列表[索引 - 1].内容}」`);
}
}
// 删除待办
function 删除(索引) {
if (索引 > 0 && 索引 <= 待办列表.length) {
const 被删除 = 待办列表.splice(索引 - 1, 1);
console.log(`🗑️ 已删除:「${被删除[0].内容}」`);
}
}
// 测试
添加("买牛奶");
添加("写周报");
添加("给妈妈打电话");
查看();
完成(2);
删除(3);
查看();
预期输出:
✅ 已添加:「买牛奶」
✅ 已添加:「写周报」
✅ 已添加:「给妈妈打电话」
1. [○] 买牛奶
2. [○] 写周报
3. [○] 给妈妈打电话
✅ 已标记完成:「写周报」
🗑️ 已删除:「给妈妈打电话」
1. [○] 买牛奶
2. [✓] 写周报
一句话解释:这个工具用数组存储待办事项,用对象记录每个事项的内容和状态。
splice()是数组的「剪切」操作,可以删除指定位置的元素。
💪 进阶:新手最容易踩的坑
坑 1:var vs let vs const 的作用域陷阱
// ❌ 错误示例:用 var 容易出 bug
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(不是你想要的 0, 1, 2)
// ✅ 正确示例:用 let 才有块级作用域
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2
坑来了:var 是函数作用域,let 是块级作用域。在循环里用 var,异步回调拿到的都是最后一个值。
坑 2:异步回调地狱
// ❌ 错误示例:层层嵌套,难以阅读
fs.readFile('a.txt', function(err, data) {
fs.readFile('b.txt', function(err, data) {
fs.readFile('c.txt', function(err, data) {
console.log(data);
});
});
});
// ✅ 正确示例:用 Promise 或 async/await
const 读取所有文件 = async () => {
const a = await fs.promises.readFile('a.txt');
const b = await fs.promises.readFile('b.txt');
const c = await fs.promises.readFile('c.txt');
console.log(a, b, c);
};
坑 3:箭头函数里没有 this
// ❌ 错误示例
const 计数器 = {
count: 0,
增加: () => {
this.count++; // 这里的 this 不是计数器对象!
}
};
// ✅ 正确示例:用普通函数
const 计数器 = {
count: 0,
增加: function() {
this.count++;
}
};
坑 4:比较运算的强制类型转换
// ❌ 错误示例
console.log(0 == false); // true(意外吧?)
console.log('' == false); // true
console.log(null == false); // false
// ✅ 正确示例:用严格等于 ===
console.log(0 === false); // false
console.log('' === false); // false
console.log(null === false); // false
注意! 永远用
===和!==,别用==和!=。
坑 5:数组方法不修改原数组
// ❌ 错误示例:以为 sort() 会返回排序后的新数组
const 数字 = [3, 1, 2];
const 排序后 = 数字.sort();
console.log(数字); // [1, 2, 3] - 原数组被改了!
console.log(排序后 === 数字); // true
// 正确示例:slice() 返回新数组
const 原数组 = [3, 1, 2];
const 副本 = 原数组.slice();
副本.sort();
console.log(原数组); // [3, 1, 2] - 原数组不变
调试技巧:console 不只是 log
// 打印对象时用 table 更清晰
const 用户们 = [
{ 名字: "张三", 年龄: 25 },
{ 名字: "李四", 年龄: 30 }
];
console.table(用户们);
// 打印带样式的日志
console.log("%c醒目文字", "color: red; font-size: 20px");
// 计时代码执行时间
console.time("排序");
const 排序结果 = 大数组.sort((a, b) => a - b);
console.timeEnd("排序");
✏️ 练习题
练习 1(2 分钟):改个变量名
// 下面代码会输出什么?如果把 let 改成 const 会怎样?
let 数字 = 10;
数字 = 20;
console.log(数字);
- 输入:无
- 预期输出:
20 - 提示:
let允许重新赋值
练习 2(3 分钟):加个判断
在项目 1 的「计算 1+2+...+100」代码里,加一个判断:如果总和大于 5000,额外打印「大数字!」。
- 输入:无
- 预期输出:
5050和大数字! - 提示:加一行
if (总和 > 5000) { console.log('大数字!'); }
练习 3(5 分钟):处理新数据
用项目 2 的 filter 方法,从以下数据中找出「成绩都及格」的学生:
const 学生成绩 = [
{ 姓名: "小明", 语文: 85, 数学: 92 },
{ 姓名: "小红", 语文: 58, 数学: 73 },
{ 姓名: "小李", 语文: 90, 数学: 88 }
];
- 输入:上述数据
- 预期输出:只输出小明的记录
- 提示:两个条件都要满足才及格
练习 4(5 分钟):组合两个项目
把项目 2 的「成绩单筛选」和项目 3 的「待办清单」组合起来:
- 把不及格学生自动加入待办清单「需要补考」
- 然后查看待办清单
- 输入:项目 2 的成绩单数据
- 预期输出:不及格名单 + 待办清单里多了「需要补考」这一项
- 提示:先筛选,再 forEach 里调用
添加()
练习 5(10 分钟):读懂报错
假设你运行以下代码,控制台报错了:
const 数字 = [1, 2, 3];
数字 = [4, 5, 6]; // 这一行报错
console.log(数字);
- 输入:上述代码
- 预期输出:
TypeError: Assignment to constant variable. - 提示:看看报错信息,再看看第 1 行声明用的什么关键词
📚 作业:做一个「章节学习记录器」
需求描述:
做一个命令行工具,记录你学习本教程的进度,并保存到本地文件。
功能点:
1. 添加一条学习记录(章节名 + 学习时长 + 备注)
2. 查看所有学习记录
3. 导出记录为 JSON 文件
4. 从 JSON 文件加载记录
加分项:
1. 支持删除某条记录
2. 显示总学习时长
验收标准:
- 能跑起来(Node.js 环境)
- 添加后查看能看到新记录
- 关闭再打开程序,加载 JSON 后记录还在
提交方式:评论区贴代码或 GitHub 链接
📚 总结
这一章我们学了 3 件事:
- JavaScript 怎么来的:1995 年被浏览器「逼」出来,2015 年 ES6 大升级
- 代码怎么跑起来的:V8 引擎翻译你的代码,让机器能懂
- 能在哪儿跑:浏览器里做网页交互,Node.js 里做服务器/工具
延伸资源:
- MDN JavaScript 教程(最权威的文档)
- 《JavaScript 高级程序设计》(红宝书)- 经典大部头
- JavaScript30 - 30 个小项目练手
互动钩子:
你在学习这章的时候,有没有想过「这玩意儿跟 Python 比起来……」?比如两种语言的变量声明、函数写法有什么不同?评论区聊聊你在两种语言间切换的经历,老粉优先回复!
下章剧透:下一章我们会学到——写了代码总得运行看效果吧?浏览器自带的「开发者工具」就是你的好帮手,能实时看到代码执行、调试网络请求、修改网页样式……学会它,你就真正入门前端开发了。

评论(0)