_、$ 组成,不能以数字开头;区分大小写;不能使用关键字 / 保留字(如 let、function、class)。userName、getUserData)。MAX_SIZE、PI)。Person、Student)。_(_privateMethod,仅约定,非真正私有)。// 全局严格模式(文件开头)
"use strict";
// 函数内严格模式(局部)
function fn() {
"use strict";
// 严格模式下的规则:
// 1. 变量必须声明后使用(未声明赋值会报错,非严格模式会创建全局变量)
// 2. 禁止删除变量/函数(delete a 报错)
// 3. this 不能指向全局对象(全局作用域 this 为 undefined)
// 4. 禁止重复参数(function(a,a){} 报错)
}
// 1. var 提升:声明提升,赋值不提升
console.log(a); // undefined(提升了声明,未提升赋值)
var a = 10;
// 等价于:
var a;
console.log(a);
a = 10;
// 2. 函数声明提升:整个函数(声明+实现)都提升
console.log(fn1()); // "hello"
function fn1() {
return "hello";
}
// 3. 函数表达式(let/const)无提升,存在「暂时性死区(TDZ)」
console.log(fn2); // 报错:Cannot access 'fn2' before initialization
let fn2 = function() {
return "world";
};
| 特性 | 基本类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈内存(Stack) | 堆内存(Heap)+ 栈存引用 |
| 赋值方式 | 值拷贝(独立) | 引用拷贝(指向同一内存) |
| 比较方式 | 比较值 | 比较引用(地址) |
| 可变性 | 不可变(重新赋值≠修改) | 可变(可修改属性 / 元素) |
// 基本类型:值拷贝,互不影响
let a = 10;
let b = a;
b = 20;
console.log(a); // 10(a 不受 b 影响)
// 引用类型:引用拷贝,修改一个影响另一个
let obj1 = { name: "JS" };
let obj2 = obj1;
obj2.name = "JavaScript";
console.log(obj1.name); // JavaScript(指向同一堆内存)
// 基本类型不可变:看似修改,实际是创建新值
let str = "hello";
str.toUpperCase(); // 返回 "HELLO",但原 str 不变
console.log(str); // hello
str = str.toUpperCase(); // 重新赋值(创建新字符串)
console.log(str); // HELLO
// NaN 是 Number 类型,且不等于任何值(包括自身)
console.log(typeof NaN); // number
console.log(NaN === NaN); // false
// 正确判断 NaN 的方式
console.log(Number.isNaN(NaN)); // true
console.log(Number.isNaN("123")); // false(仅判断是否为 NaN)
console.log(isNaN("123")); // false(先转数字:123→非NaN)
console.log(isNaN("abc")); // true(先转数字:NaN→是NaN)
let a = 10;
a += 5; // 等价于 a = a + 5 → 15
a -= 3; // 等价于 a = a - 3 → 12
a *= 2; // 等价于 a = a * 2 → 24
a /= 4; // 等价于 a = a / 4 → 6
a %= 2; // 等价于 a = a % 2 → 0
// 1. &&:第一个为假返回第一个,否则返回第二个
let res1 = 0 && 10; // 0(假值)
let res2 = "hello" && "world"; // "world"(真值)
// 实战:仅当条件为真时执行函数
let isLogin = true;
isLogin && getUserInfo(); // 等价于 if (isLogin) { getUserInfo() }
// 2. ||:第一个为真返回第一个,否则返回第二个
let res3 = 10 || 20; // 10
let res4 = "" || "默认值"; // "默认值"
// 实战:设置默认值(ES6 前的写法)
function fn(name) {
name = name || "游客";
console.log(name);
}
fn(); // 游客
fn("小明"); // 小明
// 3. ??(空值合并运算符,ES2020):仅当值为 null/undefined 时返回默认值
let res5 = 0 ?? 10; // 0(区别于 ||,0 不触发默认值)
let res6 = undefined ?? 10; // 10
// 实战:避免 0/""/false 被误判为假值
let count = 0;
let showCount = count ?? "暂无数据"; // 0(而非 "暂无数据")
let user = {
name: "小明",
address: {
city: "北京"
}
};
// 传统写法(需层层判断)
let city1 = user && user.address && user.address.city; // 北京
// 可选链写法(简洁)
let city2 = user?.address?.city; // 北京
let street = user?.address?.street; // undefined(不报错)
// 可选链 + 函数调用
let fn = user?.getInfo?.(); // 若 getInfo 不存在,返回 undefined
// 可选链 + 数组
let arr = null;
let first = arr?.[0]; // undefined(不报错)
// 1. break:跳出当前循环/switch
for (let i = 0; i < 5; i++) {
if (i === 3) break; // 当i=3时跳出循环
console.log(i); // 0 1 2
}
// 2. continue:跳过当前循环,进入下一次
for (let i = 0; i < 5; i++) {
if (i === 3) continue; // 跳过i=3
console.log(i); // 0 1 2 4
}
// 3. 标签语句:跳出多层循环(实战少用,可读性差)
outer: for (let i = 0; i < 3; i++) {
inner: for (let j = 0; j < 3; j++) {
if (i === 1 && j === 1) break outer; // 直接跳出outer循环
console.log(`i=${i}, j=${j}`);
}
}
// 1. 三元运算符(简单条件)
let score = 85;
let level = score >= 90 ? "优秀" : score >= 80 ? "良好" : "及格";
console.log(level); // 良好
// 2. 逻辑判断简化
let isAdult = age >= 18;
if (isAdult) { /* 逻辑 */ }
// 3. 空值判断简化
if (obj && obj.name) { /* 避免 obj 为 null/undefined */ }
// 等价于
if (obj?.name) { /* 可选链 */ }
window 上的变量)。let/const 声明的变量,仅在 {} 内可访问(if/for/while 等块)。ReferenceError)。// 全局作用域
let a = 10;
function fn1() {
// 函数作用域
let b = 20;
function fn2() {
// 函数作用域
let c = 30;
console.log(a + b + c); // 60(向上查找 a、b)
}
fn2();
// console.log(c); // 报错:c is not defined(作用域链不向下)
}
fn1();
function outer() {
let count = 0; // 外部函数变量,本应执行后销毁
// 内部函数引用外部变量,形成闭包
return function inner() {
count++;
console.log(count);
};
}
let fn = outer();
fn(); // 1(count 未销毁)
fn(); // 2(count 持续累加)
fn(); // 3
function createPerson(name) {
let age = 18; // 私有变量,外部无法直接访问
return {
getName: () => name,
getAge: () => age,
setAge: (newAge) => {
if (newAge >= 0) age = newAge; // 可控的修改
}
};
}
let person = createPerson("小明");
console.log(person.name); // undefined(无法直接访问)
console.log(person.getName()); // 小明
console.log(person.getAge()); // 18
person.setAge(20);
console.log(person.getAge()); // 20
fn = null)。this 是函数执行时的上下文,指向调用函数的对象,不同场景指向不同:| 执行场景 | this 指向 | 示例 |
|---|---|---|
| 全局作用域 / 普通函数调用 | 浏览器:window;Node.js:global;严格模式:undefined | function fn() { console.log(this) } |
| 对象方法调用 | 该对象 | obj.fn() → this 指向 obj |
| 构造函数(new) | 新创建的实例对象 | new Person() → this 指向实例 |
| 箭头函数 | 定义时的外层作用域 this(无自身 this) | () => { console.log(this) } |
| 事件绑定 | 触发事件的 DOM 元素 | btn.addEventListener('click', fn) |
| call/apply/bind | 手动指定的对象 | fn.call(obj) → this 指向 obj |
// 1. 全局/普通函数
function fn1() {
console.log(this); // 浏览器:window
}
fn1();
// 2. 对象方法
let obj = {
name: "JS",
fn2: function() {
console.log(this.name); // JS(this 指向 obj)
}
};
obj.fn2();
// 3. 构造函数
function Person(name) {
this.name = name; // this 指向实例
}
let p = new Person("小明");
console.log(p.name); // 小明
// 4. 箭头函数(无自身 this)
let obj2 = {
name: "JS",
fn3: () => {
console.log(this.name); // undefined(this 指向全局)
},
fn4: function() {
let inner = () => {
console.log(this.name); // JS(this 指向 obj2)
};
inner();
}
};
obj2.fn3();
obj2.fn4();
// 5. 手动绑定 this(call/apply/bind)
function fn5(a, b) {
console.log(this.name, a + b);
}
let obj3 = { name: "绑定对象" };
fn5.call(obj3, 1, 2); // 绑定对象 3(立即执行,参数逐个传)
fn5.apply(obj3, [1, 2]); // 绑定对象 3(立即执行,参数数组传)
let boundFn = fn5.bind(obj3, 1, 2); // 不立即执行,返回新函数
boundFn(); // 绑定对象 3
// 1. 数字排序(默认按字符串排序,需传回调)
let arr = [10, 2, 25, 5];
// 升序
arr.sort((a, b) => a - b);
console.log(arr); // [2,5,10,25]
// 降序
arr.sort((a, b) => b - a);
console.log(arr); // [25,10,5,2]
// 2. 对象数组排序
let users = [
{ name: "小明", age: 20 },
{ name: "小红", age: 18 },
{ name: "小李", age: 22 }
];
// 按年龄升序
users.sort((a, b) => a.age - b.age);
console.log(users); // 小红、小明、小李
// 1. 多层数组扁平化(ES6+ flat)
let arr = [1, [2, [3, [4]]]];
// flat(Infinity):扁平化所有层级
let flatArr = arr.flat(Infinity);
console.log(flatArr); // [1,2,3,4]
// 2. 手动实现扁平化(递归)
function flatten(arr) {
let result = [];
arr.forEach(item => {
if (Array.isArray(item)) {
result = result.concat(flatten(item)); // 递归
} else {
result.push(item);
}
});
return result;
}
console.log(flatten(arr)); // [1,2,3,4]
// 1. Set 去重(ES6+,简洁)
let arr = [1, 2, 2, 3, 3, 3];
let uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1,2,3]
// 2. 手动去重(兼容性好)
function unique(arr) {
let result = [];
arr.forEach(item => {
if (!result.includes(item)) {
result.push(item);
}
});
return result;
}
console.log(unique(arr)); // [1,2,3]
// 方法1:Object.assign
let obj1 = { a: 1, b: { c: 2 } };
let obj2 = Object.assign({}, obj1);
obj2.b.c = 3;
console.log(obj1.b.c); // 3(深层仍共享)
// 方法2:扩展运算符
let obj3 = { ...obj1 };
obj3.b.c = 4;
console.log(obj1.b.c); // 4
// 方法1:JSON.parse(JSON.stringify())(简单场景,有局限)
let obj4 = { a: 1, b: { c: 2 }, fn: () => {} };
let obj5 = JSON.parse(JSON.stringify(obj4));
console.log(obj5.fn); // undefined(无法拷贝函数、Symbol、循环引用)
// 方法2:手动递归深拷贝(通用)
function deepClone(obj) {
if (obj === null || typeof obj !== "object") return obj; // 非对象直接返回
if (obj instanceof Date) return new Date(obj); // 处理日期
if (obj instanceof Array) return obj.map(item => deepClone(item)); // 处理数组
// 处理普通对象
let cloneObj = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) { // 仅拷贝自身属性(排除原型链)
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
let obj6 = { a: 1, b: { c: 2 }, fn: () => console.log("hello") };
let obj7 = deepClone(obj6);
obj7.b.c = 3;
console.log(obj6.b.c); // 2(互不影响)
console.log(obj7.fn()); // hello(函数拷贝成功)
prototype。__proto__ 向上查找,直到 Object.prototype(顶层),若仍找不到则返回 undefined。proto
proto
proto
prototype
proto
proto
实例 p
Person.prototype
Object.prototype
null
Person 函数
Function.prototype
proto
proto
proto
prototype
proto
proto
实例 p
Person.prototype
Object.prototype
null
Person 函数
Function.prototype
// 构造函数
function Person(name) {
this.name = name;
}
// 原型上添加共享方法
Person.prototype.sayHi = function() {
console.log(`你好,${this.name}`);
};
// 创建实例
let p = new Person("小明");
console.log(p.name); // 小明(自身属性)
p.sayHi(); // 你好,小明(原型链查找 sayHi)
// 验证原型链
console.log(p.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
// 继承:通过原型链实现
function Student(name, score) {
Person.call(this, name); // 继承属性
this.score = score;
}
// 继承方法
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student; // 修复构造函数指向
Student.prototype.showScore = function() {
console.log(`分数:${this.score}`);
};
let stu = new Student("小红", 90);
stu.sayHi(); // 你好,小红(继承 Person 的方法)
stu.showScore(); // 分数:90
<div id="parent">
<button id="child">点击</button>
</div>
<script>
let parent = document.querySelector("#parent");
let child = document.querySelector("#child");
// 捕获阶段触发(第三个参数为 true)
parent.addEventListener("click", () => console.log("父元素捕获"), true);
child.addEventListener("click", () => console.log("子元素捕获"), true);
// 冒泡阶段触发(第三个参数为 false/省略)
child.addEventListener("click", () => console.log("子元素冒泡"));
parent.addEventListener("click", () => console.log("父元素冒泡"));
// 点击按钮,输出顺序:
// 父元素捕获 → 子元素捕获 → 子元素冒泡 → 父元素冒泡
</script>
<ul id="list">
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
</ul>
<button id="addBtn">新增选项</button>
<script>
let list = document.querySelector("#list");
let addBtn = document.querySelector("#addBtn");
// 事件委托:绑定到父元素
list.addEventListener("click", (e) => {
// e.target 是触发事件的具体子元素
if (e.target.tagName === "LI") {
console.log("点击了:", e.target.innerText);
}
});
// 动态新增元素(仍有事件)
addBtn.addEventListener("click", () => {
let li = document.createElement("li");
li.innerText = `选项${list.children.length + 1}`;
list.appendChild(li);
});
</script>
// 差:重复查询 DOM
for (let i = 0; i < 10; i++) {
document.querySelector("#box").innerText += i;
}
// 好:一次查询,多次使用
let box = document.querySelector("#box");
let text = "";
for (let i = 0; i < 10; i++) {
text += i;
}
box.innerText = text;
let fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
let li = document.createElement("li");
li.innerText = `项${i}`;
fragment.appendChild(li); // 先添加到碎片(无重排)
}
list.appendChild(fragment); // 一次性添加到 DOM(仅一次重排)
display: none,操作完成后恢复,减少重排。window(全局对象)。// 窗口尺寸
console.log(window.innerWidth); // 视口宽度(不含滚动条)
console.log(window.innerHeight); // 视口高度
// 弹窗(不推荐,影响体验)
window.alert("提示"); // 提示框
let res = window.confirm("确认?"); // 确认框(返回 true/false)
let input = window.prompt("输入内容", "默认值"); // 输入框(返回输入值/ null)
// 定时器(前文已讲)
// 窗口跳转
window.location.href = "https://www.baidu.com"; // 跳转页面
window.open("https://www.baidu.com"); // 新开窗口
window.close(); // 关闭窗口(仅能关闭自己打开的窗口)
console.log(location.href); // 完整 URL
console.log(location.protocol); // 协议(http/https)
console.log(location.host); // 域名+端口
console.log(location.pathname); // 路径
console.log(location.search); // 查询参数(?a=1&b=2)
console.log(location.hash); // 哈希(#xxx)
location.reload(); // 刷新页面(true 强制刷新缓存)
location.assign("https://www.baidu.com"); // 跳转(可回退)
location.replace("https://www.baidu.com"); // 跳转(不可回退)
history.back(); // 后退(等价于点击浏览器后退)
history.forward(); // 前进
history.go(-1); // 后退1步(go(1) 前进1步)
history.pushState({}, "", "/new-page"); // 添加历史记录(不跳转)
history.replaceState({}, "", "/replace-page"); // 替换当前历史记录
console.log(navigator.userAgent); // 浏览器 UA(判断设备/浏览器)
console.log(navigator.language); // 语言(zh-CN)
console.log(navigator.onLine); // 是否联网
setTimeout/setInterval、AJAX、DOM 事件、script 整体代码。Promise.then/catch/finally、async/await、queueMicrotask。console.log("同步1");
setTimeout(() => {
console.log("宏任务:setTimeout");
}, 0);
Promise.resolve()
.then(() => {
console.log("微任务:Promise1");
return Promise.resolve();
})
.then(() => {
console.log("微任务:Promise2");
});
queueMicrotask(() => {
console.log("微任务:queueMicrotask");
});
console.log("同步2");
// 输出顺序:
// 同步1 → 同步2 → 微任务:Promise1 → 微任务:Promise2 → 微任务:queueMicrotask → 宏任务:setTimeout
function ajax(url, method = "GET", data = null) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.setRequestHeader("Content-Type", "application/json"); // 设置请求头
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText)); // 成功
} else {
reject(new Error(`请求失败:${xhr.status}`)); // 失败
}
};
xhr.onerror = function() {
reject(new Error("网络错误"));
};
xhr.send(data ? JSON.stringify(data) : null); // 发送数据
});
}
// 调用
ajax("https://api.example.com/user", "GET")
.then(data => console.log(data))
.catch(err => console.log(err));
// GET 请求
fetch("https://api.example.com/user")
.then(response => {
if (!response.ok) throw new Error(`HTTP 错误:${response.status}`);
return response.json(); // 解析 JSON
})
.then(data => console.log(data))
.catch(err => console.log(err));
// POST 请求
fetch("https://api.example.com/user", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ name: "小明", age: 18 })
})
.then(response => response.json())
.then(data => console.log(data))
.catch(err => console.log(err));
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// GET 请求
axios.get("https://api.example.com/user", {
params: { id: 1 } // URL 参数
})
.then(response => console.log(response.data))
.catch(err => console.log(err));
// POST 请求
axios.post("https://api.example.com/user", {
name: "小明",
age: 18
})
.then(response => console.log(response.data))
.catch(err => console.log(err));
// 拦截器(请求/响应拦截)
// 请求拦截:添加 token
axios.interceptors.request.use(config => {
config.headers.Authorization = "Bearer " + localStorage.getItem("token");
return config;
});
// 响应拦截:统一处理错误
axios.interceptors.response.use(
response => response,
err => {
if (err.response.status === 401) {
// 未授权,跳转到登录页
location.href = "/login";
}
return Promise.reject(err);
}
);
// 1. Promise.all:所有 Promise 成功才成功,返回结果数组;一个失败则整体失败
let p1 = Promise.resolve(1);
let p2 = Promise.resolve(2);
let p3 = Promise.resolve(3);
Promise.all([p1, p2, p3])
.then(res => console.log(res)) // [1,2,3]
.catch(err => console.log(err));
// 2. Promise.race:第一个完成的 Promise 决定结果(无论成功/失败)
let p4 = new Promise(resolve => setTimeout(() => resolve(4), 1000));
let p5 = new Promise((resolve, reject) => setTimeout(() => reject("失败"), 500));
Promise.race([p4, p5])
.then(res => console.log(res))
.catch(err => console.log(err)); // 失败(p5 先完成)
// 3. Promise.allSettled:所有 Promise 完成(无论成功/失败),返回每个的结果
let p6 = Promise.resolve(6);
let p7 = Promise.reject("失败");
Promise.allSettled([p6, p7])
.then(res => console.log(res));
// 输出:
// [
// { status: "fulfilled", value: 6 },
// { status: "rejected", reason: "失败" }
// ]
// 4. Promise.any:第一个成功的 Promise 决定结果;所有失败则抛出 AggregateError
let p8 = new Promise((resolve, reject) => setTimeout(() => reject("失败1"), 100));
let p9 = new Promise(resolve => setTimeout(() => resolve(9), 200));
Promise.any([p8, p9])
.then(res => console.log(res)) // 9
.catch(err => console.log(err));
// 1. 命名导出(可多个)
export const name = "JS";
export function fn() { console.log("hello"); }
// 2. 默认导出(仅一个)
export default function() { console.log("默认导出"); }
// 3. 批量导出
const a = 1;
const b = 2;
export { a, b };
// 4. 重命名导出
export { a as numA, b as numB };
// 1. 导入命名导出
import { name, fn } from "./module.js";
// 2. 导入默认导出
import defaultFn from "./module.js";
// 3. 批量导入
import { a, b, numA } from "./module.js";
// 4. 重命名导入
import { name as userName } from "./module.js";
// 5. 导入所有(命名空间)
import * as mod from "./module.js";
console.log(mod.name);
mod.fn();
<!-- script 标签添加 type="module" -->
<script type="module">
import { name } from "./module.js";
console.log(name);
</script>
let set = new Set([1, 2, 2, 3]);
console.log(set.size); // 3(自动去重)
// 增删查
set.add(4); // 添加
set.delete(2); // 删除
console.log(set.has(3)); // true(是否存在)
set.clear(); // 清空
// 遍历
for (let item of set) {
console.log(item);
}
set.forEach(item => console.log(item));
// 转换为数组
let arr = [...set];
let map = new Map();
let obj = { name: "JS" };
// 增删查
map.set(obj, "value"); // 键为对象
map.set("key", 123);
console.log(map.get(obj)); // "value"
console.log(map.has("key")); // true
map.delete("key");
map.clear();
// 遍历
for (let [key, value] of map) {
console.log(key, value);
}
map.forEach((value, key) => console.log(key, value));
[Symbol.iterator] 方法,返回迭代器,通过 next() 遍历:let arr = [1, 2, 3];
let iterator = arr[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
function* 定义,yield 暂停执行:function* gen() {
yield 1;
yield 2;
return 3;
}
let g = gen();
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
// 可选链(?.):避免访问嵌套属性时报错
let user = {
name: "小明",
address: {
city: "北京"
}
};
console.log(user?.address?.street); // undefined(不报错)
console.log(user?.getInfo?.()); // undefined(函数不存在)
// 空值合并(??):仅 null/undefined 触发默认值
let count = 0;
let result = count ?? "默认值"; // 0(区别于 ||,0 不触发)
let name = undefined ?? "游客"; // 游客
function debounce(fn, delay = 500) {
let timer = null;
return function(...args) {
clearTimeout(timer); // 重置定时器
timer = setTimeout(() => {
fn.apply(this, args); // 绑定 this 和参数
}, delay);
};
}
// 使用
let input = document.querySelector("input");
input.addEventListener("input", debounce(function(e) {
console.log("搜索:", e.target.value);
}, 500));
function throttle(fn, delay = 500) {
let flag = true; // 开关
return function(...args) {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
};
}
// 使用
window.addEventListener("scroll", throttle(function() {
console.log("滚动中...");
}, 500));
function formatTime(date, format = "YYYY-MM-DD HH:mm:ss") {
const dt = new Date(date);
const year = dt.getFullYear();
const month = String(dt.getMonth() + 1).padStart(2, "0");
const day = String(dt.getDate()).padStart(2, "0");
const hour = String(dt.getHours()).padStart(2, "0");
const minute = String(dt.getMinutes()).padStart(2, "0");
const second = String(dt.getSeconds()).padStart(2, "0");
return format
.replace("YYYY", year)
.replace("MM", month)
.replace("DD", day)
.replace("HH", hour)
.replace("mm", minute)
.replace("ss", second);
}
// 使用
console.log(formatTime(new Date())); // 2025-12-26 10:00:00
console.log(formatTime(new Date(), "YYYY/MM/DD")); // 2025/12/26
| 错误类型 | 原因 | 示例 |
|---|---|---|
| ReferenceError | 引用未定义的变量 / 函数 | console.log(undefinedVar) |
| TypeError | 操作类型错误(如调用非函数) | let obj = {}; obj.fn() |
| SyntaxError | 语法错误(如括号不匹配) | let a = 10; if (a > 5) { console.log(a) |
| RangeError | 数值超出范围(如数组长度为负数) | let arr = new Array(-1) |
| URIError | URI 处理错误(如 decodeURI 格式错误) | decodeURI("%") |
console.log("普通日志");
console.warn("警告");
console.error("错误");
console.table([{ name: "小明" }, { name: "小红" }]); // 表格展示
console.time("耗时"); // 计时开始
// 执行代码
console.timeEnd("耗时"); // 计时结束(输出耗时)
console.dir(obj); // 展开对象详情
async function fn() {
try {
let res = await axios.get("https://api.example.com/error");
} catch (err) {
console.error("异步错误:", err);
}
}
/**
* 加法函数
* @param {number} a - 第一个数
* @param {number} b - 第二个数
* @returns {number} 两数之和
*/
function add(a, b) {
return a + b;
}
transform/opacity 实现动画(GPU 加速,无重排)。loading="lazy" 或手动实现)。localStorage。fn = null)。innerHTML,优先 innerText。< 转义为 <)。eval 执行字符串代码,易被注入攻击,且性能差。<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>待办清单</title>
<style>
.todo-container { max-width: 600px; margin: 20px auto; }
.todo-input { display: flex; gap: 10px; margin-bottom: 20px; }
.todo-input input { flex: 1; padding: 8px; }
.todo-input button { padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
.todo-list { list-style: none; padding: 0; }
.todo-item { display: flex; justify-content: space-between; align-items: center; padding: 10px; border-bottom: 1px solid #eee; }
.todo-item.done span { text-decoration: line-through; color: #999; }
.todo-item button { background: #dc3545; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer; }
</style>
</head>
<body>
<div class="todo-container">
<div class="todo-input">
<input type="text" id="todoInput" placeholder="请输入待办事项">
<button id="addBtn">添加</button>
</div>
<ul id="todoList" class="todo-list"></ul>
</div>
<script>
// 1. 获取 DOM 元素
const todoInput = document.querySelector("#todoInput");
const addBtn = document.querySelector("#addBtn");
const todoList = document.querySelector("#todoList");
// 2. 初始化数据(从 localStorage 读取)
let todos = JSON.parse(localStorage.getItem("todos")) || [];
// 3. 渲染列表
function renderTodos() {
todoList.innerHTML = "";
todos.forEach((todo, index) => {
const li = document.createElement("li");
li.className = `todo-item ${todo.done ? "done" : ""}`;
li.innerHTML = `
<div>
<input type="checkbox" ${todo.done ? "checked" : ""} data-index="${index}">
<span>${todo.content}</span>
</div>
<button data-index="${index}">删除</button>
`;
todoList.appendChild(li);
});
// 保存到 localStorage
localStorage.setItem("todos", JSON.stringify(todos));
}
// 4. 添加待办
function addTodo() {
const content = todoInput.value.trim();
if (!content) return alert("请输入待办事项");
todos.push({ content, done: false });
todoInput.value = "";
renderTodos();
}
// 5. 切换完成状态
function toggleTodo(index) {
todos[index].done = !todos[index].done;
renderTodos();
}
// 6. 删除待办
function deleteTodo(index) {
todos.splice(index, 1);
renderTodos();
}
// 7. 绑定事件
addBtn.addEventListener("click", addTodo);
todoInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") addTodo(); // 回车添加
});
// 事件委托:切换/删除
todoList.addEventListener("click", (e) => {
const index = e.target.dataset.index;
if (index === undefined) return;
if (e.target.type === "checkbox") {
toggleTodo(index);
} else if (e.target.tagName === "BUTTON") {
deleteTodo(index);
}
});
// 8. 初始渲染
renderTodos();
</script>
</body>
</html>
this 指向由调用方式决定,事件循环是异步执行的核心逻辑。async/await + axios;ES6+ 特性(模块化、Set/Map、可选链)大幅提升开发效率。