JavaScript基础篇

7 阅读19分钟

JavaScript 学习笔记

本笔记面向JavaScript初学者,采用通俗易懂的语言解释核心概念,每个知识点都配有实用的代码示例。


目录

  1. JavaScript基础
  2. 函数
  3. 对象与数组
  4. DOM操作
  5. 异步编程
  6. ES6+新特性
  7. 常见实用技巧

1. JavaScript基础

1.1 JavaScript简介与特点

JavaScript是一门运行在浏览器中的脚本语言,主要用于实现网页的交互效果。如今,它也能运行在服务器端(Node.js)。

JavaScript的特点:

  • 解释型语言:代码无需编译,直接由浏览器解释执行
  • 动态类型:变量的类型可以在运行时改变
  • 基于对象:既支持面向对象编程,也支持函数式编程
  • 跨平台:只要有浏览器,就能运行JavaScript

在HTML中使用JavaScript:

<!-- 方法1:内联脚本 -->
<script>
  console.log('Hello, JavaScript!');
</script>

<!-- 方法2:外部文件 -->
<script src="main.js"></script>

1.2 变量与数据类型

变量声明:var、let、const
// var:旧式声明,有作用域提升问题(了解即可,不推荐使用)
var oldName = "张三";

// let:块级作用域,声明后可以修改
let name = "李四";
name = "王五"; // 正确,可以重新赋值

// const:块级作用域,声明时必须赋值,之后不能重新赋值
const PI = 3.14159;
// PI = 3.14; // 错误!不能重新赋值

建议:优先使用 const,需要修改变量时用 let,尽量避免使用 var

基本数据类型

JavaScript有7种基本数据类型:

// 1. 字符串(String)
let name = "小明";
let greeting = '你好';

// 2. 数字(Number)
let age = 18;
let price = 19.99;

// 3. 布尔值(Boolean)
let isStudent = true;
let isAdult = false;

// 4. undefined:变量声明但未赋值
let uninitialized;
console.log(uninitialized); // undefined

// 5. null:空值,表示"这里什么都没有"
let empty = null;

// 6. Symbol:唯一标识符(ES6新增)
let sym = Symbol('id');

// 7. BigInt:大整数(ES2020新增)
let bigNumber = 9007199254740991n;
引用类型
// 对象(Object)
let person = {
  name: "小明",
  age: 18,
  isStudent: true
};

// 数组(Array)
let fruits = ["苹果", "香蕉", "橙子"];

// 函数(Function)
let sayHello = function() {
  console.log("你好!");
};
typeof 操作符

用于检测变量的数据类型:

console.log(typeof "hello");    // "string"
console.log(typeof 123);        // "number"
console.log(typeof true);       // "boolean"
console.log(typeof undefined);  // "undefined"
console.log(typeof null);       // "object" (历史bug)
console.log(typeof {});         // "object"
console.log(typeof []);         // "object" (数组也是object)
console.log(typeof function(){}); // "function"

1.3 运算符与表达式

算术运算符
let a = 10, b = 3;

console.log(a + b);   // 13 加法
console.log(a - b);   // 7  减法
console.log(a * b);   // 30 乘法
console.log(a / b);   // 3.333... 除法
console.log(a % b);   // 1  取余(模运算)
console.log(a ** b);  // 1000 幂运算
console.log(a++);     // 10 先返回再加1
console.log(++a);     // 12 先加1再返回
比较运算符
console.log(5 == "5");   // true (松散相等,只比较值)
console.log(5 === "5");  // false (严格相等,比较值和类型)
console.log(5 != "5");   // false
console.log(5 !== "5");  // true
console.log(3 > 2);      // true
console.log(3 >= 3);     // true

重要:尽量使用 ===!==,避免类型转换带来的意外结果。

逻辑运算符
// 与(AND):两边都为true才为true
console.log(true && true);   // true
console.log(true && false);  // false

// 或(OR):有一边为true就为true
console.log(true || false);  // true
console.log(false || false); // false

// 非(NOT)
console.log(!true);  // false

// 短路运算
let name = "" || "默认值";  // "默认值"
let age = 25 || 18;          // 25
条件运算符(三元运算符)
// 语法:条件 ? 值1 : 值2
let age = 20;
let status = age >= 18 ? "成年人" : "未成年";
console.log(status); // "成年人"

1.4 流程控制

条件语句

if...else 语句:

let score = 85;

if (score >= 90) {
  console.log("优秀");
} else if (score >= 80) {
  console.log("良好");
} else if (score >= 60) {
  console.log("及格");
} else {
  console.log("不及格");
}

switch 语句:

let fruit = "苹果";

switch (fruit) {
  case "苹果":
    console.log("红色的");
    break;  // 不要忘记break,否则会继续执行下一个case
  case "香蕉":
    console.log("黄色的");
    break;
  case "橙子":
    console.log("橙色的");
    break;
  default:
    console.log("未知水果");
}
循环语句

for 循环:

// 基本for循环
for (let i = 0; i < 5; i++) {
  console.log("第" + (i + 1) + "次循环");
}

// 遍历数组
let fruits = ["苹果", "香蕉", "橙子"];
for (let i = 0; i < fruits.length; i++) {
  console.log(fruits[i]);
}

while 循环:

let count = 0;
while (count < 3) {
  console.log("计数:" + count);
  count++;
}

