跳到主要内容

核心知识点

1. JavaScript 简介

1.1 什么是 JavaScript?

JavaScript (JS) 是一种轻量级的、解释型的、具有函数优先(first-class functions)的编程语言。它主要作为 Web 页面的脚本语言而闻名,但也被用于许多非浏览器环境,如 Node.js、Apache CouchDB 和 Adobe Acrobat。JavaScript 是一种基于原型、多范式的动态脚本语言,支持面向对象、命令式和声明式(如函数式编程)风格。

1.2 JavaScript 的特点

  • 解释型语言: 代码在运行时逐行解释执行,通常不需要预先编译。
  • 动态类型: 变量的类型在运行时确定,并且可以改变。
  • 基于原型: 对象继承是通过原型链实现的,而不是类(虽然 ES6 引入了 class 语法糖)。
  • 函数是一等公民: 函数可以像其他值一样被传递、赋值给变量、作为参数或返回值。
  • 事件驱动和非阻塞 I/O: 特别是在浏览器和 Node.js 环境中,常用于处理用户交互和网络请求等异步操作。

1.3 JavaScript 的用途

  • 网页交互: DOM 操作、事件处理、动画效果。
  • Web 应用开发: 构建复杂的前端应用(配合 React, Vue, Angular 等框架)。
  • 后端开发: 使用 Node.js 构建服务器、API。
  • 移动应用开发: React Native, NativeScript 等。
  • 桌面应用开发: Electron, NW.js 等。
  • 游戏开发: Phaser, Babylon.js 等。

2. 基础语法与数据类型

2.1 变量声明 (var, let, const)

  • var: 函数作用域或全局作用域,存在变量提升,可以重复声明。
  • let: 块级作用域,不存在变量提升(暂时性死区),不能重复声明。
  • const: 块级作用域,声明时必须初始化,且不能重新赋值(但对象或数组的内容可变)。
// var (avoid in modern JS)
function varTest() {
console.log(a); // undefined (hoisting)
if (true) {
var a = 10;
}
console.log(a); // 10 (no block scope)
var a = 20; // Redeclaration allowed
console.log(a); // 20
}
varTest();
// console.log(a); // ReferenceError: a is not defined (function scoped)

// let
function letTest() {
// console.log(b); // ReferenceError: Cannot access 'b' before initialization (TDZ)
let b = 10;
if (true) {
let b = 20; // Different variable in block scope
console.log("Inner b:", b); // Inner b: 20
}
console.log("Outer b:", b); // Outer b: 10
// let b = 30; // SyntaxError: Identifier 'b' has already been declared
}
letTest();

// const
const PI = 3.14159;
// PI = 3.14; // TypeError: Assignment to constant variable.

const person = { name: "Alice" };
person.name = "Bob"; // OK: Modifying object property
// person = { name: "Charlie" }; // TypeError: Assignment to constant variable. (Cannot reassign the reference)
console.log(person); // { name: 'Bob' }

const arr = [1, 2, 3];
arr.push(4); // OK: Modifying array content
// arr = [5, 6]; // TypeError: Assignment to constant variable.
console.log(arr); // [ 1, 2, 3, 4 ]

2.2 数据类型 (Data Types)

JavaScript 有两类数据类型:原始类型 (Primitive Types) 和对象类型 (Object Type)。

原始类型 (Primitives):

  • string: 文本数据。
  • number: 数字,包括整数和浮点数 (以及 NaN, Infinity)。
  • boolean: truefalse
  • null: 表示有意设置的空值或“无对象”。
  • undefined: 表示变量已声明但未赋值,或对象属性不存在。
  • symbol (ES6): 唯一的、不可变的值,常用作对象属性的键。
  • bigint (ES2020): 表示任意精度的整数。
let str = "Hello";
let num = 123.45;
let isTrue = true;
let nothing = null; // Type is 'object' (historical quirk)
let notAssigned; // Value and Type are 'undefined'
let sym = Symbol("id");
let bigNum = 123456789012345678901234567890n; // Note the 'n' suffix

console.log(typeof str); // "string"
console.log(typeof num); // "number"
console.log(typeof isTrue); // "boolean"
console.log(typeof nothing); // "object" (!!)
console.log(typeof notAssigned); // "undefined"
console.log(typeof sym); // "symbol"
console.log(typeof bigNum); // "bigint"

对象类型 (Object):

  • Object: 键值对的集合。
  • Array: 有序的元素列表。
  • Function: 可执行的代码块。
  • Date, RegExp, Error 等内置对象。
let obj = { key: "value", count: 1 };
let arrData = [1, "two", true, null];
function greet() {
console.log("Hi!");
}
let today = new Date();
let pattern = /ab+c/;

console.log(typeof obj); // "object"
console.log(typeof arrData); // "object" (Arrays are objects)
console.log(Array.isArray(arrData)); // true (Use Array.isArray to check specifically for arrays)
console.log(typeof greet); // "function" (Functions are special objects)
console.log(typeof today); // "object"
console.log(typeof pattern); // "object"

2.3 运算符 (Operators)

  • 算术运算符: +, -, *, /, % (取模), ** (幂/指数 ES7)。
  • 赋值运算符: =, +=, -=, *=, /=, %=, **=.
  • 比较运算符: == (宽松相等, 会进行类型转换), != (宽松不等), === (严格相等, 不进行类型转换), !== (严格不等), >, <, >=, <=.
  • 逻辑运算符: && (逻辑与), || (逻辑或), ! (逻辑非)。
  • 位运算符: &, |, ^, ~, <<, >>, >>>.
  • 三元运算符: condition ? exprIfTrue : exprIfFalse.
  • 类型运算符: typeof, instanceof.
  • 其他: delete (删除对象属性), new (创建实例), in (检查属性是否存在于对象或其原型链), , (逗号运算符)。
// Arithmetic
console.log(5 + 3); // 8
console.log(10 % 3); // 1
console.log(2 ** 4); // 16

// Assignment
let x = 5;
x += 3; // x is now 8

// Comparison (!!! Use strict equality === most of the time !!!)
console.log("5" == 5); // true (loose equality, string "5" converted to number 5)
console.log("5" === 5); // false (strict equality, types are different)
console.log(null == undefined); // true (loose equality special case)
console.log(null === undefined); // false (strict equality)

