====== 第一章 JavaScript基础 ======
===== 1.1 JavaScript简介 =====
==== 1.1.1 JavaScript的历史 ====
JavaScript诞生于1995年,由网景公司(Netscape)的Brendan Eich在仅仅10天内设计完成。最初命名为LiveScript,后来为了借助Java的流行改名为JavaScript。尽管名字中有"Java",但两者在语言设计上有本质区别。
**发展历程**:
* **1995年**:JavaScript 1.0发布,用于Netscape Navigator 2.0
* **1996年**:微软发布JScript,作为IE 3.0的脚本语言
* **1997年**:ECMAScript 1.0标准发布,统一了JavaScript规范
* **1999年**:ECMAScript 3发布,奠定了现代JavaScript的基础
* **2009年**:ECMAScript 5发布,引入了严格模式、JSON支持等
* **2015年**:ECMAScript 6(ES2015)发布,是JavaScript历史上最大的更新
* **2016年至今**:每年发布一个新版本(ES2016、ES2017...ES2023)
==== 1.1.2 JavaScript的特点 ====
**JavaScript是一门什么样的语言?**
* **解释型语言**:代码在运行时逐行解释执行
* **动态类型**:变量类型在运行时确定,可以随时改变
* **弱类型**:不同类型之间可以自动转换
* **基于原型**:面向对象通过原型实现,而非类(ES6引入class语法糖)
* **一等函数**:函数可以作为参数、返回值,赋值给变量
* **事件驱动**:通过事件监听实现异步编程
* **跨平台**:可以在浏览器、服务器、移动端等各种环境运行
==== 1.1.3 JavaScript的应用场景 ====
**前端开发**:
* 网页交互效果(动画、表单验证、动态内容)
* 单页应用(SPA)开发(React、Vue、Angular)
* 移动端Web应用(PWA)
**后端开发**:
* Node.js服务器开发
* RESTful API设计
* 微服务架构
**其他领域**:
* 桌面应用(Electron)
* 移动应用(React Native、Flutter)
* 游戏开发(Phaser、Three.js)
* 物联网(IoT)
* 机器学习(TensorFlow.js)
===== 1.2 JavaScript运行环境 =====
==== 1.2.1 浏览器环境 ====
浏览器是JavaScript最传统的运行环境。每个浏览器都内置了JavaScript引擎:
* **V8**:Chrome、Edge浏览器(也是Node.js的引擎)
* **SpiderMonkey**:Firefox浏览器
* **JavaScriptCore**:Safari浏览器(也称为Nitro)
**浏览器中的JavaScript能力**:
* 操作DOM(文档对象模型)
* 响应用户事件(点击、键盘输入等)
* 发送HTTP请求(Ajax、Fetch)
* 操作浏览器存储(localStorage、sessionStorage)
* 访问地理位置、摄像头等设备API
**Hello World示例**:
第一个JavaScript程序
Hello JavaScript
==== 1.2.2 Node.js环境 ====
Node.js是2009年诞生的JavaScript运行时,让JavaScript可以在服务器端运行。
**Node.js的特点**:
* 基于Chrome V8引擎
* 事件驱动、非阻塞I/O模型
* 庞大的npm生态系统(超过200万个包)
* 适合构建高并发、I/O密集型的应用
**安装Node.js**:
在Linux/macOS上:
# 使用nvm安装(推荐)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 20
nvm use 20
# 验证安装
node --version
npm --version
**第一个Node.js程序**:
创建文件 `hello.js`:
// hello.js
console.log("Hello from Node.js!");
// 获取命令行参数
const args = process.argv.slice(2);
if (args.length > 0) {
console.log(`你输入的参数是: ${args.join(', ')}`);
}
// 使用Node.js内置模块
const os = require('os');
console.log(`操作系统: ${os.platform()}`);
console.log(`CPU架构: ${os.arch()}`);
运行:
node hello.js arg1 arg2
==== 1.2.3 代码编辑器推荐 ====
**Visual Studio Code**(推荐):
* 免费开源
* 丰富的插件生态
* 内置Git支持
* 智能代码补全
* 强大的调试功能
推荐插件:
* ESLint - 代码检查
* Prettier - 代码格式化
* JavaScript (ES6) code snippets - 代码片段
* Live Server - 本地开发服务器
===== 1.3 基本语法 =====
==== 1.3.1 语句和表达式 ====
**语句(Statement)**:执行操作的完整指令,以分号结束。
let x = 5; // 变量声明语句
console.log(x); // 函数调用语句
if (x > 0) { // 条件语句
x = x + 1;
}
**表达式(Expression)**:产生一个值的代码片段。
5 + 3 // 算术表达式,值为8
x > 0 // 比较表达式,值为true
"Hello" + "World" // 字符串表达式,值为"HelloWorld"
**表达式语句**:表达式后面加上分号就变成了语句。
// 表达式
5 + 3
// 表达式语句
5 + 3; // 计算结果但不使用,没有实际意义
// 有意义的表达式语句
let sum = 5 + 3; // 赋值表达式的结果赋值给变量
==== 1.3.2 注释 ====
注释是代码中不会被执行的部分,用于解释代码。
// 单行注释
/*
* 多行注释
* 可以跨越多行
* 常用于函数说明
*/
/**
* JSDoc风格的文档注释
* @param {number} a - 第一个数字
* @param {number} b - 第二个数字
* @returns {number} 两数之和
*/
function add(a, b) {
return a + b;
}
==== 1.3.3 严格模式 ====
严格模式(Strict Mode)是ES5引入的一种限制性更强的JavaScript运行模式。
**启用严格模式**:
// 为整个脚本启用
'use strict';
// 为单个函数启用
function strictFunction() {
'use strict';
// 函数体内的代码在严格模式下运行
}
**严格模式的限制**:
* 不允许使用未声明的变量
* 不允许删除变量或函数
* 不允许重复参数名
* 不允许使用八进制字面量
* 不允许修改只读属性
* 保留字不能用作变量名
'use strict';
// 错误:未声明变量
x = 10; // ReferenceError: x is not defined
// 正确:先声明
let x = 10;
===== 1.4 变量声明 =====
==== 1.4.1 var声明(不推荐) ====
var name = "张三";
var age = 25;
**var的问题**:
* 函数作用域而非块级作用域
* 存在变量提升
* 允许重复声明
// 变量提升 - 声明被提升到作用域顶部
console.log(x); // undefined(不会报错)
var x = 5;
// 实际等价于
var x;
console.log(x); // undefined
x = 5;
// 没有块级作用域
if (true) {
var message = "Hello";
}
console.log(message); // "Hello" - 在if外部仍可访问
==== 1.4.2 let声明(推荐) ====
let name = "李四";
let age = 30;
**let的特点**:
* 块级作用域
* 不存在变量提升(暂时性死区)
* 不允许重复声明
// 块级作用域
if (true) {
let message = "Hello";
}
// console.log(message); // ReferenceError: message is not defined
// 暂时性死区
// console.log(y); // ReferenceError: Cannot access 'y' before initialization
let y = 10;
// 不允许重复声明
let z = 1;
// let z = 2; // SyntaxError: Identifier 'z' has already been declared
==== 1.4.3 const声明(常量) ====
const PI = 3.14159;
const API_URL = "https://api.example.com";
**const的特点**:
* 必须立即初始化
* 不能重新赋值
* 块级作用域
// 必须初始化
// const X; // SyntaxError: Missing initializer in const declaration
// 不能重新赋值
const MAX_SIZE = 100;
// MAX_SIZE = 200; // TypeError: Assignment to constant variable
// 对象和数组的特殊情况
const user = { name: "Alice" };
user.name = "Bob"; // ✓ 可以修改对象属性
user.age = 25; // ✓ 可以添加属性
// user = {}; // ✗ 不能重新赋值整个对象
const numbers = [1, 2, 3];
numbers.push(4); // ✓ 可以修改数组
// numbers = []; // ✗ 不能重新赋值整个数组
**命名约定**:
* 普通变量:camelCase(userName、totalCount)
* 常量:UPPER_SNAKE_CASE(MAX_VALUE、API_KEY)
* 类名:PascalCase(UserAccount、DataManager)
==== 1.4.4 变量声明最佳实践 ====
// 1. 默认使用 const
const API_BASE = "https://api.example.com";
const MAX_RETRIES = 3;
// 2. 需要重新赋值时使用 let
let currentUser = null;
let retryCount = 0;
// 3. 避免使用 var
// var data = []; // 不推荐
// 4. 一条语句声明一个变量
const firstName = "John";
const lastName = "Doe";
// 不推荐:const firstName = "John", lastName = "Doe";
// 5. 在作用域顶部声明变量
function processData() {
const config = getConfig();
let result = [];
// 使用变量...
}
===== 1.5 数据类型 =====
JavaScript有 **8种数据类型**,分为两类:
* **基本类型**(Primitive):7种,值直接存储在变量中
* **引用类型**(Reference):1种,变量存储的是指向内存地址的引用
==== 1.5.1 Number(数字) ====
JavaScript只有一种数字类型:双精度64位浮点数(IEEE 754标准)。
// 整数
let integer = 42;
let negative = -17;
// 浮点数
let float = 3.14159;
let scientific = 2.5e10; // 25000000000
// 特殊数值
let infinity = Infinity; // 正无穷
let negInfinity = -Infinity; // 负无穷
let notANumber = NaN; // 非数字
// 进制表示
let binary = 0b1010; // 10(二进制)
let octal = 0o17; // 15(八进制)
let hex = 0xFF; // 255(十六进制)
**Number的常用方法**:
let num = 123.456;
// 转字符串
num.toString(); // "123.456"
num.toString(16); // "7b.74bc6a7ef9db"(十六进制)
// 格式化
num.toFixed(2); // "123.46"(保留2位小数)
num.toPrecision(4); // "123.5"(总共4位有效数字)
// 其他方法
Number.isInteger(42); // true
Number.isFinite(1/0); // false
Number.isNaN(NaN); // true
// 全局函数(与Number方法略有不同)
parseInt("123px"); // 123
parseFloat("3.14abc"); // 3.14
isNaN("hello"); // true(尝试转换为数字后判断)
**Number的精度问题**:
// 浮点数精度问题
0.1 + 0.2 === 0.3; // false!
0.1 + 0.2; // 0.30000000000000004
// 解决方案
Math.abs(0.1 + 0.2 - 0.3) < Number.EPSILON; // true
// 或使用toFixed后转数字
parseFloat((0.1 + 0.2).toFixed(10)); // 0.3
// 大整数使用BigInt
const bigNumber = 9007199254740991n; // BigInt类型
==== 1.5.2 BigInt(大整数) ====
ES2020引入,用于表示任意精度的整数。
// 创建BigInt
const big1 = 9007199254740993n;
const big2 = BigInt("9007199254740993");
const big3 = BigInt(9007199254740993);
// Number.MAX_SAFE_INTEGER = 9007199254740991
Number.MAX_SAFE_INTEGER + 2 === Number.MAX_SAFE_INTEGER + 3; // true(精度丢失)
// BigInt可以精确表示
9007199254740991n + 2n === 9007199254740991n + 3n; // false
// 运算
const sum = 100n + 200n; // 300n
const product = 10n * 20n; // 200n
const division = 100n / 3n; // 33n(整数除法)
// 不能混合运算
// 100n + 50; // TypeError
100n + BigInt(50); // 150n
==== 1.5.3 String(字符串) ====
字符串是Unicode字符序列,可以用单引号、双引号或反引号定义。
// 字符串定义
let str1 = '单引号字符串';
let str2 = "双引号字符串";
let str3 = `模板字符串`;
// 字符串长度
"Hello".length; // 5
"你好".length; // 2
// 字符串索引
"Hello"[0]; // "H"
"Hello".charAt(1); // "e"
// 连接字符串
let firstName = "John";
let lastName = "Doe";
let fullName = firstName + " " + lastName;
// 模板字符串(ES6)
let age = 25;
let greeting = `你好,${fullName}!你今年${age}岁。`;
// 多行字符串(模板字符串)
let poem = `
春眠不觉晓,
处处闻啼鸟。
夜来风雨声,
花落知多少。
`;
// 转义字符
let quote = "他说:\"Hello\"";
let newline = "第一行\n第二行";
let tab = "列1\t列2\t列3";
**字符串常用方法**:
let text = "JavaScript Programming";
// 查找
text.indexOf("Script"); // 4
text.lastIndexOf("m"); // 19
text.includes("Java"); // true
text.startsWith("Java"); // true
text.endsWith("ing"); // true
text.search(/Pro/); // 11(正则匹配)
// 提取
text.slice(0, 4); // "Java"
text.substring(0, 4); // "Java"
text.substr(11, 7); // "Program"(已废弃)
text.charAt(0); // "J"
// 修改(返回新字符串,原字符串不变)
text.toLowerCase(); // "javascript programming"
text.toUpperCase(); // "JAVASCRIPT PROGRAMMING"
text.replace("Java", "Type"); // "TypeScript Programming"
text.replaceAll("m", "M"); // "JavaScript PrograMMing"
// 分割和连接
text.split(" "); // ["JavaScript", "Programming"]
["Hello", "World"].join(" "); // "Hello World"
// 去除空白
" hello ".trim(); // "hello"
" hello ".trimStart(); // "hello "
" hello ".trimEnd(); // " hello"
// 重复和填充
"ha".repeat(3); // "hahaha"
"5".padStart(3, "0"); // "005"
"5".padEnd(3, "0"); // "500"
==== 1.5.4 Boolean(布尔) ====
布尔类型只有两个值:true 和 false。
let isActive = true;
let isDeleted = false;
// 比较产生布尔值
5 > 3; // true
10 === "10"; // false
// 逻辑运算
true && false; // false(与)
true || false; // true(或)
!true; // false(非)
**真值和假值**:
在布尔上下文中,以下值被视为假值(falsy):
* false
* 0、-0、0n(BigInt零)
* ""、''、``(空字符串)
* null
* undefined
* NaN
其他所有值都被视为真值(truthy),包括:
* true
* 任何非零数字
* 任何非空字符串
* 任何对象(包括空对象{}、空数组[])
// 真值假值示例
if (0) console.log("不会执行");
if ("") console.log("不会执行");
if (null) console.log("不会执行");
if (undefined) console.log("不会执行");
if (NaN) console.log("不会执行");
if (1) console.log("会执行");
if ("hello") console.log("会执行");
if ({}) console.log("会执行");
if ([]) console.log("会执行");
==== 1.5.5 Undefined(未定义) ====
undefined 表示变量已声明但尚未赋值。
let x;
console.log(x); // undefined
// 函数无返回值时
function doNothing() {}
console.log(doNothing()); // undefined
// 访问不存在的属性
let obj = {};
console.log(obj.nonExistent); // undefined
// 显式赋值(不推荐)
let y = undefined; // 可以但没必要
==== 1.5.6 Null(空值) ====
null 表示一个"空"值,通常用于表示"无"或"不存在"。
let user = null; // 明确表示没有用户
// null的使用场景
function findUser(id) {
// 如果找不到用户,返回null
if (id < 0) return null;
return { id, name: "User" };
}
// typeof null 的bug
typeof null; // "object"(历史遗留bug)
typeof undefined; // "undefined"
**undefined vs null**:
undefined == null; // true(宽松相等)
undefined === null; // false(严格不相等)
// undefined:系统级别的"缺少值"
// null:程序级别的"无值"
// 最佳实践:使用null表示"无",undefined留给系统
==== 1.5.7 Symbol(符号) ====
ES6引入的新类型,表示唯一的标识符。
// 创建Symbol
const sym1 = Symbol();
const sym2 = Symbol("description");
const sym3 = Symbol("description");
// 每个Symbol都是唯一的
sym2 === sym3; // false
// 用作对象属性键
const id = Symbol("id");
const user = {
name: "Alice",
[id]: 12345 // 符号属性
};
console.log(user[id]); // 12345
console.log(Object.keys(user)); // ["name"](符号属性不可枚举)
// 全局Symbol注册表
const globalSym = Symbol.for("app.id");
const sameSym = Symbol.for("app.id");
globalSym === sameSym; // true
Symbol.keyFor(globalSym); // "app.id"
==== 1.5.8 Object(对象) ====
对象是键值对的集合,是JavaScript中最复杂也是最重要的数据类型。
// 对象字面量
const person = {
name: "张三",
age: 30,
isStudent: false,
hobbies: ["读书", "游泳"],
address: {
city: "北京",
zipCode: "100000"
},
greet: function() {
return `你好,我是${this.name}`;
},
// ES6简写方法
sayBye() {
return "再见!";
}
};
// 访问属性
person.name; // "张三"
person["age"]; // 30
// 添加/修改属性
person.email = "zhangsan@example.com";
person["phone"] = "13800138000";
// 删除属性
delete person.isStudent;
// 检查属性
"name" in person; // true
person.hasOwnProperty("age"); // true
// 遍历对象
Object.keys(person); // 所有可枚举属性名
Object.values(person); // 所有可枚举属性值
Object.entries(person); // [key, value]数组
**对象的引用特性**:
// 基本类型 - 值复制
let a = 5;
let b = a;
b = 10;
console.log(a); // 5(a不变)
// 对象 - 引用复制
let obj1 = { x: 1 };
let obj2 = obj1;
obj2.x = 2;
console.log(obj1.x); // 2(obj1也变了!)
// 真正复制对象
let obj3 = { ...obj1 }; // 浅拷贝(ES6)
let obj4 = Object.assign({}, obj1); // 浅拷贝
let obj5 = JSON.parse(JSON.stringify(obj1)); // 深拷贝(有限制)
===== 1.6 类型转换 =====
==== 1.6.1 隐式类型转换 ====
JavaScript在运算时会自动进行类型转换。
// 字符串拼接
"5" + 3; // "53"(数字转字符串)
"5" + true; // "5true"(布尔转字符串)
// 算术运算(非加号)
"5" - 3; // 2(字符串转数字)
"5" * 2; // 10
"10" / "2"; // 5
// 比较运算
"5" == 5; // true(字符串转数字)
0 == false; // true
"" == false; // true
null == undefined; // true
// 逻辑运算
!!"hello"; // true(转布尔)
!!""; // false
!!0; // false
!!{}; // true
==== 1.6.2 显式类型转换 ====
// 转字符串
String(123); // "123"
(123).toString(); // "123"
123 + ""; // "123"
// 转数字
Number("123"); // 123
Number("12.34"); // 12.34
Number(""); // 0
Number(" "); // 0
Number("123abc"); // NaN
Number(true); // 1
Number(false); // 0
Number(null); // 0
Number(undefined); // NaN
// 更灵活的转数字
parseInt("123px"); // 123
parseInt("101", 2); // 5(二进制转十进制)
parseFloat("12.34.56"); // 12.34
parseInt("FF", 16); // 255
// 转布尔
Boolean(1); // true
Boolean(0); // false
Boolean(""); // false
Boolean("hello"); // true
Boolean(null); // false
Boolean(undefined); // false
Boolean({}); // true
// 一元加号转数字
+"123"; // 123
+true; // 1
+false; // 0
===== 1.7 练习题 =====
==== 练习1:变量声明 ====
判断以下代码的正误,错误的说明原因:
// 1
var x = 1;
var x = 2;
// 2
let y = 1;
let y = 2;
// 3
const z;
z = 3;
// 4
const user = { name: "Tom" };
user.name = "Jerry";
// 5
const arr = [1, 2];
arr.push(3);
==== 练习2:数据类型判断 ====
写出以下表达式的结果:
typeof undefined;
typeof null;
typeof {};
typeof [];
typeof function() {};
typeof NaN;
typeof 123n;
typeof Symbol();
Array.isArray([]);
[] instanceof Array;
==== 练习3:类型转换 ====
预测以下表达式的结果:
"42" + 8;
"42" - 8;
"42" * "2";
"42" == 42;
"42" === 42;
null == undefined;
null === undefined;
!!"false";
!!"";
Number("12px");
parseInt("12px");
==== 练习4:编程题 ====
1. 编写一个程序,声明一个名为 `userInfo` 的常量对象,包含 `name`、`age`、`email` 属性,然后尝试修改 `age` 属性,观察结果。
2. 创建一个函数 `convertToNumber`,接受一个参数,尝试将其转换为数字,如果转换失败返回 `null` 而不是 `NaN`。
3. 编写代码验证浮点数精度问题,并提供解决方案。
===== 参考答案 =====
**练习1答案**:
* 1: 正确,var允许重复声明
* 2: 错误,let不允许重复声明,会抛出SyntaxError
* 3: 错误,const必须初始化
* 4: 正确,const对象的属性可以修改
* 5: 正确,const数组的内容可以修改
**练习2答案**:
typeof undefined; // "undefined"
typeof null; // "object"(bug)
typeof {}; // "object"
typeof []; // "object"
typeof function() {}; // "function"
typeof NaN; // "number"
typeof 123n; // "bigint"
typeof Symbol(); // "symbol"
Array.isArray([]); // true
[] instanceof Array; // true
**练习3答案**:
"42" + 8; // "428"
"42" - 8; // 34
"42" * "2"; // 84
"42" == 42; // true
"42" === 42; // false
null == undefined; // true
null === undefined; // false
!!"false"; // true(非空字符串)
!!""; // false
Number("12px"); // NaN
parseInt("12px"); // 12
**练习4参考答案**:
// 第1题
const userInfo = {
name: "张三",
age: 25,
email: "zhangsan@example.com"
};
userInfo.age = 26; // ✓ 成功
console.log(userInfo.age); // 26
// userInfo = {}; // ✗ 错误!不能重新赋值
// 第2题
function convertToNumber(value) {
const num = Number(value);
return isNaN(num) ? null : num;
}
console.log(convertToNumber("123")); // 123
console.log(convertToNumber("abc")); // null
console.log(convertToNumber("12.34")); // 12.34
// 第3题
function safeAdd(a, b) {
return Math.round((a + b) * 1e10) / 1e10;
}
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(safeAdd(0.1, 0.2)); // 0.3
===== 本章小结 =====
本章介绍了JavaScript的基础知识:
* JavaScript的历史和特点
* 浏览器和Node.js运行环境
* 基本语法规则(语句、注释、严格模式)
* 三种变量声明方式(var、let、const)及其区别
* 8种数据类型及其特点和用法
* 类型转换(隐式和显式)
掌握这些基础知识是深入学习JavaScript的前提。下一章将学习运算符和表达式,为编写复杂逻辑打下基础。
===== 延伸阅读 =====
* [[https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Grammar_and_types|MDN - 语法和数据类型]]
* [[https://github.com/getify/You-Dont-Know-JS|你不知道的JavaScript]](书籍)
* [[https://javascript.info/types|JavaScript.info - 数据类型]]