do...while 循环:

// 先执行一次,再判断条件
let i = 0;
do {
  console.log(i);
  i++;
} while (i < 3);

for...of 循环(遍历数组):

let fruits = ["苹果", "香蕉", "橙子"];
for (let fruit of fruits) {
  console.log(fruit);
}

for...in 循环(遍历对象属性):

let person = { name: "小明", age: 18, city: "北京" };
for (let key in person) {
  console.log(key + ": " + person[key]);
}

循环控制:

// break:跳出整个循环
for (let i = 0; i < 10; i++) {
  if (i === 5) break;
  console.log(i);
}

// continue:跳过当前迭代
for (let i = 0; i < 5; i++) {
  if (i === 2) continue;
  console.log(i); // 0, 1, 3, 4
}

2. 函数

2.1 函数定义与调用

函数是一段可以重复使用的代码块。

函数声明(函数提升):

// 可以先调用后定义(函数提升)
greet("小明"); // 正常工作

function greet(name) {
  console.log("你好," + name + "!");
}

// 调用函数
greet("小红");
greet("小刚");

函数表达式:

// 函数作为值赋给变量
const greet = function(name) {
  console.log("你好," + name + "!");
};

greet("小明");

2.2 参数与返回值

基本参数:

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

let result = add(3, 5);
console.log(result); // 8

默认参数:

function greet(name = "朋友") {
  console.log("你好," + name + "!");
}

greet();          // 你好,朋友!
greet("小明");    // 你好,小明!

arguments 对象(传统方式):

function sum() {
  let total = 0;
  for (let i = 0; i < arguments.length; i++) {
    total += arguments[i];
  }
  return total;
}

console.log(sum(1, 2, 3, 4, 5)); // 15

剩余参数(现代方式):

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

console.log(sum(1, 2, 3, 4, 5)); // 15

2.3 箭头函数

箭头函数是ES6引入的简洁函数写法。

基本语法:

// 传统函数
const add = function(a, b) {
  return a + b;
};

// 箭头函数
const add = (a, b) => {
  return a + b;
};

// 简写形式(单行函数体)
const add = (a, b) => a + b;

// 单参数可以省略括号
const double = x => x * 2;

// 无参数
const sayHi = () => console.log("你好!");

箭头函数与this:

// 传统函数:this指向调用它的对象
const person = {
  name: "小明",
  traditionalFunc: function() {
    console.log(this.name); // "小明"
  }
};

// 箭头函数:没有自己的this,继承外层的this
const person2 = {
  name: "小红",
  arrowFunc: () => {
    console.log(this.name); // undefined(在全局环境下)
  }
};

2.4 作用域与闭包

作用域:

// 全局作用域
const globalVar = "我是全局变量";

function outer() {
  // 函数作用域
  const outerVar = "我是外层变量";
  
  function inner() {
    // 块级作用域
    const innerVar = "我是内层变量";
    console.log(innerVar);   // 可以访问
    console.log(outerVar);   // 可以访问
    console.log(globalVar);  // 可以访问
  }
  
  inner();
  // console.log(innerVar); // 错误!访问不到
}

闭包:

闭包是指函数能够记住并访问其词法作用域,即使函数是在其词法作用域之外执行的。

