器→工具, 编程语言

JavaScript学习之入门资料

钱魏Way · · 26 次浏览
目录

JavaScript简介

JavaScript 是一种高层次的、解释型的编程语言,主要用于前端开发。它是 Web 开发的三大核心技术之一(另外两个是 HTML 和 CSS)。

基本概念

  • 解释型语言:JavaScript 是一种解释型语言,代码可以直接在浏览器中被解释和执行,无需预先编译。
  • 高层次语言:JavaScript 是一种高层次语言,提供了许多抽象机制,使开发者能够专注于解决业务逻辑,而不用处理底层细节。
  • 动态类型:JavaScript 是动态类型语言,变量的类型在运行时确定,可以改变。
  • 基于原型的面向对象:JavaScript 使用原型链来实现继承,而不是基于类的继承。

历史和发展

  • 诞生:JavaScript 由 Brendan Eich 在 1995 年为 Netscape Navigator 浏览器开发,最初称为 Mocha,后更名为 LiveScript,最终定名为 JavaScript。
  • 标准化:1997 年,JavaScript 被提交给欧洲计算机制造商协会(ECMA),并成为 ECMA-262 标准,也称为 ECMAScript。
  • 版本演进:JavaScript 的版本不断更新和演进,主要版本包括 ES3、ES5、ES6(ES2015)、以及每年的更新(如 ES2016、ES2017 等)。

主要用途

  • 前端开发:
    • JavaScript 广泛应用于前端开发,用于实现网页的交互和动态效果。通过操作 DOM(文档对象模型),可以动态地修改 HTML 和 CSS 内容。
    • 常用的前端框架和库:React、js、Angular。
  • 后端开发:
    • 通过js,JavaScript 也可以在服务器端运行,支持构建高性能的服务器端应用。
    • 常用的后端框架:js、NestJS、Koa。
  • 全栈开发:
    • 使用 JavaScript 可以实现全栈开发,即前端和后端都使用同一种语言,简化了开发流程。
  • 移动开发:
    • 通过 React Native、Ionic 等框架,JavaScript 可以用于开发跨平台的移动应用。
  • 桌面应用开发:
    • 通过 Electron 可以使用 JavaScript 创建跨平台的桌面应用。

JavaScript基本语法

变量声明

在 JavaScript 中,var、let 和 const 都用于声明变量,但它们之间存在重要的区别,这决定了它们在不同场景下的使用。

var(已废弃,但仍然可用)

var 是 JavaScript 的老式变量声明方式,它具有以下几个特性:

  • 作用域:var声明的变量作用域为函数作用域,而不是块级作用域。这意味着在同一个函数内部,所有 var 声明的变量都是共享的。
  • 提升(Hoisting):var声明的变量会被提升到当前作用域的顶部,无论它们在代码中的实际位置在哪里。
  • 可重复声明:同一个作用域内,可以多次声明同一个var 变量,后面的声明会覆盖前面的值。

使用场景:由于 var 的这些特性可能导致一些难以预料的问题,因此在 ES6(ECMAScript 2015)引入 let 和 const 之后,推荐使用它们代替 var。

let

let 是 ES6 引入的新变量声明方式,它解决了 var 的一些问题:

  • 作用域:let声明的变量具有块级作用域,这意味着它们只在声明它们的代码块(例如,循环、if 语句、函数等)内有效。
  • 不可提升:let不会像 var 那样被提升到作用域顶部,这被称为暂时性死区(Temporal Dead Zone, TDZ)。
  • 不可重复声明:在同一作用域内,不能重复声明一个let 变量。

使用场景:通常用于局部变量,特别是需要块级作用域的场景,如循环、条件语句和避免变量提升导致的问题。

const

const 也属于 ES6 引入的新的变量声明方式,用于声明常量:

  • 作用域:与let 类似,const 也有块级作用域。
  • 不可重新赋值:一旦声明并赋值,const变量的值就不能更改。
  • 注意:尽管不能改变const 变量的值,但如果它是一个对象或数组,对象或数组的属性或元素是可以修改的。

使用场景:用于声明不会改变的常量,如数学常数、配置对象的引用(但不修改对象本身)等。

示例:

function example() {
  var x = 10;
  if (true) {
    var x = 20; // 同名变量,覆盖之前的值
    console.log(x); // 输出 20
  }
  console.log(x); // 输出 20
}

example();

// let 示例
function exampleLet() {
  let y = 10;
  if (true) {
    let y = 20; // 不同作用域的变量
    console.log(y); // 输出 20
  }
  console.log(y); // 输出 10
}

exampleLet();

// const 示例
const pi = 3.14159;
console.log(pi); // 输出 3.14159
pi = 3; // 报错:Assignment to constant variable.

数据类型

JavaScript 中的基本数据类型(也称为原始数据类型)有七种:字符串(String)、数字(Number)、布尔值(Boolean)、空值(Null)、未定义(Undefined)、符号(Symbol)和大整数(BigInt)。这些数据类型都是不可变的,即它们的值无法被改变。每种数据类型有其独特的特点和使用场景。以下是对每种数据类型的详细介绍:

字符串 (String)