// Logical
let isValid = true;
let hasPermission = false;
console.log(isValid && hasPermission); // false
console.log(isValid || hasPermission); // true
console.log(!isValid); // false

// Ternary
let age = 20;
let status = age >= 18 ? "adult" : "minor";
console.log(status); // "adult"

// typeof / instanceof
let message = "hello";
let numbers = [1, 2, 3];
console.log(typeof message); // "string"
console.log(numbers instanceof Array); // true
console.log(numbers instanceof Object); // true (Arrays are objects)

2.4 类型转换 (Type Conversion)

JavaScript 是动态类型语言,经常在运算中进行隐式类型转换。也可以显式转换。

  • 转字符串: String(), value.toString(), + "" (隐式)。
  • 转数字: Number(), parseInt(), parseFloat(), +value (隐式)。
  • 转布尔: Boolean(), !!value (隐式)。Falsy 值 (转为 false): false, 0, "" (空字符串), null, undefined, NaN。其他所有值都是 Truthy。
// To String
let numVal = 123;
let strVal = String(numVal); // "123"
let boolVal = true;
let strBool = boolVal.toString(); // "true"
let concatStr = numVal + ""; // "123" (Implicit)

console.log(typeof strVal, strVal); // string 123

// To Number
let strNum = "45.6";
let numFromStr = Number(strNum); // 45.6
let intStr = "78px";
let intNum = parseInt(intStr); // 78 (stops at non-digit)
let floatStr = "3.14em";
let floatNum = parseFloat(floatStr); // 3.14
let unaryPlus = +"99"; // 99 (Implicit using unary plus)
let invalidNum = Number("hello"); // NaN (Not a Number)

console.log(typeof numFromStr, numFromStr); // number 45.6
console.log(intNum); // 78
console.log(floatNum); // 3.14
console.log(unaryPlus); // 99
console.log(invalidNum, typeof invalidNum); // NaN number

// To Boolean
console.log(Boolean(0)); // false
console.log(Boolean("")); // false
console.log(Boolean(null)); // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN)); // false

console.log(Boolean(1)); // true
console.log(Boolean("hello")); // true
console.log(Boolean({})); // true (empty object is truthy)
console.log(Boolean([])); // true (empty array is truthy)

console.log(!!0); // false (Implicit using double negation)
console.log(!!"hi"); // true

3. 控制流 (Control Flow)

3.1 条件语句 (if, else if, else, switch)

  • if...else if...else: 根据条件执行不同的代码块。
  • switch: 基于一个表达式的值匹配多个 case
// if...else
let score = 75;
if (score >= 90) {
console.log("Grade A");
} else if (score >= 80) {
console.log("Grade B");
} else if (score >= 70) {
console.log("Grade C");
} else {
console.log("Grade D or F");
}
// Output: Grade C

// switch
let day = new Date().getDay(); // 0 (Sun) to 6 (Sat)
let dayName;
switch (day) {
case 0:
dayName = "Sunday";
break; // Important! Prevents fall-through
case 1:
dayName = "Monday";
break;
case 6:
dayName = "Saturday";
break;
default: // Optional: handles cases not explicitly listed
dayName = "Weekday";
}
console.log(`Today is ${dayName}`);

3.2 循环语句 (for, while, do...while, for...in, for...of)

  • for: 重复执行代码块固定次数。
  • while: 当条件为真时重复执行代码块。
  • do...while: 先执行一次代码块,然后当条件为真时重复。
  • for...in: 遍历对象的可枚举属性 (键)。(不推荐用于数组遍历)
  • for...of (ES6): 遍历可迭代对象 (如 Array, String, Map, Set) 的值。
// for loop
console.log("For loop:");
for (let i = 0; i < 3; i++) {
console.log(i); // 0, 1, 2
}

// while loop
console.log("While loop:");
let count = 0;
while (count < 3) {
console.log(count); // 0, 1, 2
count++;
}

// do...while loop
console.log("Do...while loop:");
let j = 0;
do {
console.log(j); // 0, 1, 2
j++;
} while (j < 3);

// for...in (for object properties)
console.log("For...in loop (object):");
const car = { make: "Toyota", model: "Camry", year: 2021 };
for (let key in car) {
// It's good practice to check if the property is directly on the object
if (Object.hasOwnProperty.call(car, key)) {
console.log(`${key}: ${car[key]}`); // make: Toyota, model: Camry, year: 2021
}
}

// for...of (for iterable values - arrays, strings etc.)
console.log("For...of loop (array):");
const colors = ["red", "green", "blue"];
for (const color of colors) {
console.log(color); // red, green, blue
}

console.log("For...of loop (string):");
const text = "abc";
for (const char of text) {
console.log(char); // a, b, c
}

3.3 breakcontinue

  • break: 立即退出整个循环或 switch 语句。
  • continue: 跳过当前循环的剩余部分,开始下一次迭代。
console.log("Break/Continue:");
for (let i = 0; i < 5; i++) {
if (i === 3) {
break; // Exit loop when i is 3
}
if (i === 1) {
continue; // Skip iteration when i is 1
}
console.log(i); // 0, 2
}

4. 函数 (Functions)

函数是执行特定任务的代码块。

4.1 函数声明 (Function Declaration) vs 函数表达式 (Function Expression)

  • 声明: 使用 function 关键字开头,会被提升 (hoisted)。
  • 表达式: 将匿名或命名函数赋值给变量,不会被提升(变量声明会提升,但赋值不会)。
// Function Declaration (Hoisted)
console.log(declaredFunc(5, 3)); // 8 (can call before declaration)

function declaredFunc(a, b) {
return a + b;
}

// Function Expression (Not hoisted in terms of assignment)
// console.log(expressedFunc(5, 3)); // TypeError: expressedFunc is not a function (or ReferenceError if using let/const due to TDZ)

const expressedFunc = function (a, b) {
// Anonymous function expression
return a * b;
};
console.log(expressedFunc(5, 3)); // 15

const namedExpressedFunc = function multiply(a, b) {
// Named function expression (name 'multiply' primarily useful for debugging/recursion)
return a * b;
};
console.log(namedExpressedFunc(5, 3)); // 15
// console.log(multiply(5,3)); // ReferenceError: multiply is not defined (name is local to the function scope)