// 闭包示例:计数器
function createCounter() {
  let count = 0;  // 私有变量
  
  return function() {
    count++;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

// 每次调用createCounter都会创建一个新的闭包
const counter2 = createCounter();
console.log(counter2()); // 1

闭包的实际应用:

// 1. 数据私有化
function createUser(name) {
  let password = "123456";  // 私有变量,外部无法直接访问
  
  return {
    getName: () => name,
    changePassword: (oldPwd, newPwd) => {
      if (oldPwd === password) {
        password = newPwd;
        return true;
      }
      return false;
    }
  };
}

const user = createUser("小明");
console.log(user.getName());           // "小明"
console.log(user.changePassword("123456", "654321")); // true

// 2. 函数工厂
function multiply(factor) {
  return function(number) {
    return number * factor;
  };
}

const double = multiply(2);
const triple = multiply(3);
const quadruple = multiply(4);

console.log(double(5));   // 10
console.log(triple(5));   // 15
console.log(quadruple(5)); // 20

2.5 this关键字

this 的值取决于函数的调用方式:

// 1. 在普通函数中
function showThis() {
  console.log(this);
}

showThis(); // 在浏览器中指向 window 对象

// 2. 在对象方法中
const person = {
  name: "小明",
  sayHi: function() {
    console.log("你好,我是" + this.name);
  }
};

person.sayHi(); // this指向person对象

// 3. 在构造函数中
function Person(name) {
  this.name = name;
  this.sayHi = function() {
    console.log("你好,我是" + this.name);
  };
}

const p = new Person("小明");
p.sayHi(); // this指向新创建的对象

// 4. 箭头函数不绑定this
const obj = {
  name: "小明",
  // 箭头函数继承外层的this
  sayHi: () => {
    console.log("你好,我是" + this.name);
  }
};

obj.sayHi(); // this指向定义时的外层作用域(非obj)

改变this的指向:

const person1 = { name: "小明" };
const person2 = { name: "小红" };

function greet(age) {
  console.log("你好,我是" + this.name + ",今年" + age + "岁");
}

greet.call(person1, 18);   // 你好,我是小明,今年18岁
greet.apply(person2, [20]); // 你好,我是小红,今年20岁

const boundGreet = greet.bind(person1);
boundGreet(22);            // 你好,我是小明,今年22岁

3. 对象与数组

3.1 对象的创建与操作

创建对象:

// 字面量方式(最常用)
const person = {
  name: "小明",
  age: 18,
  isStudent: true,
  // 方法
  sayHi: function() {
    console.log("你好,我是" + this.name);
  }
};

// 构造函数方式
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const person2 = new Person("小红", 20);

// Object.create() 方式
const personProto = {
  greet: function() {
    console.log("你好");
  }
};
const person3 = Object.create(personProto);

访问对象属性:

const person = {
  name: "小明",
  age: 18
};

// 点符号
console.log(person.name); // "小明"

// 方括号
console.log(person["age"]); // 18

// 动态访问
const key = "name";
console.log(person[key]); // "小明"

修改和添加属性:

const person = { name: "小明" };

// 添加属性
person.age = 18;
person["city"] = "北京";

// 修改属性
person.name = "小红";

// 删除属性
delete person.city;

// 检查属性是否存在
console.log("name" in person);    // true
console.log(person.hasOwnProperty("age")); // true

对象方法:

const person = {
  name: "小明",
  age: 18,
  
  // 计算属性
  ["get" + "Info"]() {
    return `${this.name}, ${this.age}岁`;
  }
};

console.log(person.getInfo()); // "小明, 18岁"

常用对象方法:

const person = {
  name: "小明",
  age: 18,
  city: "北京"
};

// 获取所有键
console.log(Object.keys(person));   // ["name", "age", "city"]

// 获取所有值
console.log(Object.values(person)); // ["小明", 18, "北京"]

// 获取所有键值对
console.log(Object.entries(person)); // [["name", "小明"], ["age", 18], ["city", "北京"]]

// 遍历对象
for (let [key, value] of Object.entries(person)) {
  console.log(`${key}: ${value}`);
}

// 合并对象
const other = { hobby: "游泳", age: 20 };
const merged = Object.assign({}, person, other); // 后面的会覆盖前面的

// 对象展开运算符(更简洁)
const merged2 = { ...person, ...other };

3.2 数组的常用方法

创建数组:

const arr1 = [1, 2, 3];              // 字面量
const arr2 = new Array(1, 2, 3);     // 构造函数
const arr3 = Array(1, 2, 3);        // Array工厂方法
const arr4 = Array(3).fill(0);       // [0, 0, 0] 创建指定长度并填充

基本属性和方法:

const fruits = ["苹果", "香蕉", "橙子"];

console.log(fruits.length);           // 3
console.log(fruits[0]);                // "苹果"(索引从0开始)
console.log(fruits[fruits.length - 1]); // "橙子"(最后一个元素)

// 添加/删除元素
fruits.push("葡萄");      // 末尾添加,返回新长度
fruits.pop();            // 末尾删除,返回被删除的元素
fruits.unshift("草莓");  // 开头添加,返回新长度
fruits.shift();          // 开头删除,返回被删除的元素

遍历方法:

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

// forEach:遍历每个元素(无返回值)
numbers.forEach((num, index) => {
  console.log(`${index}: ${num}`);
});

// for...of:简洁遍历
for (let num of numbers) {
  console.log(num);
}

map - 映射(返回新数组):

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

// 每个元素乘以2
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 提取对象属性
const students = [
  { name: "小明", score: 85 },
  { name: "小红", score: 92 },
  { name: "小刚", score: 78 }
];

const names = students.map(s => s.name);
console.log(names); // ["小明", "小红", "小刚"]

filter - 过滤(返回新数组):

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

// 筛选偶数
const evens = numbers.filter(num => num % 2 === 0);
console.log(evens); // [2, 4, 6, 8, 10]

// 筛选及格的学生
const students = [
  { name: "小明", score: 85 },
  { name: "小红", score: 55 },
  { name: "小刚", score: 92 }
];

const passed = students.filter(s => s.score >= 60);
console.log(passed); // [{ name: "小明", score: 85 }, { name: "小刚", score: 92 }]

reduce - 汇总(返回单一值):

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

// 求和
const sum = numbers.reduce((total, num) => total + num, 0);
console.log(sum); // 15

// 求乘积
const product = numbers.reduce((total, num) => total * num, 1);
console.log(product); // 120

// 找出最大值
const max = numbers.reduce((a, b) => a > b ? a : b);
console.log(max); // 5

// 转换为对象
const items = ["apple", "banana", "apple", "orange", "banana", "apple"];
const count = items.reduce((obj, item) => {
  obj[item] = (obj[item] || 0) + 1;
  return obj;
}, {});
console.log(count); // { apple: 3, banana: 2, orange: 1 }

find/findIndex - 查找:

const students = [
  { name: "小明", score: 85 },
  { name: "小红", score: 92 },
  { name: "小刚", score: 78 }
];

// 找第一个满足条件的元素
const topStudent = students.find(s => s.score >= 90);
console.log(topStudent); // { name: "小红", score: 92 }

// 找第一个满足条件的索引
const index = students.findIndex(s => s.score >= 90);
console.log(index); // 1

some/every - 判断:

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

// some:是否有任一元素满足条件
const hasEven = numbers.some(n => n % 2 === 0);
console.log(hasEven); // true

// every:是否所有元素都满足条件
const allPositive = numbers.every(n => n > 0);
console.log(allPositive); // true

sort - 排序:

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

// 数字排序(必须提供比较函数)
const sorted = [...numbers].sort((a, b) => a - b);
console.log(sorted); // [1, 1, 2, 3, 4, 5, 6, 9]

// 降序
const reversed = [...numbers].sort((a, b) => b - a);

// 对象数组排序
const students = [
  { name: "小明", score: 85 },
  { name: "小红", score: 92 },
  { name: "小刚", score: 78 }
];

const sortedStudents = [...students].sort((a, b) => b.score - a.score);
console.log(sortedStudents);

slice/splice - 切片:

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

// slice:截取(不修改原数组)
const sliced = numbers.slice(1, 4);  // [2, 3, 4](不包含结束索引)
const copy = numbers.slice();        // 复制数组

// splice:插入/删除(修改原数组)
const arr = [1, 2, 3, 4, 5];
arr.splice(2, 1);           // 删除:从索引2开始删除1个 -> [1, 2, 4, 5]
arr.splice(2, 0, 99);       // 插入:在索引2插入99 -> [1, 2, 99, 4, 5]
arr.splice(2, 1, 88);       // 替换:删除1个并插入88 -> [1, 2, 88, 4, 5]

concat/join - 合并连接:

const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = arr1.concat(arr2);
console.log(combined); // [1, 2, 3, 4]

const words = ["Hello", "World", "!"];
console.log(words.join(" ")); // "Hello World !"

3.3 解构赋值

解构赋值让我们可以从数组或对象中提取值,赋给变量。

数组解构:

// 基本用法
const [a, b, c] = [1, 2, 3];
console.log(a, b, c); // 1 2 3

// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third); // 1 3

// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head); // 1
console.log(tail); // [2, 3, 4, 5]

// 默认值
const [x, y, z = 10] = [1, 2];
console.log(z); // 10

// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1

对象解构:

const person = { name: "小明", age: 18, city: "北京" };

// 基本用法
const { name, age } = person;
console.log(name, age); // "小明" 18

// 重命名
const { name: personName, age: personAge } = person;
console.log(personName, personAge); // "小明" 18

// 默认值
const { name, gender = "未知" } = person;
console.log(gender); // "未知"

// 嵌套解构
const user = {
  info: {
    address: {
      city: "北京"
    }
  }
};
const { info: { address: { city } } } = user;
console.log(city); // "北京"

函数参数解构:

// 对象参数解构
function greet({ name, age }) {
  console.log(`你好,我是${name},今年${age}岁`);
}

greet({ name: "小明", age: 18 });

// 带默认值
function createUser({ name, age = 18, city = "北京" }) {
  return { name, age, city };
}
console.log(createUser({ name: "小明" })); // { name: "小明", age: 18, city: "北京" }

3.4 展开运算符

展开运算符用 ... 表示,可以"展开"数组或对象。

数组展开:

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
const merged = [...arr1, ...arr2];
console.log(merged); // [1, 2, 3, 4, 5, 6]

// 复制数组(浅拷贝)
const copy = [...arr1];
console.log(copy); // [1, 2, 3]

// 在中间插入
const newArr = [1, 2, ...arr2, 7];
console.log(newArr); // [1, 2, 4, 5, 6, 7]

// 函数参数
const numbers = [1, 2, 3, 4, 5];
console.log(Math.max(...numbers)); // 5

// 字符串转数组
const str = "hello";
const chars = [...str];
console.log(chars); // ["h", "e", "l", "l", "o"]

对象展开:

const person = { name: "小明", age: 18 };
const student = { ...person, school: "北京大学", age: 20 }; // 后面的age会覆盖前面的
console.log(student); // { name: "小明", age: 20, school: "北京大学" }

// 复制对象
const copy = { ...person };

// 部分展开
const { name, ...rest } = person;
console.log(rest); // { age: 18 }

4. DOM操作

DOM(Document Object Model)是网页的编程接口,让我们可以用JavaScript操作网页内容。

4.1 DOM元素选择

选择单个元素:

// 通过ID选择(返回单个元素)
const header = document.getElementById("header");

// 通过CSS选择器选择(返回第一个匹配)
const button = document.querySelector(".btn-primary");
const navItem = document.querySelector("#nav .item");

选择多个元素:

// 通过CSS选择器选择所有匹配(返回NodeList)
const items = document.querySelectorAll(".list-item");

// 通过类名选择(返回HTMLCollection)
const cards = document.getElementsByClassName("card");

// 通过标签名选择
const paragraphs = document.getElementsByTagName("p");

// 遍历NodeList/HTMLCollection
items.forEach(item => console.log(item));
// 或者
for (let item of items) {
  console.log(item);
}

其他选择方式:

// 选择父元素
const parent = element.parentElement;
const parentNode = element.parentNode;

// 选择子元素
const children = element.children;
const firstChild = element.firstElementChild;
const lastChild = element.lastElementChild;

// 选择兄弟元素
const next = element.nextElementSibling;
const prev = element.previousElementSibling;

4.2 事件处理

添加事件监听:

const button = document.querySelector("#myButton");

// 推荐方式:addEventListener
button.addEventListener("click", function(event) {
  console.log("按钮被点击了!");
});

// 箭头函数写法
button.addEventListener("click", (event) => {
  console.log("按钮被点击了!");
});

// 使用命名函数
button.addEventListener("click", handleClick);

function handleClick(event) {
  console.log("按钮被点击了!");
  console.log("事件类型:", event.type);
  console.log("触发元素:", event.target);
}

// 事件捕获和冒泡
element.addEventListener("click", handler, false); // 冒泡(默认)
element.addEventListener("click", handler, true);  // 捕获

常见事件类型:

// 鼠标事件
element.addEventListener("click", handler);      // 点击
element.addEventListener("dblclick", handler);   // 双击
element.addEventListener("mouseenter", handler); // 鼠标进入
element.addEventListener("mouseleave", handler); // 鼠标离开
element.addEventListener("mousemove", handler); // 鼠标移动

// 键盘事件
element.addEventListener("keydown", handler);   // 键盘按下
element.addEventListener("keyup", handler);     // 键盘释放
element.addEventListener("keypress", handler);   // 键盘按住

// 表单事件
input.addEventListener("input", handler);       // 输入时触发
input.addEventListener("change", handler);      // 值改变且失去焦点
input.addEventListener("focus", handler);       // 获得焦点
input.addEventListener("blur", handler);        // 失去焦点
form.addEventListener("submit", handler);       // 表单提交

// 页面事件
window.addEventListener("load", handler);        // 页面加载完成
window.addEventListener("scroll", handler);     // 滚动
window.addEventListener("resize", handler);     // 窗口大小改变

事件对象详解:

button.addEventListener("click", function(event) {
  // event.target - 触发事件的元素
  // event.currentTarget - 绑定事件的元素
  // event.type - 事件类型
  // event.clientX/clientY - 鼠标相对于视口的位置
  // event.preventDefault() - 阻止默认行为
  // event.stopPropagation() - 阻止冒泡
});

事件委托:

利用事件冒泡,在一个父元素上处理多个子元素的事件:

// 不好的做法:为每个按钮单独绑定事件
document.querySelectorAll(".delete-btn").forEach(btn => {
  btn.addEventListener("click", handleDelete);
});

// 好的做法:事件委托
document.querySelector(".list").addEventListener("click", function(event) {
  // 检查点击的是否是删除按钮
  if (event.target.classList.contains("delete-btn")) {
    handleDelete(event.target);
  }
});

移除事件监听:

function handleClick() {
  console.log("点击了!");
}

button.addEventListener("click", handleClick);
// 移除时需要使用相同的函数引用
button.removeEventListener("click", handleClick);

4.3 DOM增删改

创建元素:

// 创建新元素
const newDiv = document.createElement("div");
newDiv.textContent = "我是新元素";
newDiv.className = "new-class";

// 创建带子元素的复杂结构
const card = document.createElement("div");
card.className = "card";
card.innerHTML = `
  <h3>标题</h3>
  <p>内容</p>
`;

插入元素:

const container = document.querySelector(".container");

// 追加到父元素末尾
container.appendChild(newDiv);

// 插入到指定位置
container.insertBefore(newDiv, container.firstChild);

// 插入到元素开头
container.prepend(newDiv);

// 插入到元素末尾
container.append(newDiv);

// 在元素之前/之后插入
container.before(newDiv);
container.after(newDiv);

// 插入HTML(可识别标签)
container.insertAdjacentHTML("beforeend", "<p>新段落</p>");
// position: 'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend'

删除元素:

// 删除自身
element.remove();

// 删除子元素
parent.removeChild(child);

// 清空所有子元素
container.innerHTML = "";  // 方式1
while (container.firstChild) {
  container.removeChild(container.firstChild); // 方式2
}

修改元素内容:

const element = document.querySelector(".content");

// 修改文本内容
element.textContent = "新的文本内容";  // 只处理纯文本,更安全

// 修改HTML内容(谨慎使用,可能有XSS风险)
element.innerHTML = "<strong>加粗文本</strong>";

// 读取值
console.log(element.textContent);
console.log(element.innerHTML);

修改元素属性:

const link = document.querySelector("a");

// 设置属性
link.setAttribute("href", "https://example.com");
link.setAttribute("target", "_blank");

// 获取属性
console.log(link.getAttribute("href"));

// 检查属性
console.log(link.hasAttribute("target"));

// 移除属性
link.removeAttribute("target");

// 直接操作属性
link.href = "https://example.com";
link.target = "_blank";

修改元素样式:

const box = document.querySelector(".box");

// 行内样式
box.style.color = "red";
box.style.backgroundColor = "blue";
box.style.fontSize = "16px";

// 注意:CSS属性用驼峰命名
// background-color -> backgroundColor
// font-size -> fontSize

// 获取计算后的样式
const styles = window.getComputedStyle(box);
console.log(styles.color);

// 操作class
box.classList.add("active");
box.classList.remove("hidden");
box.classList.toggle("active");  // 切换
box.classList.contains("active"); // 检查

修改元素类名(className):

// 获取整个class字符串
console.log(element.className);

// 设置class(会覆盖原有class)
element.className = "new-class";

// 建议使用classList
element.classList.add("class1", "class2");
element.classList.remove("class1");
element.classList.toggle("class1");

5. 异步编程

JavaScript是单线程的,但可以通过异步编程处理耗时的操作(如网络请求),避免阻塞。

5.1 回调函数

回调函数是异步编程的基础形式。

// 同步回调
const numbers = [1, 2, 3];
numbers.forEach(num => console.log(num)); // 同步执行

// 异步回调:模拟网络请求
function fetchData(callback) {
  setTimeout(() => {
    callback("数据加载完成");
  }, 1000);
}

console.log("开始加载...");
fetchData(result => {
  console.log(result);
});
console.log("后续代码...");

// 输出顺序:
// 开始加载...
// 后续代码...
// (1秒后)数据加载完成

回调地狱:

// 多个异步操作嵌套导致的"回调地狱"
fetchData(data => {
  processData(data, processed => {
    saveData(processed, saved => {
      console.log("完成!");
    });
  });
});
// 代码难以阅读和维护

5.2 Promise

Promise是ES6引入的异步编程解决方案,比回调更清晰。

创建Promise:

const myPromise = new Promise((resolve, reject) => {
  // 模拟异步操作
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve("操作成功!");  // 成功时调用
    } else {
      reject("操作失败!");   // 失败时调用
    }
  }, 1000);
});

