核心知识点
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
:true
或false
。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 break
和 continue
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
关键字定义静态方法和属性(属于类本身,而非实例)。get
和set
定义 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