JavaScript 学习笔记
本笔记面向JavaScript初学者,采用通俗易懂的语言解释核心概念,每个知识点都配有实用的代码示例。
目录
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等),建议查阅官方文档。