字符串用于表示文本数据。可以使用单引号 (‘)、双引号 (“) 或反引号(`)来定义字符串。

let str1 = 'Hello, world!';
let str2 = "JavaScript is awesome!";
let str3 = `Template literals allow embedding expressions: ${str1}`;

特点:

  • 不可变性:字符串一旦创建,其内容不能被改变。任何修改都会生成一个新的字符串。
  • 多行文本:使用反引号(模板字符串),可以定义多行文本。
let multiLineString = `This is
a multi-line
string.`;

常用方法:

  • length:获取字符串长度
  • toUpperCase()、toLowerCase():转换大小写
  • substring(start, end):获取子字符串
  • indexOf(substring)、includes(substring):查找子字符串
  • split(delimiter):分割字符串
let example = "JavaScript";
console.log(example.length); // 10
console.log(example.toUpperCase()); // "JAVASCRIPT"
console.log(example.substring(0, 4)); // "Java"
console.log(example.indexOf("Script")); // 4
console.log(example.includes("Script")); // true
console.log(example.split("a")); // ["J", "v", "Script"]

数字 (Number)

JavaScript 使用 Number 类型表示所有的数字,包括整数和浮点数。

let intNum = 42;
let floatNum = 3.14;
let exponentialNum = 1.23e5; // 123000

特点:

  • IEEE 754 标准:JavaScript 的数字类型基于 IEEE 754 标准的双精度 64 位浮点数。
  • 特殊值:NaN(非数字)、Infinity和 -Infinity。

常用方法:

  • parseInt(string)、parseFloat(string):将字符串转换为数字
  • toFixed(digits):格式化数字为指定小数位数
  • Math对象提供了许多数学函数,如 round()、Math.ceil()、Math.floor()、Math.random()、Math.max()、Math.min()。
let num = 123.456;
console.log(parseInt("123")); // 123
console.log(parseFloat("123.45")); // 123.45
console.log(num.toFixed(2)); // "123.46"
console.log(Math.round(num)); // 123
console.log(Math.ceil(num)); // 124
console.log(Math.floor(num)); // 123
console.log(Math.random()); // 0 到 1 之间的随机数
console.log(Math.max(1, 2, 3)); // 3
console.log(Math.min(1, 2, 3)); // 1

布尔值 (Boolean)

布尔值只有两个可能的值:true 和 false。

let isTrue = true;

let isFalse = false;

特点:

  • 逻辑运算:常用于逻辑判断和控制流语句(如if、while 等)。
  • 常用运算符
    • 逻辑与&&
    • 逻辑或||
    • 逻辑非!
let a = true;
let b = false;
console.log(a && b); // false
console.log(a || b); // true
console.log(!a); // false

空值 (Null)

null 表示一个空值或一个无效的对象引用。

let emptyValue = null;

特点:

  • 类型:null的类型是 object,这是一个被认为是设计失误的历史遗留问题。

未定义 (Undefined)

undefined 表示一个变量尚未被赋值。

let notAssigned;
console.log(notAssigned); // undefined

特点:

  • 自动初始化:声明但未赋值的变量自动初始化为undefined。

符号 (Symbol)

Symbol 是一种原始数据类型,用于创建唯一的标识符。

let symbol1 = Symbol();
let symbol2 = Symbol('description');

特点:

  • 唯一性:每个Symbol 值都是唯一的,即使描述相同。
  • 不可枚举:默认情况下,Symbol 属性不会被包含在..in 迭代或 Object.keys() 中。
let sym1 = Symbol('foo');
let sym2 = Symbol('foo');
console.log(sym1 === sym2); // false

大整数 (BigInt)

BigInt 是一种可以表示任意精度整数的原始数据类型。

let bigIntNum = BigInt(123456789012345678901234567890);
let anotherBigInt = 123456789012345678901234567890n;

特点:

  • 表示大整数:用于表示超过Number 类型安全整数范围(2^53 – 1)的整数。
  • 操作:可以进行正常的算术运算,但不能与Number 类型混合运算。
let big1 = 12345678901234567890n;
let big2 = 98765432109876543210n;
console.log(big1 + big2); // 111111111011111111100n
console.log(big2 - big1); // 86419753208641975320n

数据类型检测

可以使用 typeof 操作符来检测变量的数据类型。

console.log(typeof 'Hello'); // "string"
console.log(typeof 42); // "number"
console.log(typeof true); // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol()); // "symbol"
console.log(typeof BigInt(123)); // "bigint"
console.log(typeof null); // "object"(这是一个被认为是设计失误的历史遗留问题)
console.log(typeof {}); // "object"
console.log(typeof function() {}); // "function"

操作符

JavaScript 提供了多种操作符,用于执行各种运算和操作。常见的操作符包括算术操作符、赋值操作符、比较操作符、逻辑操作符和位操作符。下面是对这些操作符的详细介绍:

算术操作符(Arithmetic Operators)

算术操作符用于执行数学运算,主要包括加法、减法、乘法、除法等。

常见的算术操作符:

  • +:加法
  • -:减法
  • *:乘法
  • /:除法
  • %:取模(余数)
  • ++:自增
  • –:自减

示例:

let a = 10;
let b = 3;

console.log(a + b); // 13
console.log(a - b); // 7
console.log(a * b); // 30
console.log(a / b); // 3.3333333333333335
console.log(a % b); // 1

a++; // a = a + 1,a 变为 11
b--; // b = b - 1,b 变为 2

console.log(a); // 11
console.log(b); // 2

赋值操作符(Assignment Operators)

赋值操作符用于为变量分配值或进行某种运算后再赋值。

常见的赋值操作符:

  • =:赋值
  • +=:加法赋值
  • -=:减法赋值
  • *=:乘法赋值
  • /=:除法赋值
  • %=:取模赋值

示例:

let x = 5;

x += 3; // 相当于 x = x + 3,x 变为 8
x -= 2; // 相当于 x = x - 2,x 变为 6
x *= 4; // 相当于 x = x * 4,x 变为 24
x /= 2; // 相当于 x = x / 2,x 变为 12
x %= 5; // 相当于 x = x % 5,x 变为 2

console.log(x); // 输出 2

比较操作符(Comparison Operators)

比较操作符用于比较两个值,并返回布尔值(true 或 false)。

常见的比较操作符:

  • ==:相等
  • ===:全等(严格相等)
  • !=:不相等
  • !==:不全等(严格不相等)
  • >:大于
  • <:小于
  • >=:大于等于
  • <=:小于等于

示例:

let m = 10;
let n = '10';

console.log(m == n); // true,因为 == 允许类型转换
console.log(m === n); // false,因为 === 不允许类型转换
console.log(m != n); // false,因为 == 允许类型转换
console.log(m !== n); // true,因为 === 不允许类型转换
console.log(m > 5); // true
console.log(m < 15); // true
console.log(m >= 10); // true
console.log(m <= 9); // false

逻辑操作符(Logical Operators)

逻辑操作符用于布尔值的逻辑运算,主要包括与、或、非。

常见的逻辑操作符:

  • &&:逻辑与(AND)
  • ||:逻辑或(OR)
  • !:逻辑非(NOT)

示例:

let p = true;
let q = false;

console.log(p && q); // false,只有两个操作数都为 true 时才返回 true
console.log(p || q); // true,只有两个操作数都为 false 时才返回 false
console.log(!p); // false,将 true 取反为 false
console.log(!q); // true,将 false 取反为 true

位操作符(Bitwise Operators)

位操作符用于按位运算,即对二进制位进行操作。

常见的位操作符:

  • &:按位与
  • |:按位或
  • ^:按位异或
  • ~:按位非
  • <<:左移
  • >>:右移
  • >>>:无符号右移

示例:

let u = 5; // 二进制:0101
let v = 3; // 二进制:0011

console.log(u & v); // 1,按位与:0001
console.log(u | v); // 7,按位或:0111
console.log(u ^ v); // 6,按位异或:0110
console.log(~u); // -6,按位非:取反码再加1,结果是 -(u+1)
console.log(u << 1); // 10,左移一位:1010
console.log(u >> 1); // 2,右移一位:0010
console.log(u >>> 1); // 2,无符号右移一位:0010

控制结构

JavaScript 的流程控制语句用于控制代码的执行顺序,根据不同的条件执行不同的代码块。常见的流程控制语句包括条件语句、循环语句和跳转语句。下面是对这些语句的详细介绍:

条件语句(Conditional Statements)

条件语句用于根据不同的条件执行不同的代码块。

if 语句

if 语句用于在条件为 true 时执行代码块。

let condition = true;

if (condition) {
  console.log("Condition is true");
}

if…else 语句

if...else 语句用于在条件为 true 时执行一个代码块,为 false 时执行另一个代码块。
let condition = false;

if (condition) {
  console.log("Condition is true");
} else {
  console.log("Condition is false");
}

if…else if…else 语句

if…else if…else 语句用于测试多个条件。

let value = 10;

if (value > 10) {
  console.log("Value is greater than 10");
} else if (value === 10) {
  console.log("Value is equal to 10");
} else {
  console.log("Value is less than 10");
}

三元运算符(Ternary Operator)

三元运算符是一个简洁的条件语句,condition ? expr1 : expr2。

let age = 18;
let canVote = age >= 18 ? "Yes" : "No";
console.log(canVote); // 输出 "Yes"

switch 语句

switch 语句用于针对多个条件执行不同代码块。

let fruit = "apple";

switch (fruit) {
  case "apple":
    console.log("An apple a day keeps the doctor away.");
    break;
  case "banana":
    console.log("Bananas are high in potassium.");
    break;
  case "orange":
    console.log("Oranges are a great source of vitamin C.");
    break;
  default:
    console.log("Unknown fruit.");
}

循环语句(Loop Statements)

循环语句用于重复执行代码块,直到特定条件为 false。

for 循环

for 循环用于执行定次数的循环。

for (let i = 0; i < 5; i++) {
  console.log(i); // 输出 0, 1, 2, 3, 4
}

while 循环

while 循环在条件为 true 时重复执行代码块。

let i = 0;
while (i < 5) {
  console.log(i); // 输出 0, 1, 2, 3, 4
  i++;
}

do…while 循环

do…while 循环先执行代码块,然后在条件为 true 时继续执行。

let i = 0;
do {
  console.log(i); // 输出 0, 1, 2, 3, 4
  i++;
} while (i < 5);

for…in 循环

for…in 循环用于遍历对象的可枚举属性。

let person = { name: "John", age: 30 };

for (let key in person) {
  console.log(key + ": " + person[key]);
}
// 输出
// name: John
// age: 30

for…of 循环

for…of 循环用于遍历可迭代对象(如数组、字符串、集合等)。

let array = [10, 20, 30];

for (let value of array) {
  console.log(value);
}
// 输出
// 10
// 20
// 30

跳转语句(Jump Statements)

跳转语句用于控制循环和条件语句的执行。

break 语句

break 语句用于立即退出循环或 switch 语句。

for (let i = 0; i < 10; i++) {
  if (i === 5) {
    break; // 在 i 等于 5 时退出循环
  }
  console.log(i);
}
// 输出 0, 1, 2, 3, 4

continue 语句

continue 语句用于跳过当前迭代并继续下一次迭代。

for (let i = 0; i < 5; i++) {
  if (i === 2) {
    continue; // 跳过 i 等于 2 的迭代
  }
  console.log(i);
}
// 输出 0, 1, 3, 4

return 语句

return 语句用于退出函数并返回一个值。

function sum(a, b) {
  return a + b; // 返回 a 和 b 的和
}

console.log(sum(5, 3)); // 输出 8

异常处理(Exception Handling)

异常处理用于捕获和处理运行时错误,确保程序不会因为错误而中断。

try…catch 语句

try…catch 语句用于捕获和处理异常。

try {
  let result = someFunction(); // 尝试执行函数
  console.log(result);
} catch (error) {
  console.error("An error occurred:", error.message); // 捕获并处理错误
}

finally 语句

finally 语句用于在 try 和 catch 块之后执行代码,不论是否发生异常。

try {
  let result = someFunction(); // 尝试执行函数
  console.log(result);
} catch (error) {
  console.error("An error occurred:", error.message); // 捕获并处理错误
} finally {
  console.log("This will always be executed."); // 始终执行
}

throw 语句

throw 语句用于手动抛出异常。

function checkAge(age) {
  if (age < 18) {
    throw new Error("Age must be 18 or older."); // 手动抛出异常
  }
  return "Access granted.";
}

try {
  console.log(checkAge(15)); // 尝试检查年龄
} catch (error) {
  console.error("An error occurred:", error.message); // 捕获并处理错误
}

函数基础

在 JavaScript 中,函数是一等公民(first-class citizen),它们可以作为变量赋值、传递给其他函数、从函数中返回等。函数是逻辑单元,它们封装了一段可复用的代码。以下是对 JavaScript 函数的详细介绍:

函数定义

函数声明(Function Declaration)

函数声明使用 function 关键字定义一个函数。

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

console.log(add(2, 3)); // 输出 5

函数表达式(Function Expression)

函数表达式将一个函数赋值给一个变量,可以是匿名函数或命名函数。

const multiply = function(a, b) {
  return a * b;
};

console.log(multiply(2, 3)); // 输出 6

箭头函数(Arrow Function)

箭头函数是 ES6 引入的简洁的函数定义方式,使用箭头 => 语法。

const subtract = (a, b) => {
  return a - b;
};

console.log(subtract(5, 2)); // 输出 3

如果箭头函数只有一个表达式,可以省略大括号 {} 和 return 关键字。

const divide = (a, b) => a / b;

console.log(divide(6, 3)); // 输出 2

立即执行函数(Immediately Invoked Function Expression, IIFE)

IIFE 是一种立即执行的函数表达式,常用于创建独立的作用域。

(function() {
  console.log("This is an IIFE");
})();

// 使用箭头函数的 IIFE
(() => {
  console.log("This is another IIFE");
})();

函数参数

默认参数(Default Parameters)

可以为函数参数设置默认值,如果调用时未提供参数,则使用默认值。

function greet(name = "Guest") {
  return `Hello, ${name}!`;
}

console.log(greet()); // 输出 "Hello, Guest!"
console.log(greet("Alice")); // 输出 "Hello, Alice!"

剩余参数(Rest Parameters)

使用 … 语法,可以将多个参数收集为一个数组。

function sum(...numbers) {
  return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4)); // 输出 10

函数返回值

函数可以返回任意类型的值,包括基本类型和对象类型。如果函数没有 return 语句,则默认返回 undefined。

function noReturn() {}

console.log(noReturn()); // 输出 undefined

function returnObject() {
  return {
    name: "John",
    age: 30
  };
}

console.log(returnObject()); // 输出 { name: "John", age: 30 }

函数方法

JavaScript 提供了一些内置的方法来操作函数。

call

call 方法调用一个函数,并显式指定 this 值和参数。

function greet(greeting) {
  console.log(greeting + ", " + this.name);
}

const person = { name: "Alice" };
greet.call(person, "Hello"); // 输出 "Hello, Alice"

apply

apply 方法与 call 类似,但它接受一个参数数组。

function greet(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: "Alice" };
greet.apply(person, ["Hello", "!"]); // 输出 "Hello, Alice!"

bind

bind 方法创建一个新的函数,并将 this 绑定到指定的值。

function greet(greeting) {
  console.log(greeting + ", " + this.name);
}

const person = { name: "Alice" };
const boundGreet = greet.bind(person);
boundGreet("Hello"); // 输出 "Hello, Alice"

高阶函数

高阶函数(Higher-Order Function)是指接受函数作为参数,或者返回一个函数作为结果的函数。在 JavaScript 中,高阶函数是函数式编程的一个重要特性,它可以使代码更加灵活和可重用。

接受函数作为参数

高阶函数可以接受一个或多个函数作为参数,这使得可以将函数作为操作对象传递给高阶函数,从而实现更灵活的行为。

示例:Array.prototype.map

map 函数是一个常见的高阶函数,它接受一个回调函数作为参数,并对数组中的每个元素应用该回调函数,返回一个新的数组。

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(function(number) {
  return number * 2;
});

console.log(doubled); // 输出: [2, 4, 6, 8, 10]

在这个例子中,map 函数接受一个回调函数,并对数组 numbers 中的每个元素应用该回调函数,生成一个新的数组 doubled。

示例:Array.prototype.filter

filter 函数也是一个高阶函数,它接受一个回调函数作为参数,并返回一个新的数组,包含所有通过回调函数测试的元素。

const numbers = [1, 2, 3, 4, 5];

const evenNumbers = numbers.filter(function(number) {
  return number % 2 === 0;
});

console.log(evenNumbers); // 输出: [2, 4]

在这个例子中,filter 函数接受一个回调函数,并返回一个新的数组 evenNumbers,包含所有满足条件的偶数。

返回一个函数作为结果

高阶函数也可以返回一个新的函数,从而实现函数的动态生成和组合。

示例:创建一个倍数函数

function createMultiplier(multiplier) {
  return function(number) {
    return number * multiplier;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);

console.log(double(5)); // 输出: 10
console.log(triple(5)); // 输出: 15

在这个例子中,createMultiplier 是一个高阶函数,它返回一个新的函数,该函数将输入的数字乘以指定的倍数。

示例:函数组合

函数组合是函数式编程中的一个重要概念,可以通过高阶函数实现。

function compose(f, g) {
  return function(x) {
    return f(g(x));
  };
}

const add1 = x => x + 1;
const multiply2 = x => x * 2;

const add1ThenMultiply2 = compose(multiply2, add1);

console.log(add1ThenMultiply2(5)); // 输出: 12

在这个例子中,compose 函数接受两个函数 f 和 g,返回一个新的函数,该函数首先应用 g,然后应用 f。

实用高阶函数示例

示例:柯里化(Currying)

柯里化是将接受多个参数的函数转换为一系列接受单个参数的函数的技术。

function curry(f) {
  return function(a) {
    return function(b) {
      return f(a, b);
    };
  };
}

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

const curriedAdd = curry(add);

console.log(curriedAdd(1)(2)); // 输出: 3

在这个例子中,curry 函数将 add 函数柯里化,生成一个接受单个参数的函数链。

示例:偏应用(Partial Application)

偏应用是将函数的一些参数预先应用,生成一个新的函数。

function partial(f, ...fixedArgs) {
  return function(...remainingArgs) {
    return f(...fixedArgs, ...remainingArgs);
  };
}

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

const add5 = partial(add, 5);

console.log(add5(3)); // 输出: 8

在这个例子中,partial 函数预先应用了 add 函数的第一个参数,生成一个新的函数 add5,它只需要一个参数。

高阶函数在回调中的使用

高阶函数在异步编程和事件处理中也非常常见。例如,在处理 AJAX 请求时,可以将回调函数传递给异步函数。

function fetchData(url, callback) {
  fetch(url)
    .then(response => response.json())
    .then(data => callback(null, data))
    .catch(error => callback(error, null));
}

fetchData('https://api.example.com/data', function(error, data) {
  if (error) {
    console.error('Error fetching data:', error);
  } else {
    console.log('Data fetched:', data);
  }
});

在这个例子中,fetchData 是一个高阶函数,它接受一个回调函数 callback,在数据获取完成后调用该回调函数。

匿名函数

匿名函数(Anonymous Functions)是没有名字的函数。在 JavaScript 中,匿名函数可以用于多种场景,如回调、立即执行函数以及高阶函数等。匿名函数通常用于那些不需要在多个地方重复使用的函数。

创建匿名函数

作为函数表达式

匿名函数可以作为函数表达式的一部分,赋值给变量或常量。

const add = function(a, b) {
  return a + b;
};

console.log(add(2, 3)); // 输出 5
立即执行函数表达式(Immediately Invoked Function Expression, IIFE)
IIFE 是一种立即执行的匿名函数,常用于创建独立的作用域,避免变量污染全局作用域。
(function() {
  console.log("This is an IIFE");
})();

// 使用箭头函数的 IIFE
(() => {
  console.log("This is another IIFE");
})();

匿名函数的使用场景

回调函数(Callback Functions)

匿名函数经常用于回调函数中,回调函数是作为参数传递给另一个函数并在特定事件或条件下调用的函数。

setTimeout(function() {
  console.log("This is a callback function");
}, 1000);

// 使用箭头函数
setTimeout(() => {
  console.log("This is another callback function");
}, 1000);

高阶函数(Higher-order Functions)

高阶函数是接受一个或多个函数作为参数,或返回一个函数作为结果的函数。匿名函数经常用于这些场景。

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(function(num) {
  return num * 2;
});

console.log(doubled); // 输出 [2, 4, 6, 8, 10]

// 使用箭头函数
const squared = numbers.map(num => num * num);
console.log(squared); // 输出 [1, 4, 9, 16, 25]

事件处理器(Event Handlers)

在浏览器环境中,匿名函数经常用于事件处理器。

document.getElementById("myButton").addEventListener("click", function() {
  console.log("Button clicked");
});

// 使用箭头函数
document.getElementById("myButton").addEventListener("click", () => {
  console.log("Button clicked");
});

匿名函数的优缺点

优点

  • 简洁: 匿名函数可以使代码更简洁,尤其是在函数只在一个地方使用时。
  • 避免命名冲突: 由于匿名函数没有名字,不会污染全局命名空间,减少命名冲突的风险。
  • 创建闭包: 匿名函数常用于创建闭包,可以保存函数作用域外的变量状态。

缺点

  • 调试困难: 由于匿名函数没有名字,在堆栈跟踪和调试时可能不容易识别。
  • 可读性差: 如果过度使用匿名函数,尤其是嵌套的匿名函数,会降低代码的可读性。

命名函数表达式(Named Function Expressions)

虽然匿名函数没有名字,但你也可以在函数表达式中为它们命名,这样在调试时更容易识别。

const factorial = function fact(n) {
  if (n <= 1) return 1;
  return n * fact(n - 1);
};

console.log(factorial(5)); // 输出 120

箭头函数

箭头函数(Arrow Functions)是 ES6(ECMAScript 2015)引入的一种新的函数定义方式,它们提供了一种更加简洁的语法,并且在处理 this 绑定方面有一些独特的特性。箭头函数在很多场景中都非常有用,尤其是在函数作为参数传递或者简化回调函数中。

基本语法

箭头函数使用箭头 => 语法定义,基本形式如下:

const functionName = (parameters) => {
  // 函数体
};

示例:

const add = (a, b) => {
  return a + b;
};

console.log(add(2, 3)); // 输出 5

简化语法

单个参数

如果箭头函数只有一个参数,可以省略圆括号 ()。

const square = x => {
  return x * x;
};

console.log(square(4)); // 输出 16

无参数

如果箭头函数没有参数,需要使用空括号 ()。

const sayHello = () => {
  console.log("Hello");
};

sayHello(); // 输出 "Helconst multiply = (a, b) => a * b;

console.log(multiply(2, 3)); // 输出 6

this 绑定

箭头函数与普通函数最显著的区别之一是它们如何处理 this 关键字。箭头函数不会创建自己的 this,它会捕获其所在上下文的 this 值,作为自己的 this 值。这在处理回调函数时特别有用。

在普通函数中

function Person() {
  this.age = 0;

  setInterval(function growUp() {
    this.age++;
    console.log(this.age); // `this` 指向全局对象(或 `undefined` 在严格模式下)
  }, 1000);
}

const p = new Person();

在箭头函数中

function Person() {
  this.age = 0;

  setInterval(() => {
    this.age++;
    console.log(this.age); // `this` 指向 `Person` 实例
  }, 1000);
}

const p = new Person();

使用场景

高阶函数和回调

const numbers = [1, 2, 3, 4, 5];

const doubled = numbers.map(num => num * 2);
console.log(doubled); // 输出 [2, 4, 6, 8, 10]

setTimeout(() => {
  console.log("This is a callback function");
}, 1000);

事件处理器

document.getElementById("myButton").addEventListener("click", () => {
  console.log("Button clicked");
});
注意事项
没有 arguments 对象
箭头函数没有 arguments 对象。如果需要访问 arguments,可以使用剩余参数(rest parameters)语法。
const sum = (...args) => {
  return args.reduce((total, num) => total + num, 0);
};

console.log(sum(1, 2, 3)); // 输出 6

不能用作构造函数

箭头函数不能使用 new 操作符来实例化对象,因为它们没有 [[Construct]] 方法。

const Person = (name) => {
  this.name = name;
};

// const john = new Person("John"); // 会抛出错误

没有 prototype 属性

由于箭头函数不能作为构造函数,它们也没有 prototype 属性。

const foo = () => {};
console.log(foo.prototype); // 输出 undefined

无法使用 yield 关键字

箭头函数不能用作生成器函数,因为它们没有自己的执行上下文。

const generator = () => {
  // yield 1; // 会抛出错误
};

回调函数

回调函数(Callback Functions)是 JavaScript 中的一种重要编程模式,它们使得异步编程变得更加灵活和强大。回调函数就是作为参数传递给另一个函数并在某个时刻被调用的函数。通过回调函数,您可以在一个函数完成某项任务后执行另一个函数,而不需要阻塞代码执行。

基本概念

回调函数就是将一个函数作为参数传递给另一个函数,并在特定条件或事件发生时调用这个传递的函数。

示例:

function greet(name, callback) {
  console.log("Hello, " + name);
  callback();
}

function sayGoodbye() {
  console.log("Goodbye!");
}

greet("Alice", sayGoodbye);
// 输出:
// Hello, Alice
// Goodbye!

同步回调和异步回调

同步回调

同步回调是在主函数完成之前立即执行的回调。

function processArray(arr, callback) {
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i]);
  }
}

processArray([1, 2, 3, 4], function(element) {
  console.log(element * 2);
});
// 输出:
// 2
// 4
// 6
// 8

异步回调

异步回调是在主函数完成之后执行的回调,常用于处理异步操作,如网络请求、文件读取等。

console.log("Start");

setTimeout(function() {
  console.log("This is executed after 2 seconds");
}, 2000);

console.log("End");

// 输出:
// Start
// End
// This is executed after 2 seconds

回调地狱(Callback Hell)

当回调函数存在嵌套调用时,代码会变得难以阅读和维护,这种现象被称为回调地狱(Callback Hell)。

setTimeout(function() {
  console.log("Step 1");
  setTimeout(function() {
    console.log("Step 2");
    setTimeout(function() {
      console.log("Step 3");
      // 继续嵌套回调...
    }, 1000);
  }, 1000);
}, 1000);

// 输出:
// Step 1 (after 1 second)
// Step 2 (after another 1 second)
// Step 3 (after another 1 second)

解决回调地狱的方法

使用命名函数

将嵌套的匿名回调函数替换为命名函数,可以提高代码的可读性。

function step1() {
  console.log("Step 1");
  setTimeout(step2, 1000);
}

function step2() {
  console.log("Step 2");
  setTimeout(step3, 1000);
}

function step3() {
  console.log("Step 3");
}

setTimeout(step1, 1000);

使用 Promise

Promise 是一种用于处理异步操作的对象,它可以避免回调地狱。

function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

wait(1000)
  .then(() => {
    console.log("Step 1");
    return wait(1000);
  })
  .then(() => {
    console.log("Step 2");
    return wait(1000);
  })
  .then(() => {
    console.log("Step 3");
  });

// 输出:
// Step 1 (after 1 second)
// Step 2 (after another 1 second)
// Step 3 (after another 1 second)

使用 async/await

async/await 是基于 Promise 的语法糖,使异步代码看起来像同步代码。

function wait(ms) {
  return new Promise(resolve => {
    setTimeout(resolve, ms);
  });
}

async function executeSteps() {
  await wait(1000);
  console.log("Step 1");
  await wait(1000);
  console.log("Step 2");
  await wait(1000);
  console.log("Step 3");
}

executeSteps();

// 输出:
// Step 1 (after 1 second)
// Step 2 (after another 1 second)
// Step 3 (after another 1 second)

回调函数的常见使用场景

事件处理

回调函数在事件处理机制中非常常见。

document.getElementById("myButton").addEventListener("click", function() {
  console.log("Button clicked");
});

定时器

setTimeout 和 setInterval 等函数也需要回调函数。
setTimeout(function() {
  console.log("Executed after 1 second");
}, 1000);

setInterval(function() {
  console.log("Executed every 2 seconds");
}, 2000);

异步操作

回调函数在处理异步操作时非常有用,如网络请求、文件读取等。

fetch("https://api.example.com/data")
  .then(response => response.json())
  .then(data => {
    console.log(data);
  })
  .catch(error => {
    console.error("Error:", error);
  });

闭包

闭包(Closure)是 JavaScript 中的一个重要概念,它是指那些能够访问自由变量的函数。自由变量是指在函数中使用的,但既不是该函数参数也不是该函数的局部变量的变量。闭包可以记住并访问它的词法作用域,即使当函数在其词法作用域之外执行时。

闭包的定义和基本示例

定义

闭包是指一个函数以及其词法环境(Lexical Environment)的组合。这个环境包含了这个函数能够访问的所有局部变量。

基本示例

function outerFunction() {
  let outerVariable = 'I am outside!';

  function innerFunction() {
    console.log(outerVariable); // 闭包:访问外部函数的变量
  }

  return innerFunction;
}

const myInnerFunction = outerFunction();
myInnerFunction(); // 输出 "I am outside!"

在这个例子中,innerFunction 是一个闭包,因为它可以访问 outerFunction 的局部变量 outerVariable,即使在 outerFunction 已经执行完毕之后。

闭包的应用场景

数据封装和私有变量

闭包可以用于创建私有变量,从而实现数据封装。

function createCounter() {
  let count = 0;

  return {
    increment: function() {
      count++;
      return count;
    },
    decrement: function() {
      count--;
      return count;
    },
    getCount: function() {
      return count;
    }
  };
}

const counter = createCounter();
console.log(counter.increment()); // 输出 1
console.log(counter.increment()); // 输出 2
console.log(counter.decrement()); // 输出 1
console.log(counter.getCount()); // 输出 1

在这个例子中,count 变量在 createCounter 函数外部是不可访问的,但可以通过返回的对象中的方法访问,这些方法都形成了闭包。

延迟执行

闭包可以保存当前的变量状态,在未来某个时刻执行。

function delayLog(message, delay) {
  setTimeout(function() {
    console.log(message); // 每个回调都是一个闭包,记住了它创建时的 `message` 变量
  }, delay);
}

delayLog('Hello after 1 second', 1000);
delayLog('Hello after 2 seconds', 2000);

模拟块级作用域

在 ES6 之前,JavaScript 没有块级作用域,闭包可以用来模拟块级作用域。

for (var i = 0; i < 3; i++) {
  (function(j) {
    setTimeout(function() {
      console.log(j); // 输出 0, 1, 2
    }, 1000);
  })(i);
}

在这个例子中,每次循环都会创建一个新的闭包,使得 setTimeout 回调函数可以记住当前的 i 值。

闭包的内存管理

由于闭包会持有对其词法环境的引用,这可能会导致一些内存问题,如内存泄漏。为了避免这种情况,可以显式地将不再需要的引用设为 null。

function outerFunction() {
  let bigData = new Array(1000); // 大数组

  function innerFunction() {
    console.log(bigData.length);
  }

  bigData = null; // 手动清理大数组
  return innerFunction;
}

const myInnerFunction = outerFunction();
myInnerFunction(); // 输出 1000

闭包的优缺点

优点

  • 数据封装: 闭包可以创建私有变量,保护数据的隐私性。
  • 函数式编程: 闭包是实现高阶函数、柯里化等函数式编程范式的重要工具。
  • 延迟执行: 闭包可以保存当前状态并在未来某个时刻执行,非常适合异步编程。

缺点

  • 内存消耗: 闭包持有对其词法环境的引用,可能会导致内存泄漏,尤其是在处理大数据时。
  • 调试困难: 闭包的调试可能比较困难,因为它们的执行上下文可能不容易追踪。

对象(Object)

定义和创建对象

对象是 JavaScript 中的一种用于存储键值对(key-value pairs)的数据结构。它们可以通过对象字面量、构造函数或 Object.create() 方法来创建。

对象字面量

const person = {
  name: "Alice",
  age: 25,
  greet: function() {
    console.log("Hello, my name is " + this.name);
  }
};

console.log(person.name); // 输出 "Alice"
person.greet(); // 输出 "Hello, my name is Alice"

构造函数

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.greet = function() {
    console.log("Hello, my name is " + this.name);
  };
}

const person = new Person("Alice", 25);
console.log(person.name); // 输出 "Alice"
person.greet(); // 输出 "Hello, my name is Alice"

Object.create()

const personPrototype = {
  greet: function() {
    console.log("Hello, my name is " + this.name);
  }
};

const person = Object.create(personPrototype);
person.name = "Alice";
person.age = 25;

console.log(person.name); // 输出 "Alice"
person.greet(); // 输出 "Hello, my name is Alice"

对象的属性和方法

访问和修改属性

const person = {
  name: "Alice",
  age: 25
};

console.log(person.name); // 输出 "Alice"
person.age = 30;
console.log(person.age); // 输出 "30"

// 使用方括号语法
console.log(person["name"]); // 输出 "Alice"
person["age"] = 35;
console.log(person["age"]); // 输出 "35"

删除属性

delete person.age;
console.log(person.age); // 输出 "undefined"

遍历对象属性

const person = {
  name: "Alice",
  age: 25
};

for (let key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key + ": " + person[key]);
  }
}

// 使用 Object.keys() 返回一个属性数组
Object.keys(person).forEach(key => {
  console.log(key + ": " + person[key]);
});

数组(Array)

定义和创建数组

数组是 JavaScript 中用于存储有序数据集合的数据结构。数组可以通过数组字面量或 Array 构造函数来创建。

数组字面量

const fruits = ["apple", "banana", "cherry"];
console.log(fruits[0]); // 输出 "apple"

构造函数

const fruits = new Array("apple", "banana", "cherry");
console.log(fruits[1]); // 输出 "banana"

数组的方法

添加和删除元素

  • push():在数组末尾添加一个或多个元素
  • pop():删除数组末尾的元素
  • unshift():在数组开头添加一个或多个元素
  • shift():删除数组开头的元素
const fruits = ["apple", "banana"];

fruits.push("cherry");
console.log(fruits); // 输出 ["apple", "banana", "cherry"]

fruits.pop();
console.log(fruits); // 输出 ["apple", "banana"]

fruits.unshift("grape");
console.log(fruits); // 输出 ["grape", "apple", "banana"]

fruits.shift();
console.log(fruits); // 输出 ["apple", "banana"]

迭代数组

  • forEach()
  • map()
  • filter()
  • reduce()
const fruits = ["apple", "banana", "cherry"];

fruits.forEach(fruit => {
  console.log(fruit);
});
// 输出:
// apple
// banana
// cherry

const upperFruits = fruits.map(fruit => fruit.toUpperCase());
console.log(upperFruits); // 输出 ["APPLE", "BANANA", "CHERRY"]

const filteredFruits = fruits.filter(fruit => fruit.startsWith("b"));
console.log(filteredFruits); // 输出 ["banana"]

const totalLength = fruits.reduce((total, fruit) => total + fruit.length, 0);
console.log(totalLength); // 输出 17

查找和检查元素

  • indexOf()和 lastIndexOf()
  • includes()
  • find()和 findIndex()
const fruits = ["apple", "banana", "cherry", "banana"];

console.log(fruits.indexOf("banana")); // 输出 1
console.log(fruits.lastIndexOf("banana")); // 输出 3

console.log(fruits.includes("cherry")); // 输出 true

const foundFruit = fruits.find(fruit => fruit.startsWith("b"));
console.log(foundFruit); // 输出 "banana"

const foundIndex = fruits.findIndex(fruit => fruit.startsWith("b"));
console.log(foundIndex); // 输出 1

数组的解构赋值

解构赋值是从数组或对象中提取值并将其赋值给变量的语法。

const fruits = ["apple", "banana", "cherry"];

const [first, second, third] = fruits;
console.log(first); // 输出 "apple"
console.log(second); // 输出 "banana"
console.log(third); // 输出 "cherry"

多维数组

数组可以包含数组,这样的数组称为多维数组。

const matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

console.log(matrix[0][1]); // 输出 2
console.log(matrix[2][0]); // 输出 7

JavaScript 在浏览器中的运行环境

JavaScript 在浏览器中的运行环境是非常重要的概念,因为大多数开发者初次接触 JavaScript 时,都是在浏览器环境下进行开发的。以下是对 JavaScript 在浏览器中运行环境的详细介绍:

浏览器内核

浏览器内核主要包括两部分:渲染引擎和 JavaScript 引擎。

  • 渲染引擎: 负责解析 HTML 和 CSS,并将其渲染成用户可以看到的页面。常见的渲染引擎包括 Blink(Chrome 和 Edge)、WebKit(Safari)、Gecko(Firefox)。
  • JavaScript 引擎: 负责解析和执行 JavaScript 代码。常见的 JavaScript 引擎包括 V8(Chrome 和js)、SpiderMonkey(Firefox)、JavaScriptCore(Safari)。

DOM(文档对象模型)

DOM 是一种将 HTML 和 XML 文档表示为树结构的接口,每个节点代表文档的一部分。JavaScript 可以通过 DOM 操作页面内容。

// 获取页面中的元素
const element = document.getElementById('myElement');

// 修改元素的内容
element.innerHTML = 'Hello, World!';

BOM(浏览器对象模型)

BOM 允许 JavaScript 与浏览器进行交互,包含了一组用于操作浏览器窗口和实现浏览器功能的对象。

  • window: 代表浏览器窗口,是全局对象。
  • navigator: 提供了关于浏览器的信息,如用户代理等。
  • location: 提供了当前 URL 的信息,并允许重定向页面。
  • history: 提供了浏览器的历史记录,可以前进和后退。
  • screen: 提供了关于用户屏幕的信息。
// 获取浏览器的用户代理
console.log(navigator.userAgent);

// 重定向到另一个页面
location.href = 'https://www.example.com';

BOM操作

BOM(Browser Object Model,浏览器对象模型)是 JavaScript 用来与浏览器进行交互的一组 API。BOM 提供了操作浏览器窗口和框架、处理浏览器历史记录、管理 cookies 和其他浏览器相关任务的方法。以下是对 BOM 主要组件及其操作的详细介绍:

window 对象

window 对象是 BOM 的核心,它表示浏览器窗口。window 对象是所有其他对象的全局对象。

示例:

console.log(window.innerWidth); // 获取窗口的宽度
console.log(window.innerHeight); // 获取窗口的高度

常用方法和属性

尺寸和位置:

window.innerWidth; // 获取窗口的内部宽度(不包括工具栏和滚动条)
window.innerHeight; // 获取窗口的内部高度
window.outerWidth; // 获取窗口的外部宽度(包括工具栏和滚动条)
window.outerHeight; // 获取窗口的外部高度
window.moveTo(x, y); // 将窗口移动到 (x, y) 坐标
window.resizeTo(width, height); // 调整窗口大小

弹出窗口:

window.open(url, name, specs, replace); // 打开新窗口
window.close(); // 关闭当前窗口

定时器:

let timerId = window.setTimeout(function, delay); // 设置定时器(一次性)
window.clearTimeout(timerId); // 清除定时器
let intervalId = window.setInterval(function, interval); // 设置间隔定时器(循环)
window.clearInterval(intervalId); // 清除间隔定时器

对话框:

window.alert(message); // 显示警告框
window.confirm(message); // 显示确认框,返回 true 或 false
window.prompt(message, defaultValue); // 显示输入框,返回用户输入的值

document 对象

document 对象表示整个页面,是 DOM(Document Object Model,文档对象模型)的入口。通过 document 对象可以访问和操作 HTML 内容。

示例:

document.title = "New Title"; // 修改文档标题
console.log(document.getElementById("myElement")); // 获取元素

常用方法和属性

查找元素:

document.getElementById(id); // 通过 ID 查找元素
document.getElementsByClassName(className); // 通过类名查找元素
document.getElementsByTagName(tagName); // 通过标签名查找元素
document.querySelector(selector); // 通过 CSS 选择器查找单个元素
document.querySelectorAll(selector); // 通过 CSS 选择器查找所有元素

创建和操作元素:

let newElement = document.createElement(tagName); // 创建新元素
document.body.appendChild(newElement); // 将新元素添加到文档中
let textNode = document.createTextNode(text); // 创建文本节点
newElement.appendChild(textNode); // 将文本节点添加到元素中
newElement.setAttribute(name, value); // 设置元素属性
newElement.removeAttribute(name); // 移除元素属性

页面内容:

document.body; // 获取文档的 body 元素
document.title; // 获取或设置文档标题
document.cookie; // 获取或设置文档的 cookies

navigator 对象

navigator 对象包含关于浏览器的信息,例如用户代理、平台和在线状态等。

示例:

console.log(navigator.userAgent); // 浏览器的用户代理字符串
console.log(navigator.platform); // 浏览器运行的平台
console.log(navigator.onLine); // 浏览器的在线状态

location 对象

location 对象表示当前文档的 URL,并提供了操作 URL 和导航的方法。

示例:

console.log(location.href); // 获取当前 URL
location.href = "https://www.example.com"; // 导航到新 URL
location.reload(); // 重新加载当前页面

常用方法和属性

URL 组成部分:

location.protocol; // 获取 URL 的协议部分(如 "https:")
location.hostname; // 获取 URL 的主机名(如 "www.example.com")
location.port; // 获取 URL 的端口号(如 "80" 或 "443")
location.pathname; // 获取 URL 的路径部分(如 "/path/to/page")
location.search; // 获取 URL 的查询字符串(如 "?id=123")
location.hash; // 获取 URL 的哈希部分(如 "#section1")

导航:

location.assign(url); // 导航到新 URL
location.replace(url); // 替换当前页面,不保留历史记录
location.reload(forceReload); // 重新加载当前页面,可以选择是否强制从服务器加载

history 对象

history 对象用于操作浏览器的会话历史记录,例如在历史记录中前进或后退。

示例:

history.back(); // 后退一步
history.forward(); // 前进一步
history.go(-2); // 后退两步,负数表示后退,正数表示前进

常用方法

导航历史:

history.back(); // 后退
history.forward(); // 前进
history.go(delta); // 根据 delta 值导航

历史记录长度:

console.log(history.length); // 获取历史记录的长度

HTML5 新增方法:

history.pushState(state, title[, url]); // 添加一个新的历史记录条目
history.replaceState(state, title[, url]); // 修改当前的历史记录条目

screen 对象

screen 对象包含关于用户屏幕的信息,例如屏幕分辨率、颜色深度等。虽然这个对象的使用相对较少,但在某些情况下仍然有用,例如调整布局以适应不同的屏幕大小。

示例:

console.log(screen.width); // 获取屏幕的宽度
console.log(screen.height); // 获取屏幕的高度
console.log(screen.colorDepth); // 获取屏幕的颜色深度

DOM 操作

DOM(Document Object Model)树是网页文档的结构化表示,类似于家谱树,但它表示的是网页中的HTML或XML文档结构。DOM树将文档解析为节点的层次结构,每个节点表示文档的一部分,比如标签、属性、文本等。这种结构使得JavaScript可以通过编程方式访问和操作文档内容和结构。

DOM 树的基本结构

一个典型的 HTML 文档会被解析成如下的 DOM 树结构:

<!DOCTYPE html>
<html>
  <head>
    <title>DOM Tree Example</title>
  </head>
  <body>
    <h1>Hello, World!</h1>
    <p>This is a simple DOM tree example.</p>
  </body>
</html>

解析为 DOM 树后的结构如下:

#document
  |
  |-- html
       |
       |-- head
       |    |
       |    |-- title
       |          |
       |          |-- "DOM Tree Example" (text node)
       |
       |-- body
            |
            |-- h1
            |    |
            |    |-- "Hello, World!" (text node)
            |
            |-- p
                 |
                 |-- "This is a simple DOM tree example." (text node)

DOM 节点类型

DOM 树由不同类型的节点组成,每种节点都有特定的属性和方法。主要节点类型包括:

  • 文档节点(Document Node)
    • 整个文档的根节点。
    • 通过document 对象访问。
  • 元素节点(Element Node)
    • HTML 标签,比如<html>, <body>, <div> 等。
    • 可以通过.createElement() 方法创建。
  • 属性节点(Attribute Node)
    • 元素的属性,比如id, class, href 等。
    • 可以通过.setAttribute() 和 .getAttribute() 方法操作。
  • 文本节点(Text Node)
    • 元素或属性中的文本内容。
    • 可以通过.createTextNode() 方法创建。
  • 注释节点(Comment Node)
    • 文档中的注释。
    • 可以通过.createComment() 方法创建。

遍历 DOM 树

通过 DOM 提供的属性和方法,可以遍历和操作 DOM 树。

访问子节点

  • parentNode: 获取父节点
  • childNodes: 获取子节点集合
  • firstChild: 获取第一个子节点
  • lastChild: 获取最后一个子节点

示例:

const body = document.body;

console.log(body.parentNode); // 输出 <html> 节点
console.log(body.childNodes); // 输出 body 的子节点集合
console.log(body.firstChild); // 输出 <h1> 节点
console.log(body.lastChild);  // 输出 <p> 节点

访问兄弟节点

  • nextSibling: 获取下一个兄弟节点
  • previousSibling: 获取上一个兄弟节点

示例:

const h1 = document.querySelector('h1');

console.log(h1.nextSibling); // 输出 <p> 节点
console.log(h1.previousSibling); // 可能是文本节点或空白节点

操作 DOM 树

DOM(Document Object Model)是 HTML 和 XML 文档的标准表示,它将文档解析为树形结构,使得可以使用 JavaScript 来操作文档的各个部分。以下是一些常见的 DOM 操作:

获取元素

getElementById() 通过元素 ID 获取元素。

const element = document.getElementById('myElement');
console.log(element);

getElementsByClassName() 通过类名获取元素集合。

const elements = document.getElementsByClassName('myClass');
console.log(elements);

getElementsByTagName() 通过标签名获取元素集合。

const elements = document.getElementsByTagName('p');
console.log(elements);

querySelector() 选择匹配 CSS 选择器的第一个元素。

const element = document.querySelector('#myElement');
console.log(element);

querySelectorAll() 选择匹配 CSS 选择器的所有元素,返回一个 NodeList。

const elements = document.querySelectorAll('.myClass, p');
console.log(elements);

元素操作

innerHTML 设置或获取元素的 HTML 内容。

const element = document.getElementById('myElement');
element.innerHTML = '<h1>Hello, World!</h1>';
console.log(element.innerHTML);

innerText 和 textContent 设置或获取元素的文本内容。

const element = document.getElementById('myElement');
element.innerText = 'Hello, World!';
console.log(element.innerText);

appendChild() 向元素的子节点末尾添加新元素。

const parent = document.getElementById('parent');
const child = document.createElement('div');
child.textContent = 'Child Element';
parent.appendChild(child);

insertBefore() 在指定元素之前插入新元素。

const parent = document.getElementById('parent');
const existingChild = document.getElementById('existingChild');
const newChild = document.createElement('div');
newChild.textContent = 'New Child';
parent.insertBefore(newChild, existingChild);

removeChild() 移除元素。

const parent = document.getElementById('parent');
const childToRemove = document.getElementById('childToRemove');
parent.removeChild(childToRemove);

属性操作

getAttribute() 获取元素的属性值。

const element = document.getElementById('myElement');
const attributeValue = element.getAttribute('data-id');
console.log(attributeValue);

setAttribute() 设置元素的属性值。

const element = document.getElementById('myElement');
element.setAttribute('data-id', '123');

removeAttribute() 移除元素的属性。

const element = document.getElementById('myElement');
element.removeAttribute('data-id');

事件处理

addEventListener() 为元素添加事件监听器。

const button = document.getElementById('myButton');
button.addEventListener('click', () => {
  console.log('Button clicked');
});

removeEventListener()移除事件监听器。

const button = document.getElementById('myButton');
button.removeEventListener('click', clickHandler);

function clickHandler() {
  console.log('Button clicked');
}

样式操作

style 直接操作元素的内联样式。

const element = document.getElementById('myElement');
element.style.color = 'red';
element.style.fontSize = '20px';

getComputedStyle() 获取元素的计算样式(包括浏览器应用的默认样式和继承样式)。

const element = document.getElementById('myElement');
const styles = window.getComputedStyle(element);
console.log(styles.getPropertyValue('color'));

事件处理

在 JavaScript 中,事件处理是非常重要的概念,主要用于响应用户的交互操作如点击、输入、悬停等。事件处理的关键组成部分包括事件监听器(或事件处理程序)、事件对象和事件传播机制。

事件驱动编程

JavaScript 是一种事件驱动语言,这意味着程序的行为可以由各种事件触发,如用户的点击、键盘输入、鼠标移动、文件加载完成等。事件驱动编程是 JavaScript 在 Web 开发中非常重要的特性,它允许开发者编写响应用户交互的代码。以下是对 JavaScript 事件驱动核心特性的详细介绍。

事件驱动编程是一种编程范式,其中程序的执行是由事件触发的。事件可以是用户的动作(如点击按钮、滚动页面)或系统的变化(如 AJAX 请求完成、定时器结束)。在事件驱动编程中,程序等待事件的发生,当事件发生时,执行相应的事件处理程序。

事件监听与事件处理程序

事件监听器(Event Listener)或事件处理程序(Event Handler)是绑定到特定元素上的函数,当特定事件发生时,这些函数会被调用。

添加事件监听器

可以使用 addEventListener 方法来添加事件监听器,这是推荐的方式,因为它允许在同一个元素上添加多个事件处理程序。

const button = document.getElementById('myButton');

button.addEventListener('click', function(event) {
  console.log('Button clicked!');
});

移除事件监听器

可以使用 removeEventListener 方法来移除事件监听器。

function handleClick(event) {
  console.log('Button clicked!');
}

button.addEventListener('click', handleClick);

// 需要传入相同的函数引用才能成功移除
button.removeEventListener('click', handleClick);

常见事件类型

JavaScript 支持许多类型的事件,以下是一些常见的事件类型:

  • 鼠标事件:click,dblclick, mousedown, mouseup, mousemove, mouseenter, mouseleave
  • 键盘事件:keydown,keypress, keyup
  • 表单事件:submit,change, input, focus, blur
  • 窗口事件:load,resize, scroll, unload

事件对象

当事件被触发时,会产生一个事件对象(Event Object),该对象包含了事件的相关信息,并作为参数传递给事件处理程序。

常见的事件对象属性

  • type:事件的类型(如 “click”, “keydown”)
  • target:触发事件的元素
  • currentTarget:绑定事件处理程序的元素
  • preventDefault():阻止事件的默认行为
  • stopPropagation():停止事件传播
  • clientX和 clientY:鼠标点击时在窗口中的坐标
  • key:键盘事件中按下的键的值

示例:

button.addEventListener('click', function(event) {
  console.log('Event type:', event.type);
  console.log('Event target:', event.target);
  console.log('Mouse coordinates:', event.clientX, event.clientY);
});

事件传播(Event Propagation)

事件传播机制决定了事件在 DOM 树中的传播方式。事件传播分为三个阶段:

  • 捕获阶段(Capturing Phase):事件从根元素向目标元素传播
  • 目标阶段(Target Phase):事件到达目标元素
  • 冒泡阶段(Bubbling Phase):事件从目标元素向根元素传播

捕获和冒泡

默认情况下,事件处理程序在冒泡阶段触发。可以通过传递第三个参数 true 给 addEventListener 方法来使事件处理程序在捕获阶段触发。

const parent = document.getElementById('parent');
const child = document.getElementById('child');

// 捕获阶段
parent.addEventListener('click', function(event) {
  console.log('Parent capturing');
}, true);

// 冒泡阶段
parent.addEventListener('click', function(event) {
  console.log('Parent bubbling');
});

child.addEventListener('click', function(event) {
  console.log('Child clicked');
});

阻止事件传播

可以使用 stopPropagation() 方法来阻止事件在 DOM 树中的传播。

parent.addEventListener('click', function(event) {
  console.log('Parent clicked');
});

child.addEventListener('click', function(event) {
  event.stopPropagation();
  console.log('Child clicked');
});

阻止默认行为

有时候可能需要阻止事件的默认行为,可以使用 preventDefault() 方法。

const link = document.getElementById('myLink');

link.addEventListener('click', function(event) {
  event.preventDefault();
  console.log('Default action prevented');
});

事件代理(Event Delegation)

事件代理是一种将事件处理程序添加到父元素而不是多个子元素的方法。通过利用事件冒泡机制,可以使父元素处理来自子元素的事件,从而提高性能和简化代码。

假设有一个动态生成的列表项,你希望在点击任意列表项时执行某些操作。添加事件处理程序到每个列表项会非常低效,可以使用事件代理:

<ul id="myList">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
const list = document.getElementById('myList');

list.addEventListener('click', function(event) {
  if (event.target && event.target.nodeName === 'LI') {
    console.log('List item clicked:', event.target.textContent);
  }
});

异步事件

JavaScript 的事件驱动模型非常适合处理异步操作,如 AJAX 请求、定时器等。异步操作可以通过事件触发回调函数来处理。

使用定时器的异步事件:

setTimeout(function() {
  console.log('This message is displayed after 2 seconds');
}, 2000);

// 异步的 AJAX 请求:
fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => {
    console.log('Data fetched:', data);
  })
  .catch(error => {
    console.error('Error fetching data:', error);
  });

面向对象编程

面向对象编程(OOP)是一种编程范式,通过将数据和操作数据的函数封装在对象中来实现。JavaScript 是一种基于原型的面向对象编程语言,但 ES6 引入了类(class)语法,使得面向对象编程变得更加直观和易于理解。

类与实例

类(Class)

类是创建对象的蓝图或模板。ES6 引入了 class 关键字来定义类。

class Person {
  // 构造函数
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // 方法
  greet() {
    console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
  }
}

实例(Instance)

实例是从类创建的对象。使用 new 关键字可以创建类的实例。

const john = new Person('John', 30);
john.greet(); // 输出: Hello, my name is John and I am 30 years old.

const jane = new Person('Jane', 25);
jane.greet(); // 输出: Hello, my name is Jane and I am 25 years old.

构造函数模式

在 ES6 之前,JavaScript 没有类(class)的概念,主要通过构造函数和原型来模拟类和继承。

构造函数

构造函数是一种特殊的函数,用于创建和初始化对象实例。它们通常与 new 操作符一起使用。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

const alice = new Person("Alice", 25);
console.log(alice.name); // 输出 "Alice"
console.log(alice.age); // 输出 25

原型继承

每个 JavaScript 函数都有一个 prototype 属性,指向一个对象。这个对象是通过构造函数创建的实例的原型。

function Person(name, age) {
  this.name = name;
  this.age = age;
}

Person.prototype.greet = function() {
  console.log("Hello, my name is " + this.name);
};

const alice = new Person("Alice", 25);
alice.greet(); // 输出 "Hello, my name is Alice"

ES6 class 语法

ES6 引入了 class 语法,使面向对象编程更加直观和简洁。

定义类

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log("Hello, my name is " + this.name);
  }
}

const alice = new Person("Alice", 25);
alice.greet(); // 输出 "Hello, my name is Alice"

继承

使用 extends 关键字实现类的继承,子类可以继承父类的属性和方法。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  greet() {
    console.log("Hello, my name is " + this.name);
  }
}

class Student extends Person {
  constructor(name, age, major) {
    super(name, age); // 调用父类的构造函数
    this.major = major;
  }

  study() {
    console.log(this.name + " is studying " + this.major);
  }
}

const alice = new Student("Alice", 25, "Computer Science");
alice.greet(); // 输出 "Hello, my name is Alice"
alice.study(); // 输出 "Alice is studying Computer Science"

继承(extends)

继承是面向对象编程的一个重要特性,允许一个类继承另一个类的属性和方法。使用 extends 关键字可以实现继承。

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a noise.`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // 调用父类的构造函数
    this.breed = breed;
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

const myDog = new Dog('Rex', 'German Shepherd');
myDog.speak(); // 输出: Rex barks.

在上面的例子中,Dog 类继承了 Animal 类,并且重写了 speak 方法。super 关键字用于调用父类的构造函数和方法。

原型链(Prototype Chain)

JavaScript 中的每个对象都有一个原型对象(prototype),对象可以通过原型从其他对象继承属性和方法。这种继承机制称为原型链。

原型对象与 __proto__

每个函数都有一个 prototype 属性,指向其原型对象。对象通过 __proto__ 属性指向其构造函数的原型对象。

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

const cat = new Animal('Whiskers');
cat.speak(); // 输出: Whiskers makes a noise.

console.log(cat.__proto__ === Animal.prototype); // 输出: true

原型链示例

当访问一个对象的属性或方法时,JavaScript 引擎首先在对象自身查找,如果找不到,会沿着原型链继续查找。

function Animal(name) {
  this.name = name;
}

Animal.prototype.speak = function() {
  console.log(`${this.name} makes a noise.`);
};

function Dog(name, breed) {
  Animal.call(this, name);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.speak = function() {
  console.log(`${this.name} barks.`);
};

const myDog = new Dog('Rex', 'German Shepherd');
myDog.speak(); // 输出: Rex barks.

console.log(myDog.__proto__ === Dog.prototype); // 输出: true
console.log(Dog.prototype.__proto__ === Animal.prototype); // 输出: true

在这个例子中,Dog 继承了 Animal,并且通过 Object.create(Animal.prototype) 设置了原型链。访问 myDog.speak 时,JavaScript 会首先在 myDog 对象查找,如果找不到,会沿着原型链在 Dog.prototype 和 Animal.prototype 中查找。

类的静态方法和静态属性

静态方法和静态属性是直接定义在类本身上的,而不是类的实例上的。

静态方法

class MathHelper {
  static square(x) {
    return x * x;
  }
}

console.log(MathHelper.square(5)); // 输出 25

静态属性

class Configuration {
  static DEFAULT_TIMEOUT = 5000;
}

console.log(Configuration.DEFAULT_TIMEOUT); // 输出 5000

私有属性和方法

当前,JavaScript 提供了一种方式来定义类的私有属性和方法,通过在属性或方法前面加上 # 字符。

私有属性

class Person {
  #name;
  #age;

  constructor(name, age) {
    this.#name = name;
    this.#age = age;
  }

  greet() {
    console.log("Hello, my name is " + this.#name);
  }
}

const alice = new Person("Alice", 25);
alice.greet(); // 输出 "Hello, my name is Alice"
console.log(alice.#name); // 抛出错误:SyntaxError: Private field '#name' must be declared in an enclosing class

Getters 和 Setters

Getters 和 Setters 提供了一种更灵活的方式来访问和设置对象的属性。

class Person {
  constructor(name, age) {
    this._name = name; // 注意属性名的命名约定
    this._age = age;
  }

  get name() {
    return this._name;
  }

  set name(newName) {
    this._name = newName;
  }

  get age() {
    return this._age;
  }

  set age(newAge) {
    if (newAge > 0) {
      this._age = newAge;
    } else {
      console.log("Age must be positive");
    }
  }
}

const alice = new Person("Alice", 25);
console.log(alice.name); // 输出 "Alice"
alice.name = "Bob";
console.log(alice.name); // 输出 "Bob"
alice.age = -5; // 输出 "Age must be positive"
console.log(alice.age); // 输出 25

多态性

多态性允许一个对象在不同的上下文中表现出不同的行为。通过方法的重载和覆盖来实现多态性。

方法重载(JavaScript 不直接支持,但可以通过参数处理)

class MathHelper {
  static add(a, b) {
    if (typeof a === 'string' || typeof b === 'string') {
      return String(a) + String(b);
    }
    return a + b;
  }
}

console.log(MathHelper.add(1, 2)); // 输出 3
console.log(MathHelper.add("Hello", "World")); // 输出 "HelloWorld"

方法覆盖

class Animal {
  makeSound() {
    console.log("Some generic sound");
  }
}

class Dog extends Animal {
  makeSound() {
    console.log("Bark");
  }
}

const myDog = new Dog();
myDog.makeSound(); // 输出 "Bark"

异步编程

JavaScript 是一种单线程语言,这意味着它一次只能执行一段代码。然而,现代 Web 应用程序需要处理许多任务,比如网络请求、文件读取和计时等,这些任务可能会耗费大量时间。如果这些任务是同步执行的,那么会阻塞主线程,导致用户界面无响应。为了避免这种情况,JavaScript 提供了异步编程的机制。JavaScript 中有多种实现异步编程的方式,包括回调函数、Promise、async/await 等。

回调函数

回调函数(Callback Function)是最基本的异步编程方式。一个函数作为参数传递给另一个函数,并在任务完成时调用该回调函数。

示例:

function fetchData(callback) {
  setTimeout(() => {
    const data = { name: 'Alice', age: 25 };
    callback(data);
  }, 2000);
}

fetchData((data) => {
  console.log('Data received:', data);
});

// 输出(2秒后):
// Data received: { name: 'Alice', age: 25 }

Promise

Promise 是 ES6 引入的一种异步编程方式,提供了一种更加优雅和链式调用的方式来处理异步操作。Promise 提供了一种更优雅的方式来处理异步操作,避免了回调地狱。Promise 有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。

创建和使用 Promise

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { name: 'Alice', age: 25 };
      resolve(data);
    }, 2000);
  });
};