使用Promise:

myPromise
  .then(result => {
    console.log(result);
    return "下一步数据";
  })
  .then(nextResult => {
    console.log(nextResult);
  })
  .catch(error => {
    console.error(error);
  })
  .finally(() => {
    console.log("无论成功失败都会执行");
  });

Promise链式调用:

// 避免回调地狱
fetchData()
  .then(data => processData(data))
  .then(processed => saveData(processed))
  .then(saved => console.log("完成!"))
  .catch(error => console.error(error));

Promise静态方法:

// Promise.all - 所有都成功才成功
Promise.all([
  fetchUser(),
  fetchPosts(),
  fetchComments()
]).then(([user, posts, comments]) => {
  console.log(user, posts, comments);
});

// Promise.race - 任意一个先完成
Promise.race([
  fetchFast(),
  fetchSlow()
]).then(result => console.log(result));

// Promise.allSettled - 所有完成,不论成功失败
Promise.allSettled([
  fetchSuccess(),
  fetchFail()
]).then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log(result.value);
    } else {
      console.log(result.reason);
    }
  });
});

// Promise.any - 任意一个成功就成功
Promise.any([
  fetchPrimary(),
  fetchSecondary()
]).then(result => console.log(result));

将回调转换为Promise:

// 使用 Promise 包装回调函数
function fetchData() {
  return new Promise((resolve, reject) => {
    someAsyncOperation((error, result) => {
      if (error) {
        reject(error);
      } else {
        resolve(result);
      }
    });
  });
}