4.2 箭头函数 (Arrow Functions - ES6)

提供更简洁的语法,并且不绑定自己的 this (词法 this)。

// Traditional function expression
const addTrad = function (a, b) {
return a + b;
};

// Arrow function equivalent
const addArrow = (a, b) => {
return a + b;
};

// Concise body (implicit return)
const subtract = (a, b) => a - b;

// Single parameter (parentheses optional)
const square = (x) => x * x;

// No parameters
const getRandom = () => Math.random();

console.log(addArrow(2, 3)); // 5
console.log(subtract(5, 2)); // 3
console.log(square(4)); // 16
console.log(getRandom()); // Random number

// 'this' behavior difference
function CounterTrad() {
this.count = 0;
// In traditional function, 'this' inside setInterval callback is Window/global or undefined (strict mode)
// Need to use .bind(this), self = this, or arrow function
/*
setInterval(function() {
this.count++; // 'this' is not the CounterTrad instance here
console.log("Trad (problematic):", this.count);
}, 1000);
*/
}

function CounterArrow() {
this.count = 0;
// Arrow function inherits 'this' from the surrounding lexical scope (CounterArrow)
setInterval(() => {
this.count++;
console.log("Arrow:", this.count); // 'this' correctly refers to the CounterArrow instance
}, 2000); // Use different interval to see output clearly
}

// const c1 = new CounterTrad(); // Would demonstrate the 'this' issue
const c2 = new CounterArrow(); // Works as expected

4.3 参数 (Parameters) 与 arguments 对象

  • 默认参数 (ES6): function greet(name = "Guest") {...}
  • 剩余参数 (Rest Parameters - ES6): function sum(...numbers) {...} - 将多余的参数收集到一个数组中。
  • arguments 对象: 类数组对象,包含函数调用时传递的所有参数 (在箭头函数中不可用)。尽量使用剩余参数代替。