fetchData()
  .then((data) => {
    console.log('Data received:', data);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

// 输出(2秒后):
// Data received: { name: 'Alice', age: 25 }

Promise 链式调用

多个异步操作可以通过链式调用来处理,避免嵌套回调(回调地狱)。

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { name: 'Alice', age: 25 };
      resolve(data);
    }, 2000);
  });
};

fetchData()
  .then((data) => {
    console.log('Data received:', data);
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        data.age += 1;
        resolve(data);
      }, 2000);
    });
  })
  .then((updatedData) => {
    console.log('Updated data:', updatedData);
  })
  .catch((error) => {
    console.error('Error:', error);
  });

// 输出(2秒后):
// Data received: { name: 'Alice', age: 25 }
// 输出(再2秒后):
// Updated data: { name: 'Alice', age: 26 }

async/await

async 和 await 是 ES8 引入的语法糖,用于简化基于 Promise 的异步编程,使代码看起来更像同步代码。

定义 async 函数

使用 async 关键字定义的函数返回一个 Promise。await 关键字只能在 async 函数内部使用,用于等待一个 Promise 的结果。

示例:

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { name: 'Alice', age: 25 };
      resolve(data);
    }, 2000);
  });
};

const processData = async () => {
  try {
    const data = await fetchData();
    console.log('Data received:', data);

    const updatedData = await new Promise((resolve, reject) => {
      setTimeout(() => {
        data.age += 1;
        resolve(data);
      }, 2000);
    });

    console.log('Updated data:', updatedData);
  } catch (error) {
    console.error('Error:', error);
  }
};