// Node.js 风格回调的 promisify
const fs = require('fs');
const { promisify } = require('util');
const readFile = promisify(fs.readFile);

readFile('test.txt', 'utf8')
  .then(content => console.log(content));

5.3 async/await

async/await是ES2017引入的语法糖,让异步代码看起来像同步代码。

基本用法:

// async 函数:自动返回 Promise
async function fetchUser() {
  return "用户数据";
}

// 等同于
function fetchUser() {
  return Promise.resolve("用户数据");
}

// await:等待 Promise 完成(只能在 async 函数中使用)
async function loadData() {
  const result = await fetchData();
  console.log(result);
}

错误处理:

async function loadData() {
  try {
    const data = await fetchData();
    console.log(data);
  } catch (error) {
    console.error("出错了:", error);
  }
}

// 配合 Promise.all 使用
async function loadAll() {
  try {
    const [user, posts] = await Promise.all([
      fetchUser(),
      fetchPosts()
    ]);
    console.log(user, posts);
  } catch (error) {
    console.error("部分数据加载失败");
  }
}

完整示例:

// 模拟API请求
function apiRequest(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (url.includes("success")) {
        resolve({ success: true, data: `来自${url}的数据` });
      } else {
        reject({ success: false, error: "请求失败" });
      }
    }, 1000);
  });
}

