目录

第一章 JavaScript基础

1.1 JavaScript简介

1.1.1 JavaScript的历史

JavaScript诞生于1995年,由网景公司(Netscape)的Brendan Eich在仅仅10天内设计完成。最初命名为LiveScript,后来为了借助Java的流行改名为JavaScript。尽管名字中有“Java”,但两者在语言设计上有本质区别。

发展历程

1.1.2 JavaScript的特点

JavaScript是一门什么样的语言?

1.1.3 JavaScript的应用场景

前端开发

后端开发

其他领域

1.2 JavaScript运行环境

1.2.1 浏览器环境

浏览器是JavaScript最传统的运行环境。每个浏览器都内置了JavaScript引擎:

浏览器中的JavaScript能力

Hello World示例

<!DOCTYPE html>
<html>
<head>
    <title>第一个JavaScript程序</title>
</head>
<body>
    <h1>Hello JavaScript</h1>
 
    <!-- 内联JavaScript -->
    <script>
        // 在控制台输出
        console.log("Hello, World!");
 
        // 在页面显示警告框
        alert("欢迎学习JavaScript!");
 
        // 修改页面内容
        document.querySelector('h1').textContent = "Hello, JavaScript!";
    </script>
</body>
</html>

1.2.2 Node.js环境

Node.js是2009年诞生的JavaScript运行时,让JavaScript可以在服务器端运行。

Node.js的特点

安装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(推荐):

推荐插件:

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 = [];        // ✗ 不能重新赋值整个数组

命名约定

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种数据类型,分为两类:

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):

其他所有值都被视为真值(truthy),包括:

// 真值假值示例
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答案

练习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的前提。下一章将学习运算符和表达式,为编写复杂逻辑打下基础。

延伸阅读