processData();

// 输出(2秒后):
// Data received: { name: 'Alice', age: 25 }
// 输出(再2秒后):
// Updated data: { name: 'Alice', age: 26 }

异步编程的常见场景

事件处理

事件处理是异步的,当事件发生时,回调函数会被调用。

document.getElementById('myButton').addEventListener('click', () => {
  console.log('Button clicked');
});

定时器

setTimeout 和 setInterval 是异步的。
setTimeout(() => {
  console.log('Executed after 2 seconds');
}, 2000);

let count = 0;
const intervalId = setInterval(() => {
  count++;
  console.log('Interval executed', count);
  if (count === 5) {
    clearInterval(intervalId);
  }
}, 1000);

网络请求

使用 fetch API 发送网络请求时,返回一个 Promise。

const fetchData = async () => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    console.log('Data:', data);
  } catch (error) {
    console.error('Error:', error);
  }
};

fetchData();

// 输出:
// Data: { userId: 1, id: 1, title: "delectus aut autem", completed: false }

并行执行异步任务

有时候需要并行执行多个异步操作,可以使用 Promise.all 和 Promise.race 来处理这种情况。

Promise.all

Promise.all 接受一个包含多个 Promise 的数组,当所有 Promise 都完成时,返回一个新的 Promise,该 Promise 的值是一个数组,包含每个 Promise 的结果。

