1.var和let的区别
1.1作用域与有效范围
var 在函数或全局作用域中声明变量,无论在块级作用域内外都可以访问。
var 即使在块级作用域外部访问,仍然能获取到结果。
let 在块级作用域内声明变量,仅在其所在的作用域中有效。
let 如果在作用域外访问,会抛出 ReferenceError
// 示例{var a = 10;let b = 20;}console.log(a); // 10 (var 在全局或函数作用域内有效)console.log(b); // ReferenceError: b is not defined (let 仅在块级作用域内有效)
1.2重复定义
var: 可以在同一个作用域内多次声明同名变量,而不会抛出错误,后面声明的变量会覆盖前面的值。
let: 不允许在同一作用域内重复声明同名变量,重复声明会抛出 SyntaxError。
// 示例{var a = 1;var a = 2; // 无报错,a 赋值为 2console.log(a); // 2let b = 1;//let b = 2; // 报错Uncaught SyntaxError: Identifier 'b' has already been declaredconsole.log(b); // 1}
1.3变量提升
var: 变量声明会被提升到作用域的顶部,但赋值不会被提升,因此会在使用变量前显示为 undefined。let: 变量声明不会被提升,尝试在声明前访问会抛出 ReferenceError,因为变量在声明之前处于“暂时性死区(TDZ)。
// 示例
{console.log(a); // undefined (var 变量会提升,初始化为 undefined)var a = 10;console.log(b); // Uncaught ReferenceError: Cannot access 'b' before initializationlet b = 10; // let 变量不会提升,访问前会抛出错误}
1.4块级作用域与闭包
var在块级作用域内声明的变量会被提升到函数或全局作用域,因此可能会影响闭包的行为。
let创建的是块级作用域变量,避免了 var 在闭包中的不期望行为。
// 示例
for (var i = 0; i < 3; i++) {setTimeout(function () {console.log(i); // 输出 3, 由于 var 的提升,i 的值在 for 循环外部是 3}, 100);}for (let j = 0; j < 3; j++) {setTimeout(function () {console.log(j); // 输出 0, 1, 2, 因为 let 会在每次迭代中创建新的作用域}, 100);}
1.5总结
var 和 let 的主要区别在于作用域、变量提升、以及是否支持重复定义。
var 是函数作用域或全局作用域,且会提升到顶部;let 是块级作用域,不会提升。
let 在闭包和异步代码中表现得更稳定,避免了 var 可能带来的问题。
// 示例{var a = 10;let b = 20;}console.log(a); // 10 (var 在全局或函数作用域内有效)console.log(b); // ReferenceError: b is not defined (let 仅在块级作用域内有效)
2.const 与 let 的差异
const: 声明的是常量,必须在声明时初始化,且值不能修改。
但 const 声明的是引用类型的对象时,对象的内容是可变的,只是不能重新赋值给其他对象。
const a = 10;
a = 20; // Uncaught TypeError: Assignment to constant variable.
console.log(a)const obj = {name: 'tom',age: 18};
obj.age = 22;
console.log(obj) // {name: 'tom', age: 22}
obj = {}; // Uncaught TypeError: Assignment to constant variable.
总结:var 和 let 的主要区别在于作用域、变量提升、以及是否支持重复定义。var 是函数作用域或全局作用域,且会提升到顶部;let 是块级作用域,不会提升。let 在闭包和异步代码中表现得更稳定,避免了 var 可能带来的问题。
3.解构
3.1数组解构
解构赋值允许从数组中提取值并赋给变量。通过解构语法,我们可以更简洁地获取数组元素。
let arr = [1, 2, 3]
let [a, b, c] = arr
// 等价于
// let a = arr[0]
// let b = arr[1]
// let c = arr[2]
console.log(a)
console.log(b)
console.log(c)
3.2对象解构
对象解构允许从对象中提取值并赋给变量,变量名必须与对象的属性名相同。
let obj = { name: 'tom', age: 18 };
let { name, age } = obj
// 等价于
// let name = obj.name
// let age = obj.age
console.log(name)
console.log(age)
4.链判断
可选链在判断中间属性是否存在时,会自动短路并返回 undefined,避免了显式的检查
可选链允许我们在访问深层嵌套对象的属性时避免因中间某一属性为 null 或 undefined 而导致的错误。
let content = null;
// 传统的链式判断
const username = (content && content.body && content.body.user && content.body.user.username) || 'defalut';
console.log(username)
//使用可选链(?.)
const uname = content?.body?.user?.username || 'default';
console.log(uname)
5.参数默认值
传统的参数默认值 使用 || 操作符来为参数提供默认值,但当参数为 0 或 false 时,也会被误判为“假值”。
function add(a, b) {b = b || 10; // 如果 b 为 falsy 值(例如 0),则默认赋值为 10return a + b;
}
console.log(add(10)); // 20
//使用 ES6 的默认参数值 通过在函数声明时直接为参数提供默认值,避免了上面的问题。此方法仅在参数为 undefined 时生效,而不是 null 或 0 等其他“假值”。
function add2(a, b = 10) {return a + b;}
console.log(add2(10)); // 20
6.箭头函数
6.1一个参数 箭头函数提供了更简洁的函数表达式,且默认绑定函数的 this
let print1 = function (obj) {console.log(obj);
};
let print2 = obj => console.log(obj); // 箭头函数,简写形式
6.2两个参数 如果有多个参数,则使用括号包裹参数。
let sum = function (a, b) {return a + b;
}
let sum2 = (a, b) => a + b;
6.3当箭头函数有多行语句时,必须使用大括号 {} 并明确 return
let sum3 = (a, b) => {c = a + b;return c;
};
6.4当箭头函数只有一行时,可以省略 return 和大括号,简洁书写
let sum4 = (a, b) => a + b;
7.字符串模板
ES6 引入了模板字符串(也叫模板字面量),它允许我们在字符串中直接嵌入变量或表达式,使用反引号(`)包裹,并通过 ${} 插入变量。
7.1 普通的字符串拼接
let person = {name:'tom',age: 22}
let {name,age} = person
let str = "姓名:" + name + ", 年龄:" + age;
console.log(str); // 姓名:Tom, 年龄:22
7.2 字符串模板
let person = {name:'tom',age: 22}
let {name,age} = person
let str2 = `姓名:${name}, 年龄:${age}`;
console.log(str2); // 姓名:Tom, 年龄:22
7.3多行支持
let multilineStr = `这是一行这是第二行这是第三行`;
console.log(multilineStr); // 打印结果为3行
7.4字符串模板中的表达式
let num1 = 5;
let num2 = 10;
let result = `结果:${num1 + num2}`;
console.log(result); // 结果:15
7.5总结
字符串模板支持变量、表达式嵌入以及多行字符串。
使用字符串模板(Template Literals)比传统的字符串拼接更简洁和易于维护。
传统字符串拼接虽然仍然有效,但随着代码复杂度增加,使用模板字符串会使代码更具可读性和可维护性。
8.Promise用法
Promise 是 JavaScript 中用于处理异步操作的一个对象,代表了一个操作的最终完成(或失败)及其结果值。
Promise 有三种状态:
pending:等待状态,初始状态,既不是成功,也不是失败。
fulfilled:成功状态,表示操作成功完成。
rejected:失败状态,表示操作失败
8.18.1基础使用
let promise = new Promise((resolve,reject)=>{let dataFetched = true; // 模拟数据是否获取成功if(dataFetched){resolve("数据获取成功!");}else{reject("数据获取失败!");}
})
promise.then((message)=>console.log(message)).catch((error)=>{console.log(error)})
8.2链式调用多个.then()
let fetchData = new Promise((resolve)=>setTimeout(()=>resolve("8.2数据获取成功!"),1000))
fetchData.then((message)=>{console.log(message);return "8.2数据获取成功!";
}).then((processedData)=>{console.log(processedData);return "8.2保存数据!";
}).then((saveData)=>{console.log(saveData)
}).catch((error)=>{console.log(error)
})
打印结果:
8.2数据获取成功1!
ES6.html:200 8.2数据获取成功2!
ES6.html:203 8.2保存数据3!
通过 .then() 实现多个异步操作的顺序执行。
Promise 支持链式调用,即一个 .then() 返回的值会传递给下一个 .then()
8.3Promise.all()
Promise.all() 用于并行执行多个异步操作,只有所有 Promise 都完成时,它才会返回。
let fetchData1 = new Promise((resolve) => setTimeout(() => resolve("数据1获取成功"), 1000));
let fetchData2 = new Promise((resolve) => setTimeout(() => resolve("数据2获取成功"), 2000));Promise.all([fetchData1, fetchData2]).then((results) => {console.log(results); // 输出:["数据1获取成功", "数据2获取成功"]}).catch((error) => {console.error(error); // 如果其中一个失败,会进入这里
});
8.4Promise.race()
Promise.race() 也是用于并行执行多个异步操作,但它会返回第一个完成的 Promise
let fetchData3 = new Promise((resolve) => setTimeout(() => resolve("数据1获取成功"), 1000));
let fetchData4 = new Promise((resolve) => setTimeout(() => resolve("数据2获取成功"), 500));Promise.race([fetchData3, fetchData4]).then((result) => {console.log(result); // 输出:数据2获取成功}).catch((error) => {console.error(error);
});
8.5总结
Promise 基本概念:
Promise 代表异步操作的最终完成(或失败)及其结果值。
resolve() 用于标记成功并返回值,reject() 用于标记失败并返回错误。
Promise 初始状态为 pending,可以通过 resolve 转变为 fulfilled,通过 reject 转变为 rejected。
Promise 链式调用:
通过 .then() 处理成功的结果,.catch() 处理失败的错误。
Promise 支持链式调用,即一个 .then() 返回的值会传递给下一个 .then()。
并行操作:
Promise.all() 用于并行执行多个异步操作,等待所有 Promise 都完成后才会返回。
Promise.race() 用于并行执行多个异步操作,只返回第一个完成的 Promise。
优点:
Promise 提供了比回调函数更优雅的方式来处理异步操作,避免了回调地狱问题。
Promise 的链式调用使得多个异步操作可以顺序执行,逻辑更清晰。
Promise 的并行控制(Promise.all 和 Promise.race)使得多个异步任务可以在并行时得到很好的控制。
注意:如果在链式调用中某一 Promise 被拒绝,后续的 .then() 不会继续执行,进入 .catch() 捕获错误。
9.Async 和 Await
async 和 await 是 JavaScript 中用于处理异步操作的语法糖,它们基于 Promise,使得异步代码更具可读性和同步风格,减少了回调函数和 .then() 链式调用的复杂性。
9.1async 函数
async 关键字用于声明一个函数,使其成为异步函数。异步函数返回一个 Promise 对象,且在函数内部,可以使用 await 来等待异步操作完成。
let fetchData01 = async () => { return "数据获取成功!" }
fetchData01().then((message) => console.log(message))
9.2 await 关键字
await 会暂停函数的执行,直到 Promise 完成。
await 关键字只能在 async 函数内部使用,用于等待一个 Promise 完成,并返回其结果。
await 会等待 Promise 完成,如果 Promise 被 resolve,返回的值会赋给变量;如果 Promise 被 reject,会抛出异常。
let fetchData02 = async () => {let result = await new Promise((resolve) => {setTimeout(() => resolve("9.2获取数据成功!"), 1000)})console.log(result)
}
fetchData02()
9.3 async/await 错误处理
通过 try/catch 可以捕获 async 函数中 await 产生的错误,与同步代码处理异常的方式相同
let fetchData03 = async () => {try {let result = await new Promise((resolve, reject) => {setTimeout(() => reject("9.3获取数据失败!"), 1000)})console.log(result)} catch (error) {console.log(error)}
}
fetchData03()
9.
在这里插入代码片
9.4多个异步操作并行执行
let fetchData04 = async () => {let promise01 = new Promise((resolve) => setTimeout(() => resolve("9.4获取数据成功01!")), 1000)let promise02 = new Promise((resolve) => setTimeout(() => resolve("9.4获取数据成功02!")), 1000)let result = await Promise.all([promise01, promise02])console.log(result)
}
fetchData04()
9.5多个异步操作按顺序执行
let fetchData05 = async () => {let promise03 = await new Promise((resolve) => setTimeout(() => resolve("9.5获取数据成功01!")), 1000)console.log(promise03)let promise04 = await new Promise((resolve) => setTimeout(() => resolve("9.5获取数据成功02!")), 1000)console.log(promise04)
}
fetchData05()
9.6避免回调地狱
回调地狱:非常不推荐,代码难以维护,错误处理复杂。
链式的.then调用:比回调地狱更清晰,但当异步操作较多时,.then() 链条会变得长且难以调试。
async/await:最简洁和直观,尤其适合多个异步操作的顺序执行。通过 try/catch 可以像同步代码一样处理错误,强烈推荐使用。
let loadData01 = (callback) => {setTimeout(() => {console.log("9.6加载数据成功!")callback(null, '--加载数据--')}, 1000)}
let processData01 = (data, callback) => {setTimeout(() => {console.log("9.6处理数据成功!")callback(null, `--${data} 处理数据--`)}, 1000)}
let saveData01 = (data, callback) => {setTimeout(() => {console.log("9.6保存数据成功!")callback(null, `--${data} 保存数据--`)}, 1000)
}
// 回调地狱:每个操作依赖前一个操作的完成
loadData01((err, data) => {if (err) {console.error('数据加载失败:', err)return}processData01(data, (err, processedData) => {if (err) {console.error('数据处理失败:', err)return}saveData01(processedData, (err, savedData) => {if (err) {console.error('数据保存失败:', err)return}console.log('数据处理完成:', savedData)})})
})
//使用 Promise 避免回调地狱
let loadData02 = ()=> {return new Promise((resolve, reject) => {setTimeout(() => {console.log("9.6Promise加载数据成功!")resolve('--加载数据--')}, 1000)})
}let processData02 = (data)=> {return new Promise((resolve, reject) => {setTimeout(() => {console.log("9.6Promise处理数据成功!")resolve( `--${data} 处理数据--`)}, 1000)})
}let saveData02 = (data)=> { return new Promise((resolve, reject) => {setTimeout(() => {console.log("9.6Promise保存数据成功!")resolve(`--${data} 保存数据--`)}, 1000)})
}
//使用链式的.then调用,避免回调地狱
loadData02().then(data => processData02(data)).then(processedData => saveData02(processedData)).then(savedData => {console.log('9.6then数据处理完成:', savedData)}).catch(error => {console.error('9.6then数据处理失败:', error)})
//使用 async/await避免回调地狱
let loadData03 = async () =>{try {const data = await loadData02()const processedData = await processData02(data)const savedData = await saveData02(processedData)console.log('9.6await数据处理完成:', savedData)} catch (error) {console.error('9.6await数据处理失败:', error)}
}
loadData03()
10.模块化 ES6 Module
ES6 Module 模块功能主要由两个命令构成:export 和 import。
export 命令用于规定模块的对外接口,import 命令用于输入其他模块提供的功能
10.1命名导出(Named Export)
可以导出多个变量或函数。导出的名称需要与导入时的名称一致。
//写法一
//导出变量
export const PI = 3.14;
//导出函数
export function add(a, b) {return a + b;
}
export function subtract(a, b) {return a - b;
}
//导出类
export class Person {constructor(name, age) {this.name = namethis.age = age} sayHello() {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)}
}
//写法二
//导出变量
const PI = 3.14;
export {PI}
//导出函数
function add(a, b) {return a + b;
}
function subtract(a, b) {return a - b;
}
export{add,subtract}
10.2默认导出(Default Exports)
默认导出可以是一个函数、对象、类等。每个模块只能有一个默认导出。
//greet.js
//默认导出变量
export default 10
const person = {name: 'Amy',age: 20,
}
export default person
//默认导出函数
export default function greet(name) {return `Hello, ${name}!`;
}
//默认导出类
export default class Person {constructor(name, age) {this.name = namethis.age = age}sayHello() {console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`)}
}
10.3命名导入(Named Import)
导入时使用 {} 来指定要导入的变量或函数,名称必须与导出的名称一致。
// app.js
import { add, subtract, PI } from './math.js';console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 2
console.log(PI); // 3.14
10.4默认导入(Default Imports)
导入时不使用 {},可以给默认导入的内容起任何名称。
// app.js
import greet from './greet.js';console.log(greet('Tom')); // "Hello, Tom!"
10.5批量导出和导入:可以通过 export * from 来批量导出一个模块中的所有导出内容。
// math.js
export function add(a, b) {return a + b;
}
export function subtract(a, b) {return a - b;
}
export const PI = 3.14;
export * from './math.js';// app.js
import * as math from './index.js';console.log(math.add(1, 2)); // 3
console.log(math.subtract(5, 3)); // 2
console.log(math.PI); // 3.14
10.6同时导入
// app.js
import greet, { add, subtract, PI } from './math.js';console.log(greet('Tom')); // "Hello, Tom!"
console.log(add(1, 2)); // 3
console.log(subtract(5, 3)); // 2
console.log(PI); // 3.14
10.7导出导入时起别名 可以在导入时给变量或函数起别名。
// app.js
import { add as addition, subtract as subtraction, PI as pi } from './math.js';console.log(addition(2, 3)); // 5
console.log(subtraction(5, 3)); // 2
console.log(pi); // 3.14
10.8export 与 import 的复合写法
如果在一个模块之中,先输入后输出同一个模块,import 语句可以与 export 语句写在一起。
export { foo, bar } from 'my_module'// 可以简单理解为
import { foo, bar } from 'my_module'
export { foo, bar }
10.9异步导入(动态导入)
// app.js
// 动态导入
import('./math.js').then(math => {console.log(math.add(1, 2)); // 3console.log(math.PI); // 3.14
});
10.10总结
命名导出(Named Export):用于导出多个功能,导入时需要使用相同的名称。
默认导出(Default Export):每个模块只能有一个默认导出,导入时可以使用任意名称。
批量导入和导出:通过 export * 可以批量导出,使用 import * as 来导入所有内容。
动态导入:使用 import() 可以按需加载模块,实现代码分割。模块化使得功能单元可以被不同的文件复用,减少了冗余代码。
通过命名空间(模块),不同文件的函数、变量等不会发生命名冲突。
模块化可以将代码分成不同的文件,每个文件负责不同的功能,增强了代码的可维护性。