async function fetchAllData() {
  try {
    console.log("开始加载...");
    
    const userData = await apiRequest("/api/user");
    console.log("用户数据:", userData);
    
    const postData = await apiRequest("/api/posts");
    console.log("帖子数据:", postData);
    
    console.log("全部加载完成!");
  } catch (error) {
    console.error("加载失败:", error);
  }
}

fetchAllData();

async 函数返回值:

async function getNumber() {
  return 42;
}

getNumber().then(console.log); // 42

// await 总是返回 Promise 的 resolved 值
async function getValue() {
  const result = await Promise.resolve("hello");
  return result; // 实际返回 Promise.resolve("hello")
}

6. ES6+新特性

6.1 let与const

见1.2节变量声明部分。

6.2 模板字符串

模板字符串使用反引号(`)定义,支持多行和嵌入表达式。

// 基本用法
const name = "小明";
const greeting = `你好,${name}!`;
console.log(greeting); // "你好,小明!"

// 多行字符串
const html = `
  <div class="container">
    <h1>标题</h1>
    <p>这是一个段落</p>
  </div>
`;

// 嵌入表达式
const a = 5, b = 10;
console.log(`加起来等于 ${a + b}`); // "加起来等于 15"
console.log(`是偶数吗? ${a % 2 === 0 ? "是" : "否"}`); // "是偶数吗? 否"

// 嵌套模板
const data = { name: "小明" };
const status = "在线";
const message = `
  用户信息:
  ${`
    名称:${data.name}
    状态:${status}
  `}
`;

6.3 类与继承

ES6引入了class语法,让面向对象编程更直观。

类的定义:

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

  // 实例方法
  greet() {
    console.log(`你好,我是${this.name}`);
  }

  // 静态方法(类本身的方法)
  static create(name, age) {
    return new Person(name, age);
  }

  // getter/setter
  get info() {
    return `${this.name}, ${this.age}岁`;
  }

  set info(value) {
    const [name, age] = value.split(",");
    this.name = name;
    this.age = parseInt(age);
  }
}

const person = new Person("小明", 18);
person.greet(); // "你好,我是小明"
console.log(person.info); // "小明, 18岁"

类的继承:

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

  // 重写父类方法
  greet() {
    super.greet();  // 调用父类方法
    console.log(`我在${this.school}上学`);
  }

  // 新方法
  study(subject) {
    console.log(`${this.name}正在学习${subject}`);
  }
}

const student = new Student("小红", 16, "北京大学");
student.greet();
// "你好,我是小红"
// "我在北京大学上学"
student.study("数学"); // "小红正在学习数学"

私有字段(ES2022):

class Counter {
  #count = 0;  // 私有字段,外部无法访问

  increment() {
    this.#count++;
  }

  getCount() {
    return this.#count;
  }
}

const counter = new Counter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
// console.log(counter.#count); // 语法错误!

6.4 模块化

ES6模块化允许将代码拆分成多个文件,便于维护和复用。

导出(export):

// 方式1:命名导出(可以导出多个)
export const name = "小明";
export function sayHi() {
  console.log("你好!");
}
export class Person {}

// 方式2:集中导出
const age = 18;
const city = "北京";
export { age, city };

// 默认导出(每个模块只能有一个)
export default function() {
  console.log("默认导出");
}

导入(import):

// 导入命名导出
import { name, sayHi } from './module.js';
import { name as userName } from './module.js'; // 重命名

// 导入所有
import * as module from './module.js';
console.log(module.name);

// 导入默认导出
import myFunction from './module.js';

// 复合导入
import defaultExport, { namedExport } from './module.js';

实际项目结构示例:

// utils.js
export function add(a, b) {
  return a + b;
}

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

export default function formatNumber(num) {
  return num.toLocaleString();
}

// main.js
import formatNumber, { add, multiply } from './utils.js';

console.log(formatNumber(add(1000, 2000))); // "3,000"
console.log(multiply(6, 7)); // 42

7. 常见实用技巧

7.1 数组去重

方法1:Set(推荐):

const arr = [1, 2, 2, 3, 3, 3, 4, 5, 5];
const unique = [...new Set(arr)];
console.log(unique); // [1, 2, 3, 4, 5]

方法2:filter:

const arr = [1, 2, 2, 3, 3, 3, 4, 5, 5];
const unique = arr.filter((item, index) => arr.indexOf(item) === index);

方法3:reduce:

const arr = [1, 2, 2, 3, 3, 3, 4, 5, 5];
const unique = arr.reduce((acc, cur) => {
  if (!acc.includes(cur)) {
    acc.push(cur);
  }
  return acc;
}, []);

对象数组去重:

const arr = [
  { id: 1, name: "小明" },
  { id: 2, name: "小红" },
  { id: 1, name: "小明" }
];

const unique = arr.filter((item, index, self) => 
  index === self.findIndex(t => t.id === item.id)
);
// [{ id: 1, name: "小明" }, { id: 2, name: "小红" }]

7.2 深拷贝

JSON方法(简单有效,但有局限):

const original = { 
  name: "小明", 
  hobbies: ["读书", "运动"],
  address: { city: "北京" }
};

// 局限性:无法拷贝函数、undefined、Symbol、正则等
const copy = JSON.parse(JSON.stringify(original));
copy.name = "小红";
console.log(original.name); // "小明"(不变)

手写深拷贝(完整版):

function deepClone(obj, hash = new WeakMap()) {
  // 处理 null 和非对象类型
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 处理循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  // 处理 Date
  if (obj instanceof Date) {
    return new Date(obj);
  }

  // 处理 RegExp
  if (obj instanceof RegExp) {
    return new RegExp(obj);
  }

  // 处理数组和对象
  const clone = Array.isArray(obj) ? [] : {};
  hash.set(obj, clone);

  for (let key of Object.keys(obj)) {
    clone[key] = deepClone(obj[key], hash);
  }

  return clone;
}

const original = {
  name: "小明",
  date: new Date(),
  regex: /test/g,
  hobbies: ["读书", "运动"],
  nested: { address: { city: "北京" } }
};

const copy = deepClone(original);
copy.nested.address.city = "上海";
console.log(original.nested.address.city); // "北京"(不变)

structuredClone(现代浏览器原生支持):

const original = { 
  name: "小明", 
  hobbies: ["读书", "运动"],
  date: new Date()
};
const copy = structuredClone(original);

7.3 防抖与节流

防抖(Debounce):函数在触发后等待一段时间,如期间再次触发则重新计时。

function debounce(func, delay) {
  let timer = null;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

// 使用示例:搜索框输入
const handleSearch = debounce((query) => {
  console.log("搜索:" + query);
}, 500);

input.addEventListener("input", (e) => {
  handleSearch(e.target.value);
});

节流(Throttle):函数在一定时间内只执行一次。

function throttle(func, interval) {
  let lastTime = 0;
  return function(...args) {
    const now = Date.now();
    if (now - lastTime >= interval) {
      lastTime = now;
      func.apply(this, args);
    }
  };
}

// 使用示例:滚动监听
const handleScroll = throttle(() => {
  console.log("滚动位置:" + window.scrollY);
}, 200);

window.addEventListener("scroll", handleScroll);

7.4 常用数组技巧

// 判断是否为空数组
const isEmpty = arr => Array.isArray(arr) && arr.length === 0;
console.log(isEmpty([])); // true

// 打平嵌套数组
const flat = arr => arr.flat(Infinity);
console.log(flat([1, [2, [3, [4]]]])); // [1, 2, 3, 4]

// 获取数组随机元素
const randomItem = arr => arr[Math.floor(Math.random() * arr.length)];

// 生成数字数组
const range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
console.log(range(1, 5)); // [1, 2, 3, 4, 5]

// 求平均值
const average = arr => arr.reduce((a, b) => a + b, 0) / arr.length;

// 移除假值(false, null, 0, "", undefined, NaN)
const removeFalsy = arr => arr.filter(Boolean);
console.log(removeFalsy([0, 1, false, 2, "", 3, null, NaN])); // [1, 2, 3]

7.5 错误处理

try...catch:

try {
  // 可能出错的代码
  const data = JSON.parse(userInput);
  console.log(data);
} catch (error) {
  // 处理错误
  console.error("解析失败:", error.message);
} finally {
  // 无论成功失败都会执行
  console.log("清理工作");
}

自定义错误:

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

function validateAge(age) {
  if (typeof age !== "number") {
    throw new ValidationError("年龄必须是数字", "age");
  }
  if (age < 0 || age > 150) {
    throw new ValidationError("年龄必须在0-150之间", "age");
  }
}

try {
  validateAge("abc");
} catch (error) {
  if (error instanceof ValidationError) {
    console.error(`验证失败 [${error.field}]: ${error.message}`);
  } else {
    console.error("未知错误");
  }
}

异步错误处理:

// Promise 错误处理
fetchData()
  .then(data => console.log(data))
  .catch(error => console.error(error));

// async/await 错误处理
async function loadData() {
  try {
    const data = await fetchData();
    return data;
  } catch (error) {
    console.error("加载失败:", error);
    return null;
  }
}

7.6 性能优化建议

避免全局变量:

// 不推荐
function process() {
  result = calculate(); // 创建全局变量
}

// 推荐
function process() {
  const result = calculate();
  return result;
}

使用事件委托:

// 不推荐:为每个元素绑定事件
items.forEach(item => {
  item.addEventListener("click", handleClick);
});

// 推荐:事件委托
list.addEventListener("click", (e) => {
  if (e.target.matches(".item")) {
    handleClick(e.target);
  }
});

减少DOM操作:

// 不推荐:频繁操作DOM
for (let i = 0; i < 100; i++) {
  const div = document.createElement("div");
  div.textContent = i;
  container.appendChild(div);
}

// 推荐:DocumentFragment 或 拼接字符串
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const div = document.createElement("div");
  div.textContent = i;
  fragment.appendChild(div);
}
container.appendChild(fragment);

使用节流防抖优化事件处理:

// 不推荐:每次滚动都执行
window.addEventListener("scroll", handleScroll);

// 推荐:节流处理
window.addEventListener("scroll", throttle(handleScroll, 100));

优先使用ID选择器:

// 最快:ID选择器
const element = document.getElementById("myId");

// 较快:querySelector(CSS选择器)
const element = document.querySelector("#myId");

// 较慢:querySelectorAll
const elements = document.querySelectorAll(".myClass");

附录

JavaScript 关键字速查

关键字说明
var/let/const变量声明
function函数声明
return返回值
if/else条件语句
for/while循环语句
class类声明
extends继承
import/export模块导入导出
async/await异步语法
try/catch/finally错误处理
new创建对象实例
this当前执行上下文
typeof获取数据类型
instanceof检查对象类型

常用全局函数

// 类型转换
Number("123")      // 转为数字
String(123)        // 转为字符串
Boolean(1)         // 转为布尔值
parseInt("123")    // 解析为整数
parseFloat("3.14") // 解析为浮点数

// JSON
JSON.stringify(obj)  // 对象转JSON字符串
JSON.parse(str)      // JSON字符串转对象

// 编码解码
encodeURIComponent(str)  // URL编码
decodeURIComponent(str)  // URL解码

// 定时器
setTimeout(fn, ms)      // 延迟执行
setInterval(fn, ms)    // 定时执行
clearTimeout(timer)    // 清除延迟
clearInterval(timer)   // 清除定时

// 控制台
console.log()     // 输出日志
console.error()   // 输出错误
console.warn()    // 输出警告
console.table()   // 表格形式输出

📝 笔记说明:本笔记覆盖了JavaScript的核心知识点,适合作为学习参考。如需深入某个特定领域(如ES2023+新特性、TypeScript等),建议查阅官方文档。