const promise1 = doSomethingAsync();
const promise2 = doSomethingAsync();
const promise3 = doSomethingAsync();

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log(results); // 输出: ['Async operation completed', 'Async operation completed', 'Async operation completed']
  })
  .catch(error => {
    console.error(error);
  });

Promise.race

Promise.race 接受一个包含多个 Promise 的数组,当任意一个 Promise 完成时,返回一个新的 Promise,该 Promise 的值是第一个完成的 Promise 的结果。

const promise1 = new Promise((resolve) => setTimeout(resolve, 100, 'First'));
const promise2 = new Promise((resolve) => setTimeout(resolve, 200, 'Second'));

Promise.race([promise1, promise2])
  .then(result => {
    console.log(result); // 输出: First
  })
  .catch(error => {
    console.error(error);
  });

错误处理

JavaScript 的错误处理机制帮助开发者捕获和处理运行时错误,确保代码更加健壮和可靠。主要的错误处理机制包括 try…catch 语句、throw 语句、自定义错误类型以及现代的 Promise 和 async/await 异步错误处理。

try…catch 语句

try…catch 语句用于捕获和处理代码块中的异常。

语法:

try {
  // 尝试执行的代码
} catch (error) {
  // 捕获错误并处理
} finally {
  // 可选的,始终会执行的代码块
}

