开篇介绍:
hello 大家,本篇博客我们来学习JavaScript~
一、从日常上网场景,认识 JavaScript 的 "魔力"
先问大家一个问题:你每天上网都在做什么?可能是刷淘宝时,点击 "加入购物车",按钮瞬间变色、购物车数字 + 1;可能是刷抖音网页版时,手指(或鼠标)一滑,下一个视频自动加载;可能是填某个注册表单时,刚输完手机号,就弹出提示 "手机号格式不正确";甚至可能是玩网页版小游戏时,点击屏幕就能控制角色跳跃......
这些让网页 "动起来""能互动" 的功能,背后几乎都有 JavaScript(简称 JS) 的身影。如果把网页比作一个人:
- HTML 是人的 "骨头"------ 负责搭结构,比如哪里是标题、哪里是按钮、哪里放图片;
- CSS 是人的 "皮肤"------ 负责管外观,比如标题用什么颜色、按钮要不要圆角、图片多大尺寸;
- JavaScript 就是人的 "灵魂"------ 负责管行为,比如点击按钮有反应、输入内容会检查、页面滚动能加载更多。
没有 JavaScript 的网页,就像一个不会动、不会说话的 "雕塑",只能看,没法互动;有了 JavaScript,网页才变成了 "活生生的人",能和你交流、给你反馈。这就是为什么 JavaScript 能成为世界上最流行的编程语言之一 ------ 它是网页的 "互动引擎",更是互联网世界的 "粘合剂"。
二、JavaScript 到底是什么?给你一个最透彻的定义
很多教材会给 JavaScript 下这样一个定义:
JavaScript 是一种脚本语言 ,通过解释器 运行,最初主要在客户端(浏览器) 上运行,现在也能基于 Node.js 在服务器端运行。
这句话每个字都认识,但组合在一起可能让人犯迷糊。别担心,我们把它拆成四个部分,用大白话 + 例子彻底讲明白!
2.1 什么是 "脚本语言"?和 "编译型语言" 有啥区别?
要理解 "脚本语言",得先对比 "编译型语言"------ 比如我们常听说的 C、C++、Java(注意 Java 和 JavaScript 不是一回事!后面会详细讲)。
编译型语言:"先翻译好,再执行"
想象你要给外国朋友做一道中国菜,你手里有一份中文菜谱。为了让外国朋友看懂,你会先把整个菜谱完整翻译成英文,装订成一本 "英文菜谱书",然后外国朋友拿着这本翻译好的书,一步步照着做。
编译型语言就是这个逻辑:
- 你用 C++ 写好代码(中文菜谱);
- 用 "编译器" 把代码完整编译成计算机能直接看懂的 "二进制文件"(英文菜谱书);
- 以后每次运行,直接执行这个二进制文件就行,不用再重新翻译。
优点:编译好之后,运行速度特别快(因为不用边翻译边执行);缺点:编译后的文件只能在特定系统上运行(比如 Windows 下编译的 exe 文件,在 Mac 上就跑不了),而且修改代码后必须重新编译,比较麻烦。
脚本语言:"边翻译,边执行"
再换个场景:你和外国朋友一起做饭,你拿着中文菜谱,念一句,旁边的翻译就实时翻译成英文,外国朋友跟着做一句。不用提前把整本书翻译完,而是 "做一句、翻一句、执行一句"。
JavaScript 就是典型的脚本语言:
- 你写好 JS 代码(中文菜谱);
- 浏览器里的 "JS 引擎"(翻译官)会逐行读取代码,边翻译边执行;
- 不用提前编译成文件,直接在浏览器里打开就能跑。
优点:
- 跨平台性好 ------ 只要有浏览器(带 JS 引擎),不管是 Windows、Mac 还是手机,都能运行;
- 修改方便 ------ 改完代码刷新浏览器就行,不用重新编译;
- 入门简单 ------ 语法相对灵活,不用纠结复杂的编译配置。
缺点:
- 运行速度比编译型语言慢一点(因为要边翻译边执行)------ 不过现在的 JS 引擎(比如 Chrome 的 V8)已经优化得非常快了,日常使用几乎感觉不到差距。
2.2 "解释器" 是什么?JS 引擎就是它!
刚才提到的 "JS 引擎",就是 JavaScript 的 "解释器"。它的核心工作只有两个:
- 解析代码:把你写的 JS 代码,翻译成计算机能看懂的 "二进制指令";
- 执行指令:让 CPU 按照翻译好的指令去做事。
不同的浏览器有不同的 JS 引擎:
- Chrome 浏览器用的是 V8 引擎(最有名,Node.js 也是用的它);
- Firefox 浏览器用的是 SpiderMonkey(JavaScript 之父 Brendan Eich 最早写的引擎);
- Safari 浏览器用的是 JavaScriptCore;
- Edge 浏览器现在也用 V8 引擎了(因为 Edge 换成了 Chromium 内核)。
可以把 JS 引擎想象成一个 "超级翻译官"------ 它既懂人类写的 JS 代码,又懂计算机的 "机器语言",是连接你和计算机的桥梁。
2.3 "客户端(浏览器)运行" 是什么意思?
在 2009 年 Node.js 诞生之前,JavaScript 几乎只能在浏览器里运行 ------ 也就是 "客户端"(用户的电脑 / 手机上的浏览器)。
为什么只能在浏览器里跑?因为 JS 引擎最初是嵌在浏览器里的,只有打开浏览器,才能启动 JS 引擎,执行 JS 代码。这就导致早期的 JavaScript 只能做一件事:和网页互动 ------ 比如操作网页上的按钮、文字、图片,响应用户的点击、输入等操作。
举个最简单的例子:你在网页上写一个按钮,点击后弹出 "你好"。这个功能的代码是这样的:
<!DOCTYPE html>
<html>
<body>
<!-- 这是一个 HTML 按钮 -->
<button onclick="alert('你好!')">点我一下</button>
</body>
</html>
把这段代码保存成 .html 文件,用 Chrome 打开,点击按钮,就会弹出提示框。这里的 alert('你好!') 就是 JS 代码,它是被 Chrome 里的 V8 引擎解析并执行的 ------ 这就是 "在客户端(浏览器)运行" 的意思。
2.4 "基于 Node.js 在服务器端运行" 又是什么?
2009 年,一个叫 Ryan Dahl 的程序员,把 Chrome 的 V8 引擎 "抠了出来",包装了一下,做成了一个能在服务器 上运行的 JavaScript 环境 ------ 这就是 Node.js。
什么是 "服务器"?简单说,服务器就是 "放在机房里的超级电脑",24 小时不关机,负责给用户的浏览器提供数据。比如你刷淘宝时,淘宝的服务器会把商品图片、价格、详情等数据,传给你的浏览器;你发微信时,微信的服务器会把你的消息,传给对方的手机。
在 Node.js 诞生之前,服务器端的代码通常是用 Java、Python、PHP 等语言写的;有了 Node.js 之后,JavaScript 也能写服务器端代码了!这意味着什么?
- 如果你学会了 JavaScript,既能写 "前端"(浏览器里的网页),又能写 "后端"(服务器上的接口)------ 这就是所谓的 "全栈开发";
- 前端和后端用同一种语言,代码可以复用,开发效率更高。
举个最简单的 Node.js 例子:用 JS 写一个 "服务器",当你访问它时,返回 "Hello Node.js!"。代码如下:
javascript
// 这是一个简单的 Node.js 服务器代码
const http = require('http'); // 引入 Node.js 的 http 模块
// 创建服务器
const server = http.createServer((req, res) => {
res.writeHead(200, {'Content-Type': 'text/plain'}); // 设置响应头
res.end('Hello Node.js!'); // 返回内容
});
// 让服务器监听 3000 端口
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
把这段代码保存成 server.js,在电脑上安装 Node.js 后,打开命令行输入 node server.js,然后在浏览器里访问 http://localhost:3000,就能看到 "Hello Node.js!" ------ 这就是 JavaScript 在服务器端运行的例子!
2.5 总结:JavaScript 是个 "全能选手"
现在再回头看最初的定义,你应该完全理解了:
- JavaScript 是脚本语言,边翻译边执行,跨平台、易入门;
- 它有解释器(JS 引擎),比如 Chrome 的 V8;
- 最初在客户端(浏览器) 运行,负责网页互动;
- 现在能基于 Node.js 在服务器端运行,负责后端开发。
而且,JavaScript 的能力远不止于此!接下来我们就聊聊:JavaScript 到底能做哪些事?
三、JavaScript 能做什么?从网页到服务器,无处不在
很多人以为 JavaScript 只能 "写网页特效",其实它早就变成了一个 "全能选手"------ 从网页到游戏,从桌面到手机,从前端到后端,几乎没有它做不到的事!我们一个个来看:
3.1 网页开发:最核心、最基础的能力
这是 JavaScript 的 "老本行",也是最常用的场景。网页开发中,JavaScript 能做的事太多了:
- 用户交互:点击按钮弹出提示、输入内容实时检查、下拉菜单展开 / 收起、购物车加减商品...... 比如淘宝、京东的网页,所有和用户互动的功能,几乎都有 JS;
- 动态内容:不用刷新页面,就能更新网页内容 ------ 比如刷微博时,往下滑自动加载新微博;比如股票网页,价格实时跳动;
- 动画效果:图片轮播、元素淡入淡出、页面滚动时的视差效果...... 比如很多品牌官网的炫酷动画,都是用 JS(或基于 JS 的动画库)做的。
举个简单的 "动态内容" 例子:点击按钮,改变网页上的文字。代码如下:
javascript
<!DOCTYPE html>
<html>
<body>
<h1 id="title">原来的标题</h1>
<button onclick="changeTitle()">点击改变标题</button>
<script>
// 这是一个 JS 函数,用来改变标题
function changeTitle() {
// 获取 id 为 title 的元素
const titleElement = document.getElementById('title');
// 改变元素的文字内容
titleElement.textContent = '新的标题!';
}
</script>
</body>
</html>
保存成 .html 文件打开,点击按钮,标题就会从 "原来的标题" 变成 "新的标题!" ------ 这就是 JavaScript 操作网页内容的最简单例子。
3.2 网页游戏开发:从 "偷菜" 到 3D 大作
你可能不知道,很多网页游戏都是用 JavaScript 写的!
- 早年的 "偷菜" 游戏(QQ 农场),就是用 JS 做的;
- 现在很多网页版小游戏(比如《2048》《跳一跳》网页版),也是用 JS 写的;
- 甚至一些 3D 网页大作(比如《BrowserQuest》《我的世界》网页版),也是用 JS + WebGL 做的。
JavaScript 做游戏有两个核心优势:
- 不用安装:打开浏览器就能玩,用户门槛低;
- 生态丰富 :有很多成熟的游戏引擎,比如 Phaser (2D 游戏)、Three.js (3D 游戏)、Babylon.js(3D 游戏),能大大降低开发难度。
3.3 服务器开发:Node.js 让 JS 进军后端
刚才提到过,Node.js 让 JavaScript 能写服务器端代码。现在,Node.js 已经被很多大公司用于生产环境:
- Netflix(网飞):用 Node.js 做用户界面的后端,处理高并发请求;
- Uber(优步):用 Node.js 做匹配司机和乘客的核心服务;
- 淘宝、天猫:部分页面的后端接口,也是用 Node.js 写的。
Node.js 特别适合做 "高并发、I/O 密集型" 的应用 ------ 比如聊天应用、实时数据看板、API 接口等。因为 Node.js 是 "单线程、非阻塞 I/O" 的(这个概念后面深入学习 Node.js 时会讲,现在只要知道它处理并发请求很快就行)。
3.4 桌面程序开发:用 JS 写 VS Code、Discord
你没看错!我们常用的 VS Code (代码编辑器)、Discord (聊天软件)、Slack(团队协作软件),都是用 JavaScript 写的!
它们用的是 Electron 框架 ------ 这个框架把 Node.js 和 Chromium(Chrome 的开源版本)打包在一起,让你能用 JavaScript、HTML、CSS 写桌面程序,而且能同时打包成 Windows、Mac、Linux 三个平台的安装包!
用 Electron 写桌面程序的优势:
- 跨平台:写一套代码,三个平台都能用;
- 开发快:用熟悉的 Web 技术(JS、HTML、CSS),不用学新的桌面开发语言(比如 C#、Swift);
- 生态好:有很多现成的库和组件,能直接用。
3.5 手机 App 开发:用 JS 写安卓和 iOS 应用
除了桌面程序,JavaScript 还能写手机 App!常用的框架有:
- React Native:Facebook 开发的,用 JS 写代码,能生成真正的安卓和 iOS 原生 App(不是网页套壳);
- Weex:阿里巴巴开发的,和 React Native 类似,也是用 JS 写原生 App;
- Flutter:虽然 Flutter 用的是 Dart 语言,但它也支持和 JS 交互,而且很多前端开发者会从 JS 转 Flutter。
用这些框架写 App 的优势:
- 跨平台:写一套代码,安卓和 iOS 都能用;
- 成本低:不用分别招安卓和 iOS 开发者,一个前端开发者就能搞定;
- 迭代快:修改代码后,不用重新打包安装,就能看到效果(热更新)。
3.6 总结:JavaScript 是 "互联网的通用语言"
从网页到游戏,从桌面到手机,从前端到后端 ------ JavaScript 几乎覆盖了互联网开发的所有领域。这也是为什么它是世界上最流行的编程语言之一:学会了 JavaScript,你就能做几乎所有互联网相关的开发工作!
四、JavaScript 的 "出生故事":10 天诞生的传奇语言
现在你知道 JavaScript 有多厉害了,但你可能不知道,它的诞生过程充满了 "戏剧性"------ 只用了 10 天,而且是 "赶鸭子上架" 的结果。这段历史非常有意思,了解它能帮你理解为什么 JavaScript 是现在这个样子。
我们结合阮一峰老师的《JavaScript 诞生记》,来详细聊聊这段故事。
4.1 背景:1994 年的互联网,需要一种 "网页脚本语言"
时间回到 1994 年 ------ 这一年,互联网还处于 "石器时代":
- 没有淘宝、没有微信、没有 Google;
- 最流行的浏览器是 网景公司(Netscape) 发布的 Navigator 0.9 版 ------ 这是历史上第一个比较成熟的网络浏览器,一发布就轰动了全世界;
- 但这个浏览器有个大问题:只能 "看" 网页,不能 "互动" ------ 比如你填了一个表单,点击 "提交",浏览器没法检查你填的对不对,只能把数据发给服务器,服务器检查完再告诉你结果,非常慢;比如你想在网页上点个按钮、玩个小游戏,根本做不到。
网景公司意识到:必须有一种 "网页脚本语言",让浏览器能和用户互动!这种语言要满足两个要求:
- 简单易学:非专业的网页作者(比如普通的设计师、编辑)也能很快上手;
- 能嵌入网页:直接写在 HTML 里,不用复杂的编译配置。
4.2 选择困难:用现有语言,还是发明新语言?
网景公司内部开始讨论:网页脚本语言到底用什么?当时有两个选择:
选择一:用现有的语言
比如 Perl、Python、Tcl、Scheme 等 ------ 这些语言已经很成熟了,有很多现成的代码和程序员资源,推广起来比较容易。
但缺点也很明显:这些语言不是专门为网页设计的,嵌入 HTML 会很复杂,而且可能不够 "简单"------ 比如 Scheme 是函数式编程语言,语法对新手来说太晦涩了。
选择二:发明一种全新的语言
专门为网页设计,完全适配网景浏览器,实现起来比较容易,而且能做到 "足够简单"。
但缺点是:要从零开始设计,没有现成的代码和程序员资源,推广起来可能比较难。
网景公司内部争执不下,管理层一时难以下定决心 ------ 就在这时,发生了一件大事,彻底改变了决策方向。
4.3 转折点:Sun 公司推出 Java,网景动心了
1995 年,Sun 公司 (后来被 Oracle 收购)把 Oak 语言改名为 Java ,正式向市场推出。Sun 公司大肆宣传 Java 的口号:"一次编写,到处运行"(Write Once, Run Anywhere) ------ 意思是用 Java 写的代码,不用修改,就能在 Windows、Mac、Linux 等所有平台上运行。
当时的 Java 太火了,几乎所有人都认为:Java 会成为未来的编程语言主宰!
网景公司也动了心,决定和 Sun 公司结成联盟:
- 网景允许 Java 程序以 Applet(小程序) 的形式,直接在浏览器里运行 ------ 比如你可以在网页里嵌入一个 Java 写的小游戏;
- 网景甚至考虑过:直接把 Java 作为网页脚本语言嵌入 HTML ------ 但后来发现这样会让 HTML 网页变得太复杂(Java 语法太严谨,对新手不友好),不得不放弃。
虽然没直接用 Java,但网景公司的管理层已经成了 Java 的 "信徒"------ 他们决定:未来的网页脚本语言,必须 "看上去与 Java 足够相似",但比 Java 简单!
这个决策,直接把 Perl、Python、Tcl、Scheme 等 "非面向对象、语法和 Java 不像" 的语言,全部排除在外了。
4.4 主角登场:Brendan Eich,一个 "被迫" 设计 JS 的人
1995 年 4 月,网景公司录用了一个叫 Brendan Eich 的 34 岁系统程序员 ------ 这个人就是后来的 "JavaScript 之父"。
Brendan Eich 的主要方向和兴趣是 函数式编程 (比如 Scheme 语言),网景公司招聘他的目的,是研究将 Scheme 语言作为网页脚本语言的可能性。Brendan Eich 本人也是这样想的,以为进入新公司后,会主要和 Scheme 语言打交道。
但仅仅一个月之后(1995 年 5 月),网景公司就做出了我们刚才说的决策:新语言要 "像 Java,但更简单"------ 而且,Brendan Eich 被指定为这种 "简化版 Java 语言" 的设计师!
Brendan Eich 当时的心情是怎样的?用现在的话说,就是 "拒绝三连":
- 他对 Java 一点兴趣也没有;
- 他本来想研究 Scheme,结果被安排设计 "像 Java" 的语言;
- 而且时间非常紧,必须尽快拿出成果。
但公司的安排不能拒绝,怎么办?Brendan Eich 只能 "赶鸭子上架"------ 为了应付公司安排的任务,他只用了 10 天时间,就把 JavaScript 设计出来了!
4.5 10 天设计出的 JS:借鉴了四种语言,混合了两种风格
虽然设计时间只有 10 天,但 Brendan Eich 并不是 "瞎写"------ 他借鉴了四种语言的优点,把它们混合在一起,形成了 JavaScript 的核心设计思路:
(1)借鉴 C 语言:基本语法
JavaScript 的基本语法,比如大括号 {}、分号 ;、if 语句、for 循环、变量声明等,几乎和 C 语言一模一样。
比如 C 语言里的 if 语句:
int a = 10;
if (a > 5) {
printf("a 大于 5");
}
JavaScript 里的 if 语句:
var a = 10;
if (a > 5) {
console.log("a 大于 5");
}
是不是几乎一样?Brendan Eich 借鉴 C 语言的语法,是因为 C 语言当时非常流行,很多程序员都熟悉,能降低学习成本。
(2)借鉴 Java 语言:数据类型和内存管理
JavaScript 的数据类型和内存管理,借鉴了 Java:
- 数据类型:Java 把数据分成 "基本类型"(primitive)和 "对象类型"(object),JavaScript 也一样 ------ 比如 JavaScript 里的数字、字符串、布尔值是基本类型,数组、对象是对象类型;
- 内存管理:Java 有 "垃圾回收机制"(Garbage Collection),程序员不用手动分配和释放内存,JavaScript 也一样 ------ JS 引擎会自动回收不再使用的内存,不用担心 "内存泄漏"(当然,写得不好还是会泄漏,但比手动管理内存简单多了)。
不过,Brendan Eich 后来对 Java 的这个借鉴 "不太满意"------ 比如 Java 里的 "基本类型包装成对象"(比如 int 对应 Integer),JavaScript 也学了(比如数字对应 Number 对象),但 Brendan Eich 觉得这个设计 "没什么必要"。
(3)借鉴 Scheme 语言:函数是 "第一等公民"
这是 JavaScript 最有特点的设计之一 ------ 函数是 "第一等公民"(First-class Function)。
什么意思?简单说就是:函数可以像变量一样使用 ------ 可以把函数赋值给变量,可以把函数作为参数传给另一个函数,可以把函数作为另一个函数的返回值。
这个设计是从 Scheme 语言(一种函数式编程语言)里借鉴来的。Brendan Eich 虽然被迫设计 "像 Java" 的语言,但他还是把自己喜欢的函数式编程思想,融入了 JavaScript 里 ------ 这也是 JavaScript 如此灵活、如此强大的重要原因之一。
举个简单的例子:把函数赋值给变量,然后调用它。
// 定义一个函数,用来打招呼
function sayHello(name) {
console.log("你好," + name + "!");
}
// 把函数赋值给变量
const greet = sayHello;
// 调用变量里的函数
greet("张三"); // 输出:你好,张三!
这个例子在 JavaScript 里非常常见,但在 Java 里(Java 8 之前)是做不到的 ------ 这就是函数式编程的魅力。
(4)借鉴 Self 语言:基于原型的继承机制
这是 JavaScript 和 Java 最大的区别之一 ------ JavaScript 用 "基于原型(prototype)的继承",而 Java 用 "基于类(class)的继承"。
什么是 "基于类的继承"?简单说就是:先定义一个 "类"(比如 "动物类"),然后从 "类" 里 "生成" 一个 "对象"(比如 "猫" 这个对象);如果要继承,就定义一个 "子类"(比如 "猫科动物类"),继承 "父类"("动物类")的属性和方法。
什么是 "基于原型的继承"?更简单:没有 "类",只有 "对象" ------ 一个对象可以直接作为另一个对象的 "原型",另一个对象就能共享 "原型对象" 的属性和方法。
这个设计是从 Self 语言里借鉴来的。Brendan Eich 觉得 "基于类的继承" 太复杂了,不适合简单的网页脚本语言,所以选择了更灵活的 "基于原型的继承"。
举个简单的例子:用原型继承,让 "猫" 对象共享 "动物" 对象的 "吃" 方法。
javascript
// 定义一个"动物"对象
const animal = {
eat: function() {
console.log("我在吃东西!");
}
};
// 定义一个"猫"对象,把"动物"对象作为它的原型
const cat = Object.create(animal);
// "猫"对象可以直接调用"动物"对象的"吃"方法
cat.eat(); // 输出:我在吃东西!
这个例子看起来简单,但背后的思想非常深刻 ------ 也是 JavaScript 最独特的地方之一。
总结:JavaScript 是两种风格的混合产物
所以,JavaScript 语言实际上是两种语言风格的混合产物:
- 简化的函数式编程(来自 Scheme 和 Brendan Eich 的个人兴趣);
- 简化的面向对象编程(来自 Java 和网景公司的要求)。
这是由 Brendan Eich(函数式编程)与网景公司(面向对象编程)共同决定的 ------ 也正是这种 "混合",让 JavaScript 既灵活又强大,既能写简单的网页脚本,又能写复杂的大型应用。
4.6 名字的由来:从 LiveScript 到 JavaScript
JavaScript 最初的名字叫 LiveScript ------ 因为它是 "用来让网页'活'(Live)起来的脚本语言"。
那为什么后来改名叫 JavaScript 了呢?主要有两个原因:
- 借 Java 的名气:当时 Java 太火了,带有 "Java" 的名字,有助于这门新生语言的传播;
- 网景和 Sun 的合作:网景和 Sun 公司是联盟,改名叫 JavaScript,也是两家公司合作的象征。
但这里要特别强调:Java 和 JavaScript 之间的语法风格相去甚远!它们的关系,就像 "雷锋" 和 "雷峰塔"------ 名字里都有 "雷峰",但实际上没有任何关系。
很多新手会误以为 "JavaScript 是 Java 的简化版",这是完全错误的!记住:它们是两门完全不同的编程语言,只是名字里都有 "Java" 而已。
4.7 设计时间太短的 "后遗症":细节不够严谨
因为设计时间只有 10 天,JavaScript 的一些细节考虑得不够严谨,导致后来很长一段时间,JavaScript 写出来的程序 "混乱不堪"------ 比如:
- 变量提升:变量可以在声明之前使用,导致逻辑混乱;
- 全局变量污染:如果不小心声明了全局变量,可能会和其他代码冲突;
- 类型转换混乱 :比如
[] == false是true,[] == ![]也是true,让人摸不着头脑。
这些问题,后来都通过 ECMAScript 标准的更新(比如 ES5、ES6)慢慢修复了,但早期的 JavaScript 确实因为这些问题,被很多程序员 "吐槽"。
Brendan Eich 后来也说过:"如果我预见到,未来这种语言会成为互联网第一大语言,全世界有几百万学习者,我会不会多花一点时间呢?" ------ 可惜历史没有如果,不过也正是因为这些 "不完美",才让 JavaScript 有了后来的发展和进步。
4.8 Brendan Eich 对 Java 的 "吐槽":Y2K 问题的锅
多年以后,Brendan Eich 还是 "看不起" Java ------ 他甚至说过:"与其说我爱 JavaScript,不如说我恨它。它是 C 语言和 Self 语言一夜情的产物。"
为什么他对 Java 这么大意见?其中一个重要原因是 Date.getYear () 的 Y2K 问题 ------ 这个问题完全是 "继承" 自 Java 的。
我们来详细看看这个问题:
- Java 里有个
java.util.Date类,它的getYear()方法,返回的是 "年份的最后两位" ------ 比如 1999 年返回99; - JavaScript 直接把这个类 "抄" 了过来,所以早期 JavaScript 的
Date.getYear()方法,也是一样的逻辑; - 但到了 2000 年,问题来了:
getYear()返回的是100(因为 2000 - 1900 = 100)!
我们来写代码验证一下:
// 测试 1999 年
var date1 = new Date(1999, 0, 1); // 注意:月份是从 0 开始的,0 表示 1 月
var year1 = date1.getYear();
console.log(year1); // 输出:99
// 测试 2000 年
var date2 = new Date(2000, 0, 1);
var year2 = date2.getYear();
console.log(year2); // 输出:100
如果用这个函数生成年份,某些网页可能会出现 "19100" 这样的结果 ------ 这就是所谓的 "Y2K 问题"(千年虫问题)。
Brendan Eich 显然对这个结果很不满意,所以后来 JavaScript 不得不添加了一个新的方法:Date.getFullYear() ------ 这个方法会返回四位数的年份,比如 1999 年返回 1999,2000 年返回 2000,完美解决了问题。
现在我们写代码,都应该用 getFullYear(),而不是 getYear() 了:
var date = new Date(2000, 0, 1);
console.log(date.getFullYear()); // 输出:2000
4.9 总结:JavaScript 的诞生,是 "偶然中的必然"
回顾 JavaScript 的诞生历史,我们可以说:它是 "偶然中的必然"------
- 偶然的是:它只用了 10 天,是 Brendan Eich "被迫" 设计的,名字是借 Java 的;
- 必然的是:当时的互联网需要一种 "简单、灵活、能嵌入网页" 的脚本语言,JavaScript 正好满足了这个需求。
正是这种 "偶然中的必然",让 JavaScript 从一门 "赶鸭子上架" 的语言,变成了今天世界上最流行的编程语言之一。了解这段历史,能帮我们更好地理解 JavaScript 的设计思路,也能让我们在学习时更有耐心 ------ 毕竟,它的 "不完美" 是有历史原因的。
五、JavaScript 和 HTML、CSS 的关系:骨、皮、魂
在文章的开头,我们简单提到过:HTML 是 "骨",CSS 是 "皮",JavaScript 是 "魂"。现在我们来详细展开这个比喻,用一个具体的例子,让你彻底搞懂它们的关系!
5.1 HTML:网页的 "骨头"------ 负责结构
HTML(HyperText Markup Language,超文本标记语言)的作用,是给网页 "搭结构" ------ 就像盖房子时,先搭好钢筋骨架,确定哪里是客厅、哪里是卧室、哪里是厨房。
HTML 用 "标签" 来定义网页的结构,常见的标签有:
<h1>~<h6>:标题标签(<h1>是一级标题,最大;<h6>是六级标题,最小);<p>:段落标签;<button>:按钮标签;<input>:输入框标签;<img>:图片标签;<div>:块级容器标签(用来把其他标签包在一起,方便布局);<span>:行内容器标签(用来包裹文字,方便修改样式)。
举个例子:用 HTML 写一个 "用户注册表单" 的结构。
html
<!DOCTYPE html>
<html>
<head>
<title>用户注册</title>
</head>
<body>
<!-- 一级标题 -->
<h1>用户注册</h1>
<!-- 用户名输入框 -->
<div>
<label>用户名:</label>
<input type="text" id="username">
</div>
<!-- 密码输入框 -->
<div>
<label>密码:</label>
<input type="password" id="password">
</div>
<!-- 注册按钮 -->
<button id="registerBtn">注册</button>
</body>
</html>
把这段代码保存成 .html 文件打开,你会看到:
- 一个 "用户注册" 的大标题;
- 两个输入框(用户名和密码);
- 一个 "注册" 按钮。
但这个页面非常 "丑"------ 没有颜色、没有布局、按钮和输入框挤在一起,就像一个没有皮肤的 "骨架"。这时候,就需要 CSS 出场了!
5.2 CSS:网页的 "皮肤"------ 负责表现
CSS(Cascading Style Sheets,层叠样式表)的作用,是给网页 "化妆" ------ 就像给骨架贴上皮肤、穿上衣服,让它变得好看。
CSS 可以控制网页元素的:
- 颜色:文字颜色、背景颜色;
- 字体:字体大小、字体类型(比如宋体、微软雅黑);
- 布局:元素的位置、宽度、高度、边距、内边距;
- 外观:按钮的圆角、图片的边框、元素的阴影。
我们给刚才的 HTML 加上 CSS,让它变好看:
css
<!DOCTYPE html>
<html>
<head>
<title>用户注册</title>
<style>
/* 给整个页面设置背景颜色和字体 */
body {
background-color: #f0f0f0; /* 浅灰色背景 */
font-family: "微软雅黑", sans-serif; /* 字体用微软雅黑 */
padding: 20px; /* 页面内边距 20 像素 */
}
/* 给一级标题设置颜色和居中 */
h1 {
color: #333; /* 深灰色文字 */
text-align: center; /* 文字居中 */
}
/* 给每个输入框的容器设置边距 */
div {
margin-bottom: 15px; /* 底部边距 15 像素 */
text-align: center; /* 内容居中 */
}
/* 给标签设置宽度和对齐方式 */
label {
display: inline-block; /* 行内块元素 */
width: 80px; /* 宽度 80 像素 */
text-align: right; /* 文字右对齐 */
margin-right: 10px; /* 右边距 10 像素 */
}
/* 给输入框设置宽度、内边距和边框 */
input {
width: 200px; /* 宽度 200 像素 */
padding: 5px; /* 内边距 5 像素 */
border: 1px solid #ccc; /* 灰色边框 */
border-radius: 3px; /* 圆角 3 像素 */
}
/* 给注册按钮设置样式 */
#registerBtn {
display: block; /* 块级元素,独占一行 */
width: 100px; /* 宽度 100 像素 */
height: 35px; /* 高度 35 像素 */
margin: 0 auto; /* 左右自动边距,居中 */
background-color: #007bff; /* 蓝色背景 */
color: white; /* 白色文字 */
border: none; /* 去掉边框 */
border-radius: 5px; /* 圆角 5 像素 */
cursor: pointer; /* 鼠标移上去变成手型 */
}
/* 鼠标移到按钮上时,改变背景颜色 */
#registerBtn:hover {
background-color: #0056b3; /* 深蓝色背景 */
}
</style>
</head>
<body>
<!-- 这里的 HTML 和刚才一样,省略 -->
<h1>用户注册</h1>
<div>
<label>用户名:</label>
<input type="text" id="username">
</div>
<div>
<label>密码:</label>
<input type="password" id="password">
</div>
<button id="registerBtn">注册</button>
</body>
</html>
现在再打开这个页面,你会发现:
- 页面背景变成了浅灰色,字体变成了微软雅黑;
- 标题居中,颜色是深灰色;
- 输入框和标签整齐排列,有圆角和边框;
- 注册按钮是蓝色的,鼠标移上去会变成深蓝色,而且是居中的。
这个页面现在 "好看" 了,但还是 "死" 的 ------ 点击 "注册" 按钮,没有任何反应;输入用户名和密码,也不会检查是否合法。这时候,就需要 JavaScript 出场了!
5.3 JavaScript:网页的 "灵魂"------ 负责行为
JavaScript 的作用,是给网页 "注入灵魂" ------ 让它能和用户互动,能 "动" 起来。
我们给刚才的页面加上 JavaScript,实现两个功能:
- 点击 "注册" 按钮时,检查用户名和密码是否为空;
- 如果为空,弹出提示;如果不为空,弹出 "注册成功"。
代码如下:
html
<!DOCTYPE html>
<html>
<head>
<title>用户注册</title>
<style>
/* 这里的 CSS 和刚才一样,省略 */
body { background-color: #f0f0f0; font-family: "微软雅黑", sans-serif; padding: 20px; }
h1 { color: #333; text-align: center; }
div { margin-bottom: 15px; text-align: center; }
label { display: inline-block; width: 80px; text-align: right; margin-right: 10px; }
input { width: 200px; padding: 5px; border: 1px solid #ccc; border-radius: 3px; }
#registerBtn { display: block; width: 100px; height: 35px; margin: 0 auto; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; }
#registerBtn:hover { background-color: #0056b3; }
</style>
</head>
<body>
<h1>用户注册</h1>
<div>
<label>用户名:</label>
<input type="text" id="username">
</div>
<div>
<label>密码:</label>
<input type="password" id="password">
</div>
<button id="registerBtn">注册</button>
<script>
// 1. 获取注册按钮元素
const registerBtn = document.getElementById('registerBtn');
// 2. 给按钮添加"点击事件监听器"
registerBtn.addEventListener('click', function() {
// 3. 获取用户名和密码输入框的内容
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
// 4. 检查用户名是否为空
if (username === '') {
alert('请输入用户名!');
return; // 停止执行后面的代码
}
// 5. 检查密码是否为空
if (password === '') {
alert('请输入密码!');
return;
}
// 6. 如果都不为空,弹出注册成功
alert('注册成功!用户名:' + username);
});
</script>
</body>
</html>
现在再打开这个页面,试试:
- 如果不输入用户名,直接点击 "注册",会弹出 "请输入用户名!";
- 如果输入了用户名,但不输入密码,会弹出 "请输入密码!";
- 如果都输入了,会弹出 "注册成功!用户名:XXX"。
终于!这个页面 "活" 了 ------ 它能和你互动,能根据你的操作做出反应。这就是 JavaScript 的魔力!
5.4 总结:三者缺一不可,共同构成网页
现在你应该彻底理解了:
- HTML 是 "骨头"------ 没有它,网页就没有结构,立不起来;
- CSS 是 "皮肤"------ 没有它,网页就不好看,用户不想用;
- JavaScript 是 "灵魂"------ 没有它,网页就不能互动,只是个 "摆设"。
三者缺一不可,共同构成了我们每天看到的、丰富多彩的网页世界!
六、JavaScript 的运行过程:从硬盘到 CPU,一步步拆解
很多新手会好奇:我写的 JS 代码,是怎么变成电脑上的操作的?比如我写 alert('你好'),为什么点击按钮就会弹出提示框?
现在我们就来一步步拆解 JavaScript 的运行过程,用生活中的例子,让你彻底搞懂!
6.1 第一步:代码保存在硬盘(外存)里
首先,你写的 JS 代码,是保存在文件 里的 ------ 比如 .html 文件(内嵌式或行内式 JS),或者 .js 文件(外部式 JS)。这些文件,是存储在你的电脑硬盘(或 SSD) 里的。
硬盘属于 "外存"------ 特点是 "容量大、断电后数据不会丢失,但读取速度慢"。就像你家里的 "书架"------ 能放很多书,书放在上面不会丢,但你要拿书,需要走过去,比较慢。
6.2 第二步:浏览器读取文件,加载到内存里
当你双击 .html 文件(或者在浏览器里输入网址,访问网页)时,浏览器(比如 Chrome)会启动,然后:
- 从硬盘里读取
.html文件(以及它引用的.css、.js文件); - 把这些文件的内容,加载到内存(RAM) 里。
内存属于 "内存"------ 特点是 "容量小、断电后数据会丢失,但读取速度特别快"。就像你手里的 "案板"------ 能放的东西不多,放下的东西很快就能拿到,但你离开厨房(断电),案板上的东西就会被收走。
为什么要加载到内存里?因为 CPU 只能直接读取内存里的数据,不能直接读取硬盘里的数据 ------ 就像你炒菜时,只能拿案板上的菜,不能直接拿书架上的菜谱。
6.3 第三步:浏览器解析代码,分成两部分处理
浏览器加载完文件后,会开始解析代码------ 它会把代码分成两部分,分别交给两个 "引擎" 处理:
(1)渲染引擎:解析 HTML 和 CSS
渲染引擎(俗称 "浏览器内核")的工作是:
- 解析 HTML,生成 DOM 树(Document Object Model,文档对象模型)------ 就像把 HTML 标签,变成一棵 "树",每个标签是树上的一个 "节点";
- 解析 CSS,生成 CSSOM 树(CSS Object Model,CSS 对象模型)------ 就像把 CSS 样式,也变成一棵 "树";
- 把 DOM 树和 CSSOM 树结合起来,生成 渲染树(Render Tree);
- 根据渲染树,计算每个元素的位置和大小(布局);
- 把元素绘制到屏幕上(绘制)。
经过这几步,我们就能看到网页的 "样子" 了 ------ 也就是 HTML 和 CSS 构成的 "骨" 和 "皮"。
不同的浏览器有不同的渲染引擎:
- Chrome 和 Edge:Blink(基于 WebKit);
- Firefox:Gecko;
- Safari:WebKit。
(2)JS 引擎:解析和执行 JavaScript
JS 引擎的工作是:解析和执行 JavaScript 代码。我们以 Chrome 的 V8 引擎为例,看看它是怎么工作的:
- 词法分析 :把 JS 代码拆成一个个 "词法单元"(Token)------ 比如
var a = 10;会被拆成var、a、=、10、;; - 语法分析:把词法单元组成 "抽象语法树"(AST,Abstract Syntax Tree)------ 就像把词法单元,变成一棵 "树",表示代码的逻辑结构;
- 代码生成:把抽象语法树,翻译成计算机能看懂的 "二进制指令"(机器码);
- 执行指令:把二进制指令交给 CPU,让 CPU 去执行。
不过,现代的 JS 引擎(比如 V8)为了提高运行速度,会用 "即时编译(JIT,Just-In-Time Compilation)" 技术 ------ 简单说就是:把经常执行的代码(比如循环里的代码),提前编译成机器码,保存起来,下次执行时直接用,不用再重新翻译。这样,JS 的运行速度就接近编译型语言了!
6.4 第四步:CPU 执行二进制指令,完成操作
最后,JS 引擎把翻译好的二进制指令,交给 CPU(中央处理器,电脑的 "大脑")去执行。CPU 执行完指令后,就会完成我们想要的操作 ------ 比如:
- 执行
alert('你好'):CPU 会调用浏览器的 API,在屏幕上弹出一个提示框; - 执行
console.log('日志'):CPU 会调用浏览器的 API,在开发者工具的控制台里打印日志; - 执行修改网页文字的代码:CPU 会调用渲染引擎,重新布局和绘制网页,更新屏幕上的内容。
6.5 一个完整的例子:点击按钮弹出提示
现在我们把这四步结合起来,看一个完整的例子:点击按钮,弹出 "你好"。
代码如下:
<!DOCTYPE html>
<html>
<body>
<button onclick="alert('你好')">点我</button>
</body>
</html>
运行过程:
- 保存到硬盘 :代码保存在
test.html文件里,放在硬盘上; - 加载到内存 :双击
test.html,Chrome 启动,读取文件,加载到内存; - 渲染引擎解析 HTML:生成 DOM 树,绘制按钮到屏幕上;
- 等待用户操作:你点击屏幕上的按钮;
- JS 引擎解析和执行 :
- 浏览器捕获到 "点击按钮" 的事件;
- JS 引擎读取
onclick里的代码alert('你好'); - 词法分析、语法分析、生成二进制指令;
- CPU 执行指令:CPU 执行二进制指令,调用浏览器的 API,弹出提示框;
- 你看到结果:屏幕上弹出 "你好" 的提示框。
6.6 总结:运行过程就像 "做饭"
为了让你更好地记住,我们把 JavaScript 的运行过程,类比成 "做饭":
- 硬盘(外存):书架,放菜谱(代码文件);
- 内存:案板,放要用到的菜和工具(加载的文件内容);
- 浏览器:厨师,负责整个流程;
- 渲染引擎:帮厨,负责准备食材(解析 HTML 和 CSS,绘制网页);
- JS 引擎:翻译官,负责把中文菜谱(JS 代码)翻译成厨师能懂的指令(二进制指令);
- CPU:灶台,负责执行指令(炒菜)。
整个流程就是:厨师从书架拿菜谱(硬盘→内存),帮厨准备食材(渲染引擎解析 HTML/CSS),翻译官翻译菜谱(JS 引擎解析 JS),灶台炒菜(CPU 执行指令),最后做出菜(弹出提示框、更新网页等)。
七、JavaScript 的组成:三驾马车,缺一不可
很多教材会说:JavaScript 由三部分组成 ------ ECMAScript 、DOM 、BOM。这三部分是什么?有什么用?我们来详细讲!
7.1 ECMAScript:JavaScript 的 "语法标准"
ECMAScript(简称 ES)是 JavaScript 的语法标准------ 它规定了 JavaScript 的 "核心语法",比如:
- 变量怎么声明(
var、let、const); - 数据类型有哪些(数字、字符串、布尔值、数组、对象等);
- 运算符怎么用(
+、-、*、/、==、===等); - 流程控制怎么写(
if语句、for循环、while循环等); - 函数怎么定义和调用;
- 等等......
可以把 ECMAScript 想象成 "车同轨、书同文" 的标准------ 不管是 Chrome 的 V8 引擎,还是 Firefox 的 SpiderMonkey 引擎,或者是 Node.js 的 V8 引擎,都必须遵守这个标准来实现。这样,你写的 JavaScript 代码,在任何浏览器、任何 Node.js 环境里,都能运行(只要环境支持对应的 ES 版本)。
ECMAScript 有很多版本,比如:
- ES3:1999 年发布,是早期 JavaScript 的标准;
- ES5 :2009 年发布,修复了很多 ES3 的问题,增加了一些新特性(比如
forEach、map等数组方法); - ES6(ES2015) :2015 年发布,是 JavaScript 历史上最大的一次更新,增加了很多新特性(比如
let、const、箭头函数、类、Promise 等); - ES2016~ES2025 :每年发布一个新版本,增加一些小特性(比如 ES2016 的
Array.prototype.includes,ES2020 的可选链操作符?.等)。
我们现在学习 JavaScript,主要学习 ES5 和 ES6+(ES2015 及以后的版本)------ 因为这些版本是现在最常用的。
7.2 DOM:页面文档对象模型,操作网页元素
DOM(Document Object Model,文档对象模型)是浏览器提供的一套 API(Application Programming Interface,应用程序编程接口)------ 它把网页上的每个 HTML 元素,都变成一个 "对象",你可以用 JavaScript 调用这些对象的属性和方法,来操作网页元素。
这个非常重要~
比如:
- 获取网页上的按钮元素:
document.getElementById('btn'); - 修改元素的文字内容:
element.textContent = '新文字'; - 修改元素的样式:
element.style.color = 'red'; - 给元素添加点击事件:
element.addEventListener('click', function() { ... }); - 等等......
可以把 DOM 想象成 "遥控器"------ 网页元素是 "电视",DOM 就是 "遥控器",你可以用遥控器(DOM API)控制电视(网页元素)的开关、频道、音量等。
DOM 有一个核心概念:DOM 树 ------ 浏览器解析 HTML 时,会把 HTML 标签变成一棵 "树",树的根节点是 document 对象,每个 HTML 标签是树上的一个 "元素节点",标签里的文字是 "文本节点",标签的属性是 "属性节点"。
比如下面的 HTML:
<!DOCTYPE html>
<html>
<body>
<h1>标题</h1>
<p>段落</p>
</body>
</html>
对应的 DOM 树大概是这样的:
document
└── html
├── head
└── body
├── h1
│ └── 文本节点:标题
└── p
└── 文本节点:段落
你可以用 JavaScript 遍历这棵树,找到你想要的节点,然后操作它 ------ 这就是 DOM 的核心作用。
7.3 BOM:浏览器对象模型,操作浏览器窗口
BOM(Browser Object Model,浏览器对象模型)也是浏览器提供的一套 API------ 它把浏览器窗口变成一个 "对象",你可以用 JavaScript 调用这些对象的属性和方法,来操作浏览器窗口。
比如:
- 弹出一个新窗口:
window.open('https://www.baidu.com'); - 获取浏览器的 URL:
window.location.href; - 刷新页面:
window.location.reload(); - 前进 / 后退页面:
window.history.forward()/window.history.back(); - 获取浏览器的信息(比如浏览器类型、版本):
window.navigator.userAgent; - 等等......
可以把 BOM 想象成 "汽车的控制面板"------ 浏览器窗口是 "汽车",BOM 就是 "控制面板",你可以用控制面板控制汽车的窗户、方向盘、油门等。
BOM 的核心对象是 window ------ 它是浏览器的 "全局对象",所有的全局变量和全局函数,都是 window 对象的属性和方法。比如:
alert()其实是window.alert();console.log()其实是window.console.log();- 你声明的全局变量
var a = 10,其实是window.a = 10。
7.4 总结:三驾马车,各司其职
现在你应该明白了:
- ECMAScript:核心语法,是 JavaScript 的 "基础"------ 没有它,你没法写 JS 代码;
- DOM:操作网页元素,是 JavaScript 的 "网页互动工具"------ 没有它,你没法操作网页;
- BOM:操作浏览器窗口,是 JavaScript 的 "浏览器控制工具"------ 没有它,你没法控制浏览器。
这三部分结合起来,就是完整的 JavaScript!不过要注意:
- 在浏览器端运行 JS,需要这三部分;
- 在服务器端(Node.js) 运行 JS,只需要 ECMAScript,加上 Node.js 的 API ------ 因为服务器端没有网页和浏览器窗口,所以不需要 DOM 和 BOM。
八、JavaScript 的书写形式:三种方式,各有优缺点
JavaScript 代码有三种书写形式:行内式 、内嵌式 、外部式。每种形式都有自己的优缺点,适合不同的场景。我们来详细讲!
8.1 行内式:直接写在 HTML 元素的属性里
行内式,就是把 JS 代码直接写在 HTML 元素的 "事件属性" 里 ------ 比如 onclick(点击事件)、onmouseover(鼠标移入事件)等。
例子
html
<!DOCTYPE html>
<html>
<body>
<!-- 点击按钮,弹出提示 -->
<button onclick="alert('你好,行内式!')">点我一下</button>
<!-- 鼠标移入 div,改变背景颜色;移出,恢复 -->
<div
style="width: 100px; height: 100px; background-color: red;"
onmouseover="this.style.backgroundColor='blue'"
onmouseout="this.style.backgroundColor='red'"
></div>
</body>
</html>
注意事项
- HTML 里的属性推荐用双引号 ,JS 里的字符串推荐用单引号 ------ 避免冲突,比如
onclick="alert('haha')"是对的,onclick='alert("haha")'也可以,但onclick="alert("haha")"就会报错(因为双引号提前结束了); this关键字:在事件属性里,this表示 "当前触发事件的 HTML 元素"------ 比如上面的例子里,this.style.backgroundColor就是改变当前 div 的背景颜色。
优缺点
- 优点 :简单、方便,适合写非常小的功能(比如测试一下某个事件);
- 缺点 :
- HTML 和 JS 混在一起,代码多了会非常乱,难以维护;
- 代码不能复用 ------ 比如你有 10 个按钮,都要弹出同样的提示,就得写 10 遍
onclick="alert('...')"; - 不符合 "结构与行为分离" 的原则 ------ HTML 负责结构,JS 负责行为,混在一起不好。
总结 :行内式只适合写 "测试代码" 或 "极小的功能",实际开发中不推荐使用。
8.2 内嵌式:写在 HTML 的 script 标签里
内嵌式,就是把 JS 代码写在 HTML 的 <script> 标签里 ------ <script> 标签可以放在 HTML 的 <head> 里,也可以放在 <body> 里。
例子
html
<!DOCTYPE html>
<html>
<head>
<title>内嵌式示例</title>
<!-- script 标签放在 head 里 -->
<script>
// 这是一个函数,用来打招呼
function sayHello(name) {
alert("你好," + name + "!");
}
</script>
</head>
<body>
<button onclick="sayHello('张三')">点击打招呼</button>
<!-- script 标签也可以放在 body 的最后(推荐) -->
<script>
// 获取按钮元素
const btn = document.getElementById('testBtn');
// 给按钮添加点击事件
btn.addEventListener('click', function() {
console.log("这是在 body 最后的 script 里执行的!");
});
</script>
<button id="testBtn">点击打印日志</button>
</body>
</html>
注意事项:script 标签的位置
<script> 标签的位置非常重要,因为浏览器是从上到下解析 HTML 的:
- 如果把
<script>放在<head>里,JS 代码会在 HTML 元素解析之前执行 ------ 这时候,如果你在 JS 里获取 HTML 元素(比如document.getElementById('btn')),会获取不到(因为元素还没解析); - 如果把
<script>放在<body>的最后,JS 代码会在所有 HTML 元素解析之后执行 ------ 这时候,你可以放心地获取 HTML 元素,因为它们都已经解析好了。
总结 :推荐把 <script> 标签放在 <body> 的最后!
优缺点
- 优点 :
- HTML 和 JS 分离了一点,代码比行内式清晰;
- 适合写中等长度的代码,或者 "单页面的小功能";
- 缺点 :
- 代码还是在一个 HTML 文件里,代码多了还是难以维护;
- 代码不能复用 ------ 比如你有多个 HTML 文件,都要用同样的 JS 代码,就得在每个 HTML 文件里都写一遍
<script>标签里的代码。
总结 :内嵌式适合写 "单页面的小功能",或者 "测试代码",大型项目中不推荐使用。
8.3 外部式:写在单独的 .js 文件里
外部式,就是把 JS 代码写在单独的 .js 文件里,然后在 HTML 里用 <script> 标签的 src 属性引入它。
例子
第一步:创建一个 hello.js 文件,写 JS 代码:
// hello.js 文件里的代码
function sayHello(name) {
alert("你好," + name + "!");
}
// 页面加载完成后,执行代码
window.onload = function() {
const btn = document.getElementById('helloBtn');
btn.addEventListener('click', function() {
sayHello('李四');
});
};
第二步:创建一个 index.html 文件,引入 hello.js:
<!DOCTYPE html>
<html>
<head>
<title>外部式示例</title>
</head>
<body>
<button id="helloBtn">点击打招呼</button>
<!-- 引入 hello.js 文件 -->
<script src="hello.js"></script>
</body>
</html>
注意事项
- 引入外部 JS 的
<script>标签中间不能写代码 ------ 写了也不会执行,比如<script src="hello.js">alert('这行不会执行');</script>是错误的; <script>标签的src属性,可以是相对路径 (比如hello.js、./js/hello.js),也可以是绝对路径 (比如https://cdn.example.com/hello.js);- 可以引入多个外部 JS 文件 ------ 比如
<script src="a.js"></script><script src="b.js"></script>,浏览器会从上到下依次加载和执行。
优缺点
- 优点 :
- HTML 和 JS 完全分离 ,代码清晰,易于维护;
- 代码可以复用 ------ 多个 HTML 文件可以引入同一个
.js文件,修改代码只需要改.js文件; - 浏览器可以缓存
.js文件 ------ 第一次加载后,后面再访问这个页面,就不用重新下载.js文件了,提高页面加载速度;
- 缺点 :
- 需要多创建一个
.js文件,小项目可能麻烦一点 ------ 但这是值得的!
- 需要多创建一个
总结 :外部式是实际开发中最推荐的方式,不管项目大小,都应该尽量用外部式!
8.4 总结:三种方式的对比
为了让你更清楚,我们用一个表格对比三种书写形式:
| 书写形式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 行内式 | 简单、方便 | HTML 和 JS 混在一起、难以维护、不能复用 | 测试代码、极小的功能 |
| 内嵌式 | HTML 和 JS 分离一点、适合中等代码 | 代码仍在 HTML 里、难以维护、不能复用 | 单页面小功能、测试代码 |
| 外部式 | 完全分离、易于维护、可复用、可缓存 | 需要多创建文件 | 所有实际开发项目 |
九、JavaScript 的注释:给代码写 "说明书",方便自己和别人
什么是 "注释"?注释就是给代码写的 "说明书" ------ 它是写给人看的,不是写给计算机看的,JS 引擎会自动忽略注释,不会执行它。
注释非常重要!尤其是当你写了很多代码,过了几个月再看,或者别人看你的代码时,注释能帮你快速理解:这段代码是做什么的?为什么要这么写?
JavaScript 有两种注释:单行注释 和多行注释。
9.1 单行注释://
单行注释用 // 表示 ------ // 后面的内容,直到这一行结束,都是注释,JS 引擎会忽略。
例子
// 这是一个单行注释,用来解释下面的代码
var a = 10; // 声明一个变量 a,赋值为 10(这也是单行注释)
// 下面的代码用来计算 a + b
var b = 20;
var sum = a + b; // 计算 a 和 b 的和,存到 sum 里
console.log(sum); // 在控制台打印 sum 的值(输出 30)
快捷键
在 VSCode(以及大多数代码编辑器)里,你可以用 Ctrl + / (Windows/Linux)或 Cmd + /(Mac)来快速切换单行注释:
- 选中一行代码,按
Ctrl + /,这行代码就会变成注释; - 再按一次
Ctrl + /,注释就会变回代码; - 选中多行代码,按
Ctrl + /,每一行都会变成单行注释。
适用场景
单行注释适合注释简短的内容------ 比如解释某一行代码的作用,或者某一个变量的含义。
9.2 多行注释:/* */
多行注释用 /* 开头,*/ 结尾 ------ 中间的内容,不管有多少行,都是注释,JS 引擎会忽略。
例子
/*
这是一个多行注释
可以写很多行内容
用来解释一大段代码的作用
或者写一些详细的说明
*/
/*
函数名:calculateSum
作用:计算两个数的和
参数:
- num1:第一个数(数字类型)
- num2:第二个数(数字类型)
返回值:两个数的和(数字类型)
*/
function calculateSum(num1, num2) {
return num1 + num2;
}
// 调用函数
var result = calculateSum(10, 20);
console.log(result); // 输出 30
快捷键
在 VSCode 里,你可以用 Shift + Alt + A (Windows/Linux)或 Shift + Option + A(Mac)来快速切换多行注释。
注意事项:多行注释不能嵌套
多行注释不能嵌套------ 也就是不能在一个多行注释里,再写另一个多行注释。比如下面的代码会报错:
/*
这是第一个多行注释
/*
这是嵌套的多行注释 ------ 这里会报错!
*/
第一个多行注释的结尾
*/
为什么会报错?因为 JS 引擎遇到第一个 /* 后,会一直找第一个 */,找到就认为注释结束了 ------ 上面的代码里,第一个 /* 遇到嵌套的 */ 就结束了,后面的 */ 就变成了 "多余的符号",所以会报错。
适用场景
多行注释适合注释大段的内容------ 比如解释一个函数的作用、参数、返回值,或者暂时注释掉一大段不想执行的代码(调试时常用)。
9.3 注释的最佳实践
写注释也有 "最佳实践"------ 好的注释能帮你和别人更好地理解代码,坏的注释反而会让人更困惑。这里给新手几个建议:
- 不要写 "废话" 注释 ------ 比如
var a = 10; // 声明变量 a 为 10,这种注释是废话,因为代码本身已经说明了; - 要写 "为什么这么写" 的注释 ------ 比如
// 这里用 parseInt 而不是 Number,因为输入可能有非数字字符,这种注释能帮别人理解你的思路; - 要写 "复杂逻辑" 的注释------ 比如一段复杂的算法,或者一段容易出错的逻辑,一定要写注释;
- 注释要简洁明了------ 不要写太长的句子,用简短的语言说明白就行;
- 保持注释和代码同步------ 如果你修改了代码,一定要同时修改对应的注释,否则注释会误导别人。
9.4 总结:注释是代码的 "好帮手"
注释不是 "多余的",而是代码的 "好帮手"------ 它能帮你记住代码的思路,能帮别人快速理解你的代码。新手一定要养成多写注释的好习惯!
十、JavaScript 的输入输出:和用户互动的基础
现在我们终于到了 "实战环节"------ JavaScript 的输入输出!这是 JavaScript 最基础、最常用的功能,也是你和用户互动的第一步。
JavaScript 有三个最基础的输入输出函数:
- 输入 :
prompt()------ 弹出输入框,让用户输入内容; - 输出 :
alert()------ 弹出警示框,显示内容; - 输出 :
console.log()------ 在控制台打印日志(供程序员看)。
我们来一个个详细讲!
10.1 输入:prompt () ------ 弹出输入框
prompt() 函数的作用是:弹出一个输入框,让用户输入内容,然后返回用户输入的内容。
语法
var 用户输入的内容 = prompt(提示文字, 默认值);
参数说明
- 提示文字(可选):字符串,用来告诉用户要输入什么 ------ 比如 "请输入您的姓名";
- 默认值(可选):字符串,输入框里会预先填好这个值 ------ 比如 "18"。
返回值说明
- 如果用户点击确定 :返回用户输入的字符串(注意:不管用户输入的是数字还是文字,返回的都是字符串!);
- 如果用户点击取消 :返回
null(空值)。
例子 1:简单的输入框
// 弹出输入框,提示"请输入您的姓名",没有默认值
var name = prompt("请输入您的姓名");
// 弹出提示框,显示用户输入的名字
alert("您好," + name + "!");
执行这段代码:
- 浏览器会弹出一个输入框,提示 "请输入您的姓名";
- 你输入 "张三",点击确定;
- 浏览器会弹出提示框,显示 "您好,张三!"。
例子 2:带默认值的输入框
// 弹出输入框,提示"请输入您的年龄",默认值是"18"
var age = prompt("请输入您的年龄", "18");
alert("您的年龄是:" + age + "岁");
执行这段代码:
- 输入框里会预先填好 "18";
- 你可以直接点击确定,也可以修改成其他数字(比如 "20");
- 点击确定后,会弹出提示框显示年龄。
例子 3:处理点击取消的情况
如果用户点击取消,prompt() 会返回 null ------ 我们可以用 if 语句来处理这种情况。
var name = prompt("请输入您的姓名");
// 检查用户是否点击了取消
if (name === null) {
alert("您取消了输入!");
} else {
// 检查用户是否输入了内容
if (name === "") {
alert("您没有输入任何内容!");
} else {
alert("您好," + name + "!");
}
}
执行这段代码:
- 如果你点击取消:会弹出 "您取消了输入!";
- 如果你不输入内容,直接点击确定:会弹出 "您没有输入任何内容!";
- 如果你输入 "张三",点击确定:会弹出 "您好,张三!"。
注意事项:返回的是字符串!
这里要特别强调:不管用户输入的是数字还是文字,prompt() 返回的都是字符串!
比如用户输入 "18",返回的是字符串 "18",而不是数字 18 ------ 如果你直接用它做加法,会得到 "字符串拼接" 的结果,而不是 "数字相加" 的结果。
举个例子:
var num1 = prompt("请输入第一个数字");
var num2 = prompt("请输入第二个数字");
var sum = num1 + num2;
alert("和是:" + sum);
执行这段代码:
- 输入 "10" 和 "20";
- 你以为会弹出 "和是:30",但实际上会弹出 "和是:1020"!
为什么?因为 num1 是字符串 "10",num2 是字符串 "20",+ 号在字符串之间是 "拼接" 的意思,所以 "10" + "20" 就是 "1020"。
那怎么解决?我们需要用 Number() 函数,把字符串转换成数字:
// 用 Number() 把字符串转换成数字
var num1 = Number(prompt("请输入第一个数字"));
var num2 = Number(prompt("请输入第二个数字"));
var sum = num1 + num2;
alert("和是:" + sum);
现在再执行:
- 输入 "10" 和 "20";
- 会弹出 "和是:30"------ 对了!
关于数据类型和类型转换,我们后面会详细讲,现在只要记住:prompt() 返回的是字符串,如果要做数字运算,一定要先转换成数字!
10.2 输出:alert () ------ 弹出警示框
alert() 函数的作用是:弹出一个警示框,显示你指定的内容,然后等待用户点击确定。
语法
alert(要显示的内容);
参数说明
- 要显示的内容:可以是字符串、数字、变量、表达式等 ------ 只要能转换成字符串就行。
例子
// 显示字符串
alert("你好,JavaScript!");
// 显示数字
alert(123);
// 显示变量
var a = 10;
alert(a);
// 显示表达式
var b = 20;
alert(a + b); // 显示 30
// 显示字符串拼接
var name = "张三";
alert("您好," + name + "!");
注意事项:alert () 会 "阻塞" 代码执行
alert() 有一个特点:它会 "阻塞" 代码执行------ 也就是弹出警示框后,后面的代码会暂停执行,直到用户点击确定,后面的代码才会继续执行。
举个例子:
alert("第一个提示"); // 先弹出这个
alert("第二个提示"); // 点击确定后,才会弹出这个
alert("第三个提示"); // 再点击确定,才会弹出这个
执行这段代码:
- 先弹出 "第一个提示";
- 点击确定,弹出 "第二个提示";
- 再点击确定,弹出 "第三个提示"。
这个特点在调试时可能有用,但在实际开发中,alert() 用得越来越少了 ------ 因为它会阻塞代码,影响用户体验,而且只能显示简单的内容。现在更多用 console.log() 来调试,或者用 HTML/CSS/JS 自己做 "弹窗组件"。
10.3 输出:console.log () ------ 在控制台打印日志
console.log() 函数的作用是:在浏览器的 "控制台" 里打印日志------ 这些日志是给程序员看的,普通用户看不到(除非他们打开开发者工具)。
console.log() 是调试 JavaScript 代码最常用、最有用的工具!
语法
console.log(要打印的内容1, 要打印的内容2, ...);
参数说明
- 要打印的内容:可以是多个,用逗号隔开 ------ 可以是字符串、数字、变量、对象、数组等,几乎什么都能打印。
怎么打开控制台?
要看到 console.log() 打印的日志,需要打开浏览器的开发者工具:
- 打开 Chrome(或其他浏览器);
- 按 F12 键(或者右键点击网页,选择 "检查");
- 在弹出的面板里,点击 Console(控制台)标签页 ------ 这里就是看日志的地方。
例子 1:打印简单内容
// 打印字符串
console.log("这是一条日志");
// 打印数字
console.log(123);
// 打印变量
var a = 10;
console.log(a);
// 打印多个内容,用逗号隔开
var name = "张三";
var age = 18;
console.log("姓名:", name, "年龄:", age);
打开控制台,你会看到这些内容被打印出来了。
例子 2:打印对象和数组
console.log() 可以打印对象和数组的详细内容 ------ 这是 alert() 做不到的!
// 打印对象
var person = {
name: "张三",
age: 18,
hobby: ["读书", "游泳"]
};
console.log(person);
// 打印数组
var arr = [1, 2, 3, 4, 5];
console.log(arr);
打开控制台,你会看到:
- 对象会显示它的所有属性和值;
- 数组会显示它的所有元素,你可以点击展开查看。
例子 3:用 console.log () 调试代码
console.log() 最常用的场景是调试代码 ------ 比如你写了一段代码,发现结果不对,你可以在关键位置加 console.log(),打印变量的值,看看哪里出了问题。
举个例子:计算 1 到 10 的和,结果不对,我们用 console.log() 调试。
var sum = 0;
for (var i = 1; i <= 10; i++) {
sum = sum + i;
// 打印每次循环的 i 和 sum,看看哪里错了
console.log("i =", i, "sum =", sum);
}
console.log("最终的 sum =", sum);
打开控制台,你会看到每次循环的 i 和 sum ------ 这样你就能清楚地看到代码的执行过程,找到问题所在。
VSCode 快捷输入
在 VSCode 里,你可以输入 log ,然后按 Tab 键,就会快速输入 console.log() ------ 这是一个非常实用的小技巧!
总结:console.log () 是程序员的 "好帮手"
console.log() 是调试 JavaScript 代码最常用的工具 ------ 它不会阻塞代码执行,能打印几乎所有类型的内容,能帮你快速找到代码的问题。新手一定要学会用 console.log()!
10.4 重要概念:什么是 "日志"?什么是 "对象" 和 "."?
在讲 console.log() 时,我们提到了两个重要概念:"日志" 和 "对象"、"." ------ 现在我们来详细解释一下。
什么是 "日志"?
"日志"(Log)就是 "记录"------ 就像你写日记,记录每天发生的事;程序员写代码,用 console.log() 记录代码的执行过程、变量的值、有没有出错等。
我们可以用一个生活中的例子类比:医生看病。
- 医生给病人看病,会让病人做各种检查:血常规、尿常规、B 超、CT 等;
- 这些检查结果,就是医生的 "日志"------ 医生通过这些日志,诊断病人的病情;
- 程序员调试代码,就像医生看病 ------
console.log()打印的日志,就是程序员的 "检查结果",程序员通过这些日志,找到代码的 "病情"(Bug)。
还有一个小段子:"相比于医生来说,程序员多一个终极大招,'重启下试试'"------ 虽然是段子,但也说明了日志的重要性:重启之前,先看日志,找到问题所在!
什么是 "对象" 和 "."?
在讲 console.log() 时,我们说:console 是一个 JS 中的 "对象","." 表示取对象中的某个属性或者方法,可以直观理解成 "的"。
现在我们来简单解释一下(后面讲对象时会详细讲,现在只要有个大概印象就行):
**什么是 "对象"?**对象就是 "一组属性和方法的集合"------ 就像一个 "人",有 "名字"、"年龄"、"爱好" 等属性,有 "吃饭"、"睡觉"、"走路" 等方法。
在 JavaScript 里,console 就是一个 "控制台对象"------ 它有很多方法,比如:
log():打印日志;warn():打印警告(黄色文字);error():打印错误(红色文字);info():打印信息(蓝色文字)。
什么是 "."?"." 是 "成员访问运算符"------ 简单说就是 "的" 的意思,用来取对象的属性或方法。
比如:
console.log:就是 "控制台对象的 log 方法";person.name:就是 "person 对象的 name 属性";arr.length:就是 "arr 数组的 length 属性"(数组也是一种对象)。
现在再看 console.log("这是一条日志"),你应该完全理解了:
- 我们在使用 "控制台对象"(
console); - 调用它的 "log 方法"(
log); - 给这个方法传了一个参数:"这是一条日志";
- 这个方法的作用是:在控制台里打印这个参数。
十一、实战练习:用输入输出做几个小功能
现在我们学了这么多,来做几个实战练习,巩固一下!
练习 1:输入姓名,弹出欢迎语
要求:
- 用
prompt()输入用户的姓名; - 用
alert()弹出 "欢迎你,XXX!"; - 处理点击取消和输入为空的情况。
参考代码:
var name = prompt("请输入您的姓名");
if (name === null) {
alert("您取消了输入!");
} else if (name === "") {
alert("请输入有效的姓名!");
} else {
alert("欢迎你," + name + "!");
}
练习 2:输入两个数字,计算它们的和、差、积、商
要求:
- 用
prompt()输入两个数字; - 把输入的字符串转换成数字;
- 计算它们的和、差、积、商;
- 用
alert()弹出结果; - 用
console.log()打印结果(方便调试)。
参考代码:
javascript
// 输入第一个数字
var num1Str = prompt("请输入第一个数字");
if (num1Str === null) {
alert("您取消了输入!");
} else {
var num1 = Number(num1Str);
// 检查是否是有效数字
if (isNaN(num1)) {
alert("请输入有效的数字!");
} else {
// 输入第二个数字
var num2Str = prompt("请输入第二个数字");
if (num2Str === null) {
alert("您取消了输入!");
} else {
var num2 = Number(num2Str);
if (isNaN(num2)) {
alert("请输入有效的数字!");
} else {
// 计算
var sum = num1 + num2;
var diff = num1 - num2;
var product = num1 * num2;
var quotient = num1 / num2;
// 拼接结果
var result = "计算结果:\n" +
"和:" + sum + "\n" +
"差:" + diff + "\n" +
"积:" + product + "\n" +
"商:" + quotient;
// 弹出结果
alert(result);
// 打印到控制台
console.log(result);
}
}
}
}
说明:
isNaN()函数用来检查一个值是不是 "非数字"(Not a Number)------ 如果是,返回true;如果不是,返回false;\n是 "换行符"------ 用来在字符串里换行,让结果更清晰。
练习 3:输入圆的半径,计算圆的周长和面积
要求:
- 用
prompt()输入圆的半径; - 转换成数字;
- 计算周长(公式:
2 * π * r)和面积(公式:π * r * r); - 用
alert()弹出结果,保留两位小数; - 用
console.log()打印结果。
参考代码:
var radiusStr = prompt("请输入圆的半径");
if (radiusStr === null) {
alert("您取消了输入!");
} else {
var radius = Number(radiusStr);
if (isNaN(radius) || radius <= 0) {
alert("请输入有效的正数半径!");
} else {
// 计算周长和面积
var pi = 3.1415926;
var circumference = 2 * pi * radius;
var area = pi * radius * radius;
// 保留两位小数:toFixed(2)
var result = "圆的计算结果:\n" +
"半径:" + radius + "\n" +
"周长:" + circumference.toFixed(2) + "\n" +
"面积:" + area.toFixed(2);
alert(result);
console.log(result);
}
}
说明:
toFixed(n)方法用来把数字保留n位小数 ------ 比如3.1415926.toFixed(2)就是"3.14"(注意:返回的是字符串!)。
结语:
恭喜你!终于看完了这篇上万字的 JavaScript 入门博客!现在我们来总结一下,这篇博客我们学了什么:
核心内容回顾
-
JavaScript 是什么:
- 脚本语言,边翻译边执行;
- 最初在浏览器(客户端)运行,现在能在服务器(Node.js)运行;
- 世界上最流行的编程语言之一,能做网页、游戏、桌面程序、手机 App、服务器开发等。
-
JavaScript 的诞生历史:
- 1995 年,Brendan Eich 用 10 天设计出来;
- 借鉴了 C 语言(语法)、Java(数据类型、内存管理)、Scheme(函数是第一等公民)、Self(基于原型的继承);
- 最初叫 LiveScript,后来借 Java 的名气改名为 JavaScript。
-
JavaScript 和 HTML、CSS 的关系:
- HTML:骨(结构);
- CSS:皮(表现);
- JavaScript:魂(行为)。
-
JavaScript 的运行过程:
- 代码保存在硬盘 → 浏览器读取加载到内存 → 渲染引擎解析 HTML/CSS 绘制网页 → JS 引擎解析执行 JS → CPU 执行二进制指令 → 完成操作。
-
JavaScript 的组成:
- ECMAScript:语法标准;
- DOM:操作网页元素;
- BOM:操作浏览器窗口。
-
JavaScript 的书写形式:
- 行内式:写在 HTML 元素属性里(不推荐);
- 内嵌式:写在 script 标签里(推荐放在 body 最后);
- 外部式:写在单独的 .js 文件里(最推荐)。
-
JavaScript 的注释:
- 单行注释:
//(常用); - 多行注释:
/* */(不能嵌套); - 注释是代码的 "说明书",要多写。
- 单行注释:
-
JavaScript 的输入输出:
- 输入:
prompt()------ 弹出输入框,返回字符串; - 输出:
alert()------ 弹出警示框,会阻塞; - 输出:
console.log()------ 控制台打印日志,调试神器。
- 输入:
JavaScript 是一门非常有趣、非常强大的语言 ------ 它入门简单,但深入学习需要花很多时间和精力。希望这篇博客能帮你打下一个坚实的基础,让你对 JavaScript 有一个全面的了解。
学习编程的最好方法是 "多写代码、多调试、多思考"------ 不要只看,要动手写!遇到问题不要怕,用 console.log() 调试,查资料,问别人 ------ 每个程序员都是这么过来的。
最后,祝你在 JavaScript 的学习道路上越走越远,早日成为一名优秀的前端(或全栈)开发者!
好了,这篇万字长文终于写完了!如果你觉得这篇博客对你有帮助,欢迎点赞、收藏、分享给你的朋友。如果你有任何问题,或者想让我写更多 JavaScript 的内容,欢迎在评论区留言!我们下篇文章再见!