// Default parameters
function greet(name = "Guest") {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Guest!
greet("Alice"); // Hello, Alice!

// Rest parameters
function sum(...numbers) {
let total = 0;
for (const num of numbers) {
total += num;
}
return total;
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(10, 20, 30, 40)); // 100

// arguments object (less common in modern JS)
function logArgs() {
console.log("Arguments object:", arguments);
console.log("First arg:", arguments[0]);
// return arguments.reduce(...); // Error: arguments.reduce is not a function (it's array-like, not a real array)
// Convert to array if needed: Array.from(arguments) or [...arguments]
}
logArgs("a", "b", "c"); // Arguments object: [Arguments] { '0': 'a', '1': 'b', '2': 'c' }
// First arg: a

4.4 作用域 (Scope) 与 闭包 (Closure)

  • 作用域: 变量和函数的可访问范围。
    • 全局作用域 (Global Scope)
    • 函数作用域 (Function Scope)
    • 块级作用域 (Block Scope - let, const)
  • 闭包: 一个函数能够“记住”并访问其词法作用域(创建该函数时的作用域),即使该函数在其词法作用域之外执行。
// Scope Example
let globalVar = "I'm global";

function outerFunc() {
let outerVar = "I'm outer";

function innerFunc() {
let innerVar = "I'm inner";
console.log(globalVar); // Access global
console.log(outerVar); // Access outer scope
console.log(innerVar); // Access local scope
}

innerFunc();
// console.log(innerVar); // ReferenceError: innerVar is not defined
}

outerFunc();
// console.log(outerVar); // ReferenceError: outerVar is not defined

// Closure Example
function makeCounter() {
let count = 0; // 'count' is captured by the inner function's closure

return function () {
// This inner function is the closure
count++;
console.log(count);
};
}

const counter1 = makeCounter();
const counter2 = makeCounter();

counter1(); // 1
counter1(); // 2 (count is preserved within counter1's closure)
counter2(); // 1 (counter2 has its own independent closure and count)
counter1(); // 3

4.5 this 关键字

this 的值在函数调用时确定,取决于函数的调用方式。

  • 全局上下文: this 指向全局对象 (window 在浏览器,global 在 Node.js,严格模式下是 undefined)。
  • 函数调用:
    • 普通函数调用: func() - this 指向全局对象 (非严格模式) 或 undefined (严格模式)。
    • 作为对象方法调用: obj.method() - this 指向调用该方法的对象 (obj)。
    • 构造函数调用: new Constructor(...) - this 指向新创建的实例对象。
    • apply, call, bind: 显式设置 this 的值。
  • 箭头函数: 不绑定自己的 this,它会捕获其所在上下文(词法作用域)的 this 值。
console.log(this === window); // true (in browser, non-module context)

function showThisStrict() {
"use strict";
console.log("Strict mode function 'this':", this); // undefined
}
showThisStrict();

const myObj = {
name: "My Object",
logName: function () {
// Method
console.log("Method 'this'.name:", this.name); // My Object
},
logNameArrow: () => {
// Arrow function as property
// 'this' here refers to the 'this' where myObj was defined (likely window/global)
console.log("Arrow func 'this'.name:", this ? this.name : this); // undefined (or window.name if set)
},
delayedLog: function () {
// Problem: 'this' inside setTimeout callback is not myObj
setTimeout(function () {
console.log(
"Delayed Trad (problematic) 'this'.name:",
this ? this.name : this
); // undefined or window.name
}, 50);
// Solution 1: Arrow function
setTimeout(() => {
console.log("Delayed Arrow 'this'.name:", this.name); // My Object
}, 100);
// Solution 2: bind
setTimeout(
function () {
console.log("Delayed Bind 'this'.name:", this.name); // My Object
}.bind(this),
150
);
},
};

myObj.logName();
myObj.logNameArrow();
myObj.delayedLog();

// Constructor call
function Person(name) {
this.name = name;
console.log("Constructor 'this':", this); // Person { name: 'Alice' }
}
const p = new Person("Alice");

// call, apply, bind
function greetPerson(greeting, punctuation) {
console.log(`${greeting}, ${this.name}${punctuation}`);
}
const personData = { name: "Bob" };

greetPerson.call(personData, "Hello", "!"); // Hello, Bob!
greetPerson.apply(personData, ["Hi", "."]); // Hi, Bob.
const greetBob = greetPerson.bind(personData, "Hey"); // Creates a new function with 'this' bound
greetBob("?"); // Hey, Bob?

5. 数据结构 (Data Structures)

5.1 对象 (Objects)

键值对的无序集合。键通常是字符串 (或 Symbol)。

  • 创建: 字面量 {}new Object()
  • 访问属性: 点表示法 obj.property,方括号表示法 obj['property'] (键可以是变量或非标准标识符)。
  • 添加/修改属性: 直接赋值 obj.newProp = value
  • 删除属性: delete obj.prop
  • 遍历: for...in, Object.keys(), Object.values(), Object.entries() (ES8)。
// Object literal
const student = {
name: "Charlie",
age: 20,
"major subject": "Computer Science", // Key with space needs quotes
greet: function () {
console.log(`Hi, I'm ${this.name}`);
},
};

// Accessing properties
console.log(student.name); // Charlie
console.log(student["age"]); // 20
console.log(student["major subject"]); // Computer Science

// Adding/Modifying
student.grade = "A";
student.age = 21;
console.log(student);

// Deleting
delete student.grade;
console.log(student);

// Methods
student.greet(); // Hi, I'm Charlie

// Iteration
console.log("Keys:", Object.keys(student)); // ["name", "age", "major subject", "greet"]
console.log("Values:", Object.values(student)); // ["Charlie", 21, "Computer Science", ƒ]
console.log("Entries:", Object.entries(student)); // [ ["name", "Charlie"], ... ]

console.log("Using for...in:");
for (const key in student) {
if (Object.hasOwnProperty.call(student, key)) {
console.log(`${key} -> ${student[key]}`);
}
}

5.2 数组 (Arrays)

有序的元素列表。

  • 创建: 字面量 [], new Array().
  • 访问元素: 通过索引 arr[index] (从 0 开始)。
  • 长度: arr.length.
  • 常用方法:
    • 修改原数组: push(), pop(), shift(), unshift(), splice(), sort(), reverse().
    • 不修改原数组 (返回新数组): slice(), concat(), map(), filter(), reduce(), join().
    • 遍历: forEach(), for...of.
// Array literal
const fruits = ["Apple", "Banana", "Orange"];

// Accessing
console.log(fruits[1]); // Banana
console.log(fruits.length); // 3

// Modifying methods
fruits.push("Mango"); // ["Apple", "Banana", "Orange", "Mango"]
console.log("After push:", fruits);
let lastFruit = fruits.pop(); // Removes "Mango"
console.log("Popped:", lastFruit, "Array:", fruits); // Popped: Mango Array: ["Apple", "Banana", "Orange"]
fruits.unshift("Strawberry"); // Adds to beginning
console.log("After unshift:", fruits); // ["Strawberry", "Apple", "Banana", "Orange"]
let firstFruit = fruits.shift(); // Removes "Strawberry"
console.log("Shifted:", firstFruit, "Array:", fruits); // Shifted: Strawberry Array: ["Apple", "Banana", "Orange"]

// splice(start, deleteCount, item1, item2, ...)
fruits.splice(1, 1, "Kiwi", "Peach"); // Remove 1 element at index 1, add "Kiwi", "Peach"
console.log("After splice:", fruits); // ["Apple", "Kiwi", "Peach", "Orange"]

// Non-modifying methods
const citrus = fruits.slice(2); // ["Peach", "Orange"] (from index 2 to end)
console.log("Slice:", citrus, "Original:", fruits); // Original is unchanged

const moreFruits = fruits.concat(["Grape", "Lemon"]);
console.log("Concat:", moreFruits);

const upperFruits = fruits.map((fruit) => fruit.toUpperCase());
console.log("Map:", upperFruits); // ["APPLE", "KIWI", "PEACH", "ORANGE"]

const longFruits = fruits.filter((fruit) => fruit.length > 5);
console.log("Filter:", longFruits); // ["Orange"]

const numbers = [1, 2, 3, 4];
const sumOfNums = numbers.reduce(
(accumulator, currentValue) => accumulator + currentValue,
0
);
console.log("Reduce:", sumOfNums); // 10

// Iteration
console.log("forEach:");
fruits.forEach((fruit, index) => {
console.log(`${index}: ${fruit}`);
});

6. 面向对象编程 (OOP)

6.1 基于原型的继承 (Prototype-based Inheritance)

JavaScript 对象有一个指向其原型对象的内部链接。当试图访问一个对象的属性时,如果在该对象上找不到,就会沿着原型链向上查找。

// Constructor function (pre-ES6 common pattern)
function Animal(name) {
this.name = name;
}

// Add method to the prototype (shared by all instances)
Animal.prototype.speak = function () {
console.log(`${this.name} makes a sound.`);
};

const genericAnimal = new Animal("Generic");
genericAnimal.speak(); // Generic makes a sound.

// Inheriting from Animal
function Dog(name, breed) {
Animal.call(this, name); // Call parent constructor to set 'name' on the Dog instance
this.breed = breed;
}

// Set up the prototype chain: Dog.prototype inherits from Animal.prototype
// Object.create is preferred over new Animal() to avoid running Animal constructor unnecessarily
Dog.prototype = Object.create(Animal.prototype);
// Restore the constructor property (optional but good practice)
Dog.prototype.constructor = Dog;

// Add Dog-specific method or override
Dog.prototype.speak = function () {
// Override
console.log(`${this.name} barks.`);
};
Dog.prototype.wagTail = function () {
console.log(`${this.name} wags tail.`);
};

const myDog = new Dog("Buddy", "Golden Retriever");
myDog.speak(); // Buddy barks. (Own method)
myDog.wagTail(); // Buddy wags tail. (Own method)
console.log(myDog.name); // Buddy (Set by Animal.call)

console.log(myDog instanceof Dog); // true
console.log(myDog instanceof Animal); // true (due to prototype chain)

6.2 class 语法 (ES6 Syntactic Sugar)

ES6 引入了 class 关键字,提供了一种更清晰、更熟悉的语法来创建对象和实现继承,但底层仍然是基于原型的。

  • class 声明类。
  • constructor 方法用于初始化实例。
  • extends 关键字用于继承。
  • super 关键字用于调用父类的构造函数和方法。
  • static 关键字定义静态方法和属性(属于类本身,而非实例)。
  • getset 定义 getter 和 setter。
class Vehicle {
constructor(make, model) {
this.make = make;
this.model = model;
this._serialNumber = Math.random().toString(16).slice(2); // Example private-like property
}

// Method
getInfo() {
return `${this.make} ${this.model}`;
}

// Static method (called on the class itself)
static compareVehicles(v1, v2) {
return v1.make === v2.make && v1.model === v2.model;
}

// Getter
get serialNumber() {
console.log("Getting serial number");
return this._serialNumber;
}

// Setter (usually includes validation or logic)
set serialNumber(value) {
console.log("Attempting to set serial number (not allowed)");
// Example: throw new Error("Serial number cannot be changed");
}
}

class Car extends Vehicle {
constructor(make, model, numDoors) {
super(make, model); // Call parent constructor
this.numDoors = numDoors;
}

// Override parent method
getInfo() {
return `${super.getInfo()} with ${this.numDoors} doors. Serial: ${
this.serialNumber
}`; // Call parent method + getter
}

// Car specific method
honk() {
console.log("Beep beep!");
}
}

const myCar = new Car("Honda", "Civic", 4);
console.log(myCar.getInfo()); // Honda Civic with 4 doors. Serial: <random_hex>
myCar.honk(); // Beep beep!
// myCar.serialNumber = "new"; // Attempting to set serial number (not allowed)

const anotherCar = new Car("Honda", "Civic", 2);
console.log(Vehicle.compareVehicles(myCar, anotherCar)); // true (Using static method)

7. 异步编程 (Asynchronous Programming)

JavaScript 是单线程的,但通过事件循环、回调、Promises 和 async/await 可以处理耗时操作(如网络请求、文件 I/O、定时器)而不会阻塞主线程。

7.1 回调函数 (Callbacks)

将一个函数作为参数传递给另一个函数,在异步操作完成后调用。缺点是容易导致“回调地狱 (Callback Hell)”。

console.log("Start");

function fetchData(url, callback) {
console.log(`Fetching data from ${url}...`);
// Simulate network request delay
setTimeout(() => {
const data = { result: `Data from ${url}` };
// Check for simulated error
const error = url.includes("fail") ? new Error("Failed to fetch") : null;
callback(error, data); // Call the callback with error or data
}, 1000);
}

fetchData("api/users", (err, userData) => {
if (err) {
console.error("Error fetching users:", err.message);
return;
}
console.log("Got user data:", userData);
// Nested callback (potential callback hell)
fetchData(`api/posts?userId=${userData.result}`, (err, postData) => {
if (err) {
console.error("Error fetching posts:", err.message);
return;
}
console.log("Got post data:", postData);
});
});

console.log("End (will log before callbacks finish)");

7.2 Promises (ES6)

Promise 对象表示一个异步操作的最终完成(或失败)及其结果值。它有三种状态: pending (进行中), fulfilled (已成功), rejected (已失败)。使用 .then() 处理成功,.catch() 处理失败,.finally() 处理无论成功或失败都要执行的操作。Promises 可以链式调用,改善了回调地狱。

console.log("Start Promise");

function fetchDataPromise(url) {
return new Promise((resolve, reject) => {
console.log(`Fetching (Promise) from ${url}...`);
setTimeout(() => {
if (url.includes("fail")) {
reject(new Error(`Failed to fetch ${url}`));
} else {
resolve({ result: `Promise data from ${url}` });
}
}, 1000);
});
}

fetchDataPromise("api/users")
.then((userData) => {
// Handle success of first promise
console.log("Promise Got user data:", userData);
// Return a new promise for chaining
return fetchDataPromise(`api/posts?userId=${userData.result}`);
})
.then((postData) => {
// Handle success of second promise
console.log("Promise Got post data:", postData);
// Example of returning a non-promise value for the next .then
return "Processing complete";
})
.then((message) => {
console.log("Final step:", message);
// Simulate another failure
return fetchDataPromise("api/comments?postId=fail");
})
.catch((error) => {
// Handle any error in the chain
console.error("Promise Error:", error.message);
})
.finally(() => {
// Executes regardless of success or failure
console.log("Promise operation finished.");
});

console.log("End Promise (will log before promises resolve/reject)");

7.3 async/await (ES8/ES2017)

建立在 Promises 之上,提供了一种用同步风格编写异步代码的方式,使代码更易读。async 关键字用于声明一个异步函数,该函数隐式返回一个 Promise。await 关键字只能在 async 函数内部使用,它会暂停函数的执行,等待 Promise 被解决(fulfilled 或 rejected),然后恢复执行并返回解决的值(或抛出拒绝的原因)。

console.log("Start Async/Await");

// Re-use the fetchDataPromise function from the Promise example

async function processUserData() {
try {
console.log("Async: Getting user data...");
const userData = await fetchDataPromise("api/users"); // Pauses here until promise resolves
console.log("Async: Got user data:", userData);

console.log("Async: Getting post data...");
const postData = await fetchDataPromise(
`api/posts?userId=${userData.result}`
); // Pauses here
console.log("Async: Got post data:", postData);

console.log("Async: Getting comments (will fail)...");
const commentData = await fetchDataPromise("api/comments?postId=fail"); // Pauses and will throw error
console.log("Async: Got comment data:", commentData); // This line won't be reached

return "All data processed successfully"; // This becomes the resolved value of the promise returned by processUserData
} catch (error) {
console.error("Async/Await Error:", error.message);
// Can re-throw or return an error indicator if needed
return "Processing failed"; // This becomes the resolved value in case of error
} finally {
console.log("Async function finished attempt.");
}
}

// Call the async function and handle its returned promise
processUserData()
.then((result) => console.log("Async function final result:", result))
.catch((err) => console.error("Unexpected error from async function:", err)); // Catch errors not handled inside

console.log("End Async/Await (will log before async function completes)");

8. DOM 操作 (Document Object Model)

在浏览器环境中,JavaScript 可以通过 DOM API 与 HTML 和 XML 文档进行交互。

8.1 选择元素

  • document.getElementById(id): 通过 ID 获取单个元素。
  • document.getElementsByTagName(tagName): 通过标签名获取元素集合 (HTMLCollection)。
  • document.getElementsByClassName(className): 通过类名获取元素集合 (HTMLCollection)。
  • document.querySelector(selector): 通过 CSS 选择器获取匹配的第一个元素。
  • document.querySelectorAll(selector): 通过 CSS 选择器获取所有匹配的元素 (NodeList)。
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>DOM Example</title>
</head>
<body>
<h1 id="main-title">Hello World</h1>
<p class="content">This is a paragraph.</p>
<p class="content">Another paragraph.</p>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<button id="myButton">Click Me</button>

<script src="script.js"></script>
</body>
</html>
// script.js
// Get single elements
const titleElement = document.getElementById("main-title");
const firstParagraph = document.querySelector(".content"); // Gets the first element with class 'content'
const button = document.querySelector("#myButton"); // Using CSS selector for ID

// Get collections
const allParagraphsByClass = document.getElementsByClassName("content"); // HTMLCollection (live)
const allParagraphsBySelector = document.querySelectorAll(".content"); // NodeList (static)
const listItemsByTag = document.getElementsByTagName("li"); // HTMLCollection (live)

console.log(titleElement);
console.log(firstParagraph);
console.log(allParagraphsByClass); // HTMLCollection [ p.content, p.content ]
console.log(allParagraphsBySelector); // NodeList [ p.content, p.content ]
console.log(listItemsByTag); // HTMLCollection [ li, li ]

// Difference between HTMLCollection (live) and NodeList (static from querySelectorAll)
const list = document.querySelector("ul");
const liveListItems = list.getElementsByTagName("li"); // Live collection
const staticListItems = list.querySelectorAll("li"); // Static collection

console.log("Before Add:", liveListItems.length, staticListItems.length); // 2, 2
const newItem = document.createElement("li");
newItem.textContent = "Item 3";
list.appendChild(newItem);
console.log("After Add:", liveListItems.length, staticListItems.length); // 3, 2 (Live collection updated, Static did not)

8.2 修改元素内容和属性

  • element.innerHTML: 获取或设置元素的 HTML 内容。
  • element.textContent: 获取或设置元素的文本内容 (更安全,不会解析 HTML)。
  • element.style.property: 获取或设置 CSS 样式。
  • element.setAttribute(name, value): 设置属性。
  • element.getAttribute(name): 获取属性。
  • element.removeAttribute(name): 移除属性。
  • element.classList: add(), remove(), toggle(), contains() - 操作类名。
// script.js (continued)

// Change content
if (titleElement) {
titleElement.textContent = "DOM Manipulation"; // Safer
// titleElement.innerHTML = "<em>DOM</em> Manipulation"; // Allows HTML
}

// Change style
if (firstParagraph) {
firstParagraph.style.color = "blue";
firstParagraph.style.fontWeight = "bold";
}

// Attributes
const listElement = document.querySelector("ul");
if (listElement) {
listElement.setAttribute("data-id", "fruit-list");
console.log(listElement.getAttribute("data-id")); // fruit-list
}

// ClassList
if (titleElement) {
titleElement.classList.add("important");
titleElement.classList.remove("old-class"); // If it existed
titleElement.classList.toggle("active"); // Add 'active' class
titleElement.classList.toggle("active"); // Remove 'active' class
console.log(titleElement.classList.contains("important")); // true
console.log(titleElement.className); // The full class string
}

8.3 创建和插入/删除元素

  • document.createElement(tagName): 创建新元素。
  • document.createTextNode(text): 创建文本节点。
  • parentNode.appendChild(childNode): 将子节点添加到父节点的末尾。
  • parentNode.insertBefore(newNode, referenceNode): 在参考节点之前插入新节点。
  • parentNode.removeChild(childNode): 移除子节点。
  • element.remove(): (较新) 直接移除元素自身。
// script.js (continued)

// Create new element
const newParagraph = document.createElement("p");
const textNode = document.createTextNode("This is a newly created paragraph.");
newParagraph.appendChild(textNode);
newParagraph.classList.add("content", "new");
newParagraph.style.backgroundColor = "#eee";

// Append to body
document.body.appendChild(newParagraph);

// Insert before the button
const theButton = document.getElementById("myButton");
if (theButton) {
const anotherPara = document.createElement("p");
anotherPara.textContent = "Inserted before button.";
theButton.parentNode.insertBefore(anotherPara, theButton);
}

// Remove an element
const secondParagraph = document.querySelectorAll(".content")[1]; // The "Another paragraph."
if (secondParagraph) {
// secondParagraph.parentNode.removeChild(secondParagraph); // Old way
secondParagraph.remove(); // Simpler new way
}

9. 事件处理 (Event Handling)

响应用户交互(点击、鼠标移动、键盘输入等)或浏览器事件(页面加载完成、窗口大小改变等)。

9.1 添加事件监听器

  • element.addEventListener(eventType, handlerFunction, useCapture): 推荐方式。
    • eventType: 事件名称 (如 "click", "mouseover", "keydown")。
    • handlerFunction: 事件触发时执行的函数。
    • useCapture: 布尔值,指定是在捕获阶段 (true) 还是冒泡阶段 (false, 默认) 处理事件。
  • element.on<eventtype> = handlerFunction: (旧方式) 如 onclick, onmouseover。一次只能附加一个处理程序。

9.2 事件对象 (Event Object)

事件处理函数通常会接收一个事件对象作为参数,包含事件的详细信息。

  • event.type: 事件类型。
  • event.target: 触发事件的原始元素。
  • event.currentTarget: 当前正在处理事件的元素 (在事件冒泡时可能不同于 target)。
  • event.preventDefault(): 阻止事件的默认行为 (如阻止表单提交或链接跳转)。
  • event.stopPropagation(): 阻止事件冒泡到父元素。
  • event.key (键盘事件), event.keyCode (键盘事件, 不推荐)。
  • event.clientX, event.clientY (鼠标事件)。
// script.js (continued)

const clickButton = document.getElementById("myButton");
const mainTitle = document.getElementById("main-title");

function handleButtonClick(event) {
console.log("Button clicked!");
console.log("Event Type:", event.type); // click
console.log("Target Element:", event.target); // <button id="myButton">...</button>
console.log("Current Target:", event.currentTarget); // <button id="myButton">...</button>
mainTitle.textContent = `Button clicked at ${new Date().toLocaleTimeString()}`;
// event.preventDefault(); // Not needed for button click unless it's type="submit" in a form
}

function handleTitleMouseover(event) {
console.log("Mouse is over the title!");
event.target.style.color = "red";
}

function handleTitleMouseout(event) {
event.target.style.color = ""; // Reset color
}

if (clickButton) {
// Preferred way: addEventListener
clickButton.addEventListener("click", handleButtonClick);

// Old way (overwrites previous onclick handlers if any)
// clickButton.onclick = handleButtonClick;
}

if (mainTitle) {
mainTitle.addEventListener("mouseover", handleTitleMouseover);
mainTitle.addEventListener("mouseout", handleTitleMouseout);
}

// Event Propagation Example (Bubbling)
document.body.addEventListener("click", function (event) {
console.log(`Body clicked! Target was: ${event.target.tagName}`);
// If button is clicked, this will log after handleButtonClick logs,
// because the event "bubbles up" from the button to the body.
});

// To stop bubbling from the button:
/*
clickButton.addEventListener('click', function(event) {
handleButtonClick(event);
event.stopPropagation(); // Prevents the body's click handler from firing
});
*/

9.3 事件委托 (Event Delegation)

将事件监听器添加到父元素,利用事件冒泡来处理子元素的事件。适用于子元素是动态添加或数量很多的情况,可以提高性能并简化代码。

<!-- index.html (add this inside body) -->
<ul id="item-list">
<li>Item A</li>
<li>Item B</li>
<li>Item C</li>
</ul>
<button id="addItemBtn">Add Item</button>
// script.js (continued)

const itemList = document.getElementById("item-list");
const addItemBtn = document.getElementById("addItemBtn");

if (itemList) {
// Attach ONE listener to the parent UL
itemList.addEventListener("click", function (event) {
// Check if the clicked element (event.target) is an LI
if (event.target && event.target.nodeName === "LI") {
console.log(`List item clicked: ${event.target.textContent}`);
event.target.style.textDecoration = "line-through"; // Example action
}
});
}

if (addItemBtn) {
let itemCount = 3;
addItemBtn.addEventListener("click", function () {
itemCount++;
const newItem = document.createElement("li");
newItem.textContent = `Item ${String.fromCharCode(65 + itemCount - 1)}`; // A, B, C, D...
itemList.appendChild(newItem);
// No need to add a new event listener to the new LI,
// the parent listener handles it via delegation.
});
}

10. ES6+ 主要特性 (Modern JavaScript Features)

除了上面已涉及的 let/const, Arrow Functions, Classes, Promises, for...of 等,还有:

10.1 模板字面量 (Template Literals)

使用反引号 (`) 定义字符串,可以方便地嵌入变量 ( ${expression}) 和创建多行字符串。

const userName = "Alice";
const userAge = 30;

// Old way
const greetingOld =
"Hello, my name is " +
userName +
" and I am " +
userAge +
" years old.\nThis is a new line.";

// Template literal
const greetingNew = `Hello, my name is ${userName} and I am ${userAge} years old.
This is a new line. Calculation: ${10 + 5}`;

console.log(greetingOld);
console.log(greetingNew);

10.2 解构赋值 (Destructuring Assignment)

从数组或对象中提取值并赋给变量的简洁语法。

// Array Destructuring
const coordinates = [10, 20, 30];
const [x, y, z] = coordinates;
console.log(x, y, z); // 10 20 30

const [a, , c] = coordinates; // Skip an element
console.log(a, c); // 10 30

const [p, ...rest] = coordinates; // Rest element
console.log(p, rest); // 10 [ 20, 30 ]

// Object Destructuring
const userProfile = {
id: 123,
displayName: "Bob",
email: "bob@example.com",
address: {
street: "123 Main St",
city: "Anytown",
},
};

const { displayName, email } = userProfile;
console.log(displayName, email); // Bob bob@example.com

// Rename variables
const { id: userId, email: userEmail } = userProfile;
console.log(userId, userEmail); // 123 bob@example.com

// Default values
const { displayName: name = "Guest", phone = "N/A" } = userProfile;
console.log(name, phone); // Bob N/A

// Nested destructuring
const {
address: { city },
} = userProfile;
console.log(city); // Anytown

// Function parameter destructuring
function printUserInfo({ displayName, email }) {
console.log(`User: ${displayName}, Email: ${email}`);
}
printUserInfo(userProfile); // User: Bob, Email: bob@example.com

10.3 展开语法 (Spread Syntax) 与 剩余语法 (Rest Syntax)

... 语法根据上下文有不同作用。

  • 展开 (Spread): 在需要多个元素/参数/属性的地方,将可迭代对象(数组、字符串)或对象展开。
    • 数组字面量: [...arr1, ...arr2]
    • 函数调用: myFunction(...argsArray)
    • 对象字面量 (ES2018): { ...obj1, ...obj2, key: 'override' }
  • 剩余 (Rest): 在函数参数或解构赋值中,将剩余的元素/属性收集到一个数组或对象中。 (已在函数参数和解构中展示)
// Spread Syntax

// Arrays
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combinedArr = [...arr1, 0, ...arr2];
console.log(combinedArr); // [ 1, 2, 3, 0, 4, 5, 6 ]

const arrCopy = [...arr1]; // Shallow copy
arrCopy.push(4);
console.log(arr1); // [ 1, 2, 3 ] (original unchanged)
console.log(arrCopy); // [ 1, 2, 3, 4 ]

// Function calls
function multiplyThree(a, b, c) {
return a * b * c;
}
const numsToMultiply = [2, 3, 4];
console.log(multiplyThree(...numsToMultiply)); // 24

// Objects (ES2018)
const objA = { a: 1, b: 2 };
const objB = { b: 3, c: 4 };
const combinedObj = { ...objA, ...objB, d: 5 }; // { a: 1, b: 3, c: 4, d: 5 } (objB.b overrides objA.b)
console.log(combinedObj);

const objACopy = { ...objA }; // Shallow copy
objACopy.a = 100;
console.log(objA); // { a: 1, b: 2 }
console.log(objACopy); // { a: 100, b: 2 }

10.4 模块 (Modules - ES6 Imports/Exports)

将代码组织成可重用的、独立的文件。

  • export: 导出函数、类、变量等。
    • 命名导出: export const name = ...; / export function func() {} / export { var1, func2 };
    • 默认导出: export default ...; (每个模块只能有一个)
  • import: 导入其他模块导出的内容。
    • 导入命名导出: import { name1, name2 } from './module.js';
    • 导入命名导出并重命名: import { name1 as newName } from './module.js';
    • 导入所有命名导出到一个对象: import * as myModule from './module.js';
    • 导入默认导出: import myDefaultExport from './module.js';
    • 混合导入: import myDefault, { named1, named2 } from './module.js';
// --- utils.js ---
export const PI = 3.14159;

export function circumference(radius) {
return 2 * PI * radius;
}

export default function greetModule(name = "Module User") {
console.log(`Hello from the module, ${name}!`);
}

const secretValue = "Internal only"; // Not exported

// --- main.js ---
// Assuming utils.js is in the same directory
// Note: In browsers, you need <script type="module" src="main.js"></script>

import greetModule, {
PI,
circumference as calcCircumference,
} from "./utils.js";
// OR import * as utils from './utils.js';

console.log(PI); // 3.14159
// console.log(circumference(10)); // If using named import without alias
console.log(calcCircumference(10)); // 62.8318 (Using alias)

greetModule(); // Hello from the module, Module User!
greetModule("Alice"); // Hello from the module, Alice!

// If using namespace import:
// console.log(utils.PI);
// console.log(utils.circumference(10));
// utils.default(); // Calling default export via namespace

11. 错误处理 (Error Handling)

11.1 try...catch...finally

  • try: 包含可能抛出错误的代码。
  • catch: 如果 try 块中发生错误,则执行此块,接收错误对象。
  • finally: 无论是否发生错误,都会执行此块 (用于清理资源等)。

11.2 throw

手动抛出一个错误(可以是任何值,但通常是 Error 对象或其子类的实例)。

function divide(a, b) {
if (b === 0) {
throw new Error("Division by zero is not allowed."); // Throw an error object
}
if (typeof a !== "number" || typeof b !== "number") {
throw new TypeError("Both arguments must be numbers.");
}
return a / b;
}

try {
console.log("Attempting division...");
let result1 = divide(10, 2);
console.log("Result 1:", result1); // 5

let result2 = divide(8, 0); // This will throw an error
console.log("Result 2:", result2); // This line won't execute
} catch (error) {
// Catches the thrown error
console.error("An error occurred:");
console.error("Name:", error.name); // e.g., "Error" or "TypeError"
console.error("Message:", error.message); // The string passed to the Error constructor
// console.error("Stack:", error.stack); // Stack trace (environment dependent)
} finally {
console.log("Division attempt finished."); // Executes regardless of error
}

try {
let result3 = divide("ten", 2); // Throws TypeError
console.log("Result 3:", result3);
} catch (error) {
console.error("Another error:", error.message);
} finally {
console.log("Finished another attempt.");
}

console.log("Program continues after try...catch.");

12. JSON (JavaScript Object Notation)

轻量级的数据交换格式,易于人阅读和编写,也易于机器解析和生成。基于 JavaScript 对象字面量语法。

  • JSON.stringify(value): 将 JavaScript 值(对象、数组等)转换为 JSON 字符串。
  • JSON.parse(text): 将 JSON 字符串解析为 JavaScript 值。
const dataObject = {
id: 42,
name: "Example Data",
isActive: true,
tags: ["tag1", "tag2"],
metadata: null,
process: function () {}, // Functions are ignored by JSON.stringify
};

// Convert JS object to JSON string
const jsonString = JSON.stringify(dataObject, null, 2); // null, 2 for pretty printing with 2 spaces indentation
console.log("JSON String:\n", jsonString);
/* Output:
JSON String:
{
"id": 42,
"name": "Example Data",
"isActive": true,
"tags": [
"tag1",
"tag2"
],
"metadata": null
}
*/

// Convert JSON string back to JS object
const parsedObject = JSON.parse(jsonString);
console.log("\nParsed Object:", parsedObject);
console.log(parsedObject.name); // Example Data
console.log(parsedObject.tags[0]); // tag1
// console.log(parsedObject.process); // undefined (function was not included in JSON)

13. Web APIs (Browser Environment)

浏览器提供了许多 API 供 JavaScript 调用。

  • DOM API: (已覆盖) 操作文档结构。
  • Fetch API / XMLHttpRequest: 发送和接收网络请求。
  • LocalStorage / SessionStorage: 在浏览器中存储键值对数据。
  • Timers: setTimeout(), setInterval(), clearTimeout(), clearInterval().
  • Geolocation API: 获取用户地理位置。
  • Canvas API: 绘制 2D 图形。
  • Web Workers: 在后台线程执行 JavaScript。
  • ...等等
// Fetch API Example (replaces older XMLHttpRequest)
const apiUrl = "https://jsonplaceholder.typicode.com/posts/1"; // Example public API

fetch(apiUrl)
.then((response) => {
if (!response.ok) {
// Check for HTTP errors (e.g., 404, 500)
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json(); // Parse the response body as JSON (returns a promise)
})
.then((data) => {
console.log("Fetched Data:", data);
})
.catch((error) => {
console.error("Fetch Error:", error);
});

// LocalStorage Example
localStorage.setItem("username", "Alice");
localStorage.setItem("theme", "dark");

const storedUsername = localStorage.getItem("username");
console.log("Stored Username:", storedUsername); // Alice

localStorage.removeItem("theme");
// localStorage.clear(); // Removes all items

// SessionStorage has the same API but data persists only for the session duration

// Timers Example
console.log("Timer Start");
const timeoutId = setTimeout(() => {
console.log("This message appears after 2 seconds.");
}, 2000);

let intervalCount = 0;
const intervalId = setInterval(() => {
intervalCount++;
console.log(`Interval message #${intervalCount}`);
if (intervalCount >= 3) {
clearInterval(intervalId); // Stop the interval
console.log("Interval stopped.");
}
}, 500);

// clearTimeout(timeoutId); // Uncomment to cancel the timeout before it fires