示例:

try {
  let result = someUndefinedFunction();
  console.log(result);
} catch (error) {
  console.error('Error caught:', error.message);
} finally {
  console.log('This will always run');
}

// 输出:
// Error caught: someUndefinedFunction is not defined
// This will always run

finally 代码块

无论是否发生错误,finally 代码块中的代码都会执行,通常用于释放资源或做清理工作。

try {
  let result = someFunction();
} catch (error) {
  console.error('Error caught:', error.message);
} finally {
  console.log('Cleaning up...');
}

throw 语句

throw 语句用于抛出一个自定义错误,可以是字符串、数字、布尔值或对象。

示例:

function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero is not allowed.');
  }
  return a / b;
}

try {
  console.log(divide(10, 0));
} catch (error) {
  console.error('Caught an error:', error.message);
}

// 输出:
// Caught an error: Division by zero is not allowed.

错误类型

JavaScript 中的错误类型是为了帮助开发者识别和处理程序中的各种异常情况。理解这些错误类型有助于更好地进行调试和错误处理。以下是 JavaScript 中常见的错误类型及其详细解释:

Error

Error 是所有错误类型的基类,所有其他错误类型都继承自 Error。可以直接使用 Error 构造函数创建一个通用的错误对象。

示例:

throw new Error("Something went wrong");

SyntaxError

SyntaxError 表示代码的语法错误,例如缺少括号、分号等。通常在代码解析阶段(编译时)就会被发现。

示例:

try {
  eval("var a = ;"); // 语法错误
} catch (e) {
  console.log(e instanceof SyntaxError); // true
}

TypeError

TypeError 表示变量或参数不是预期类型。例如,试图调用非函数类型的变量,或者访问未定义的属性。

示例:

try {
  const obj = null;
  obj.prop = 123; // TypeError: Cannot set property 'prop' of null
} catch (e) {
  console.log(e instanceof TypeError); // true
}

ReferenceError

ReferenceError 表示对未定义的变量进行引用。例如,访问一个不存在的变量。

示例:

try {
  console.log(nonExistentVariable); // ReferenceError: nonExistentVariable is not defined
} catch (e) {
  console.log(e instanceof ReferenceError); // true
}

RangeError

RangeError 表示数值超出允许的范围。例如,数组长度为负,或者传递给函数的参数超出范围。

示例:

try {
  const arr = new Array(-1); // RangeError: Invalid array length
} catch (e) {
  console.log(e instanceof RangeError); // true
}

URIError

URIError 表示全局 URI 处理函数(如 decodeURI、decodeURIComponent、encodeURI、encodeURIComponent)的参数无效。

示例:

try {
  decodeURIComponent("%"); // URIError: URI malformed
} catch (e) {
  console.log(e instanceof URIError); // true
}

EvalError

EvalError 表示对 eval 函数的错误使用。在现代 JavaScript 中,这种错误类型几乎不再使用,但为了向后兼容仍然存在。

示例:

try {
  throw new EvalError("EvalError example");
} catch (e) {
  console.log(e instanceof EvalError); // true
}

AggregateError

AggregateError 是 ES2021 引入的一种新的错误类型,用于表示多个错误的集合,通常与 Promise.any 方法一起使用。

示例:

try {
  throw new AggregateError([
    new Error("Error 1"),
    new Error("Error 2")
  ], "Multiple errors occurred");
} catch (e) {
  console.log(e instanceof AggregateError); // true
  console.log(e.errors); // [Error: Error 1, Error: Error 2]
}

自定义错误类型

通过继承 Error 类,可以创建自定义错误类型,添加更多的上下文信息。

示例:

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError';
  }
}

function validateUser(user) {
  if (!user.name) {
    throw new ValidationError('Name is required');
  }
  if (!user.age) {
    throw new ValidationError('Age is required');
  }
  // 其他验证逻辑
}

try {
  validateUser({ name: 'Alice' });
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation Error:', error.message);
  } else {
    console.error('Unexpected Error:', error.message);
  }
}

// 输出:
// Validation Error: Age is required

Promise 错误处理

在异步编程中,Promise 提供了一种处理错误的机制。可以使用 .catch() 方法捕获 Promise 链中的错误。

示例:

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Failed to fetch data'));
    }, 2000);
  });
};

fetchData()
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    console.error('Caught an error:', error.message);
  });

// 输出:
// Caught an error: Failed to fetch data

async/await 错误处理

async 和 await 提供了一种更加直观的方式来处理异步代码,可以结合 try…catch 来捕获错误。

示例:

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('Failed to fetch data'));
    }, 2000);
  });
};

const processData = async () => {
  try {
    const data = await fetchData();
    console.log('Data:', data);
  } catch (error) {
    console.error('Caught an error:', error.message);
  }
};

processData();

// 输出:
// Caught an error: Failed to fetch data

全局错误处理

有时需要处理未捕获的全局错误,可以使用 window.onerror 或 unhandledrejection 事件来捕获全局错误和未处理的 Promise 拒绝。

全局错误捕获

window.onerror = (message, source, lineno, colno, error) => {
  console.error('Global error caught:', message);
  // 可以选择返回 true 来防止默认的浏览器错误提示
  return true;
};

// 触发一个未捕获的错误
someUndefinedFunction();

未处理的 Promise 拒绝

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
});

// 触发一个未处理的 Promise 拒绝
Promise.reject(new Error('Promise rejected'));

JavaScript 框架

热门的 JavaScript 框架和库遍布前端和后端开发领域,它们帮助开发者更加高效地构建和维护应用程序。以下是一些当前最流行且广泛使用的 JavaScript 框架和库:

前端框架和库

  • React
    • 概述: 由 Facebook 开发和维护,是一个用于构建用户界面的库,特别适用于单页应用(SPA)。
    • 特点: 组件化开发、虚拟 DOM、单向数据流。
    • 官网:React
  • js
    • 概述: 由尤雨溪(Evan You)开发的渐进式 JavaScript 框架,适用于构建用户界面。
    • 特点: 易于学习和使用、双向数据绑定、渐进式框架。
    • 官网:js
  • Angular
    • 概述: 由 Google 开发和维护的前端框架,适用于构建复杂和大型的单页应用。
    • 特点: 完整的框架、强类型支持(TypeScript)、依赖注入、内置路由和表单处理。
    • 官网:Angular
  • Svelte
    • 概述: 一个新兴的前端框架,通过编译时将组件转换为高效的命令式代码,减少了运行时开销。
    • 特点: 编译时框架、更少的开销和更高的性能、简洁的语法。
    • 官网:Svelte
  • js
    • 概述: 一个对开发者友好的框架,用于构建具有高度交互性的前端应用。
    • 特点: 强大的路由系统、内建最佳实践、双向数据绑定。
    • 官网:js

后端框架

  • js
    • 概述: 虽然不是一个框架,但js 是一个重要的 JavaScript 运行时,允许在服务器端运行 JavaScript,支持事件驱动和非阻塞 I/O。
    • 特点: 高并发性能、丰富的包生态系统(通过 npm)。
    • 官网:js
  • js
    • 概述: 一个轻量级的js Web 应用框架,简洁且灵活。
    • 特点: 简洁的 API、强大的路由机制、中间件支持。
    • 官网:Express
  • NestJS
    • 概述: 一个用于构建高效、可扩展的js 服务器端应用的框架,构建在 TypeScript 之上。
    • 特点: 模块化架构、依赖注入、内置支持微服务。
    • 官网:NestJS
  • Koa
    • 概述: 由 Express 的原班人马创建的一个新一代js 框架,致力于更小、更具表现力和更强大的基础。
    • 特点: 更简洁的 API、良好的错误处理机制、现代化设计。
    • 官网:Koa
  • js
    • 概述: 一个用于构建强大且可扩展的应用程序的js 框架。
    • 特点: 丰富的插件系统、内置验证和授权、配置驱动的声明式编码。
    • 官网:js

流行趋势和选择建议

选择哪个框架或库取决于你的项目需求和个人偏好:

  • React和 js 是前端开发中的主流选择,适合构建现代、响应式的用户界面。
  • Angular适合大型企业级应用开发,由于其完整的解决方案和强类型支持。
  • Svelte是一个新兴的选择,性能优秀且代码简洁,非常适合追求高效和现代开发的项目。
  • js和 NestJS 是后端开发的主流选择,前者轻量且灵活,后者则是一个更加全面和结构化的框架。
  • js是全栈开发中的重要技术,通过其丰富的包生态系统,可以快速构建和部署应用。

无论选择哪个框架或库,掌握其核心概念和生态系统中的工具都是非常重要的。希望这些信息能帮助你更好地选择适合自己的框架进行学习和实践。如果有任何问题,请随时向我提问!

参考链接:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注