JS
for...in & for...of
for...in
和for...of
都是JavaScript
中的循环语句。for...in
循环主要是为了遍历对象而生,不适用于遍历数组;for...of
循环可以用来遍历数组、类数组对象,字符串、Set
、Map
以及 Generator
对象。
-
for...of
遍历获取的是对象的键值 ,for...in
获取的是对象的键名; -
for... in
会遍历对象的整个原型链 ,性能非常差不推荐使用,而for ... of
只遍历当前对象不会遍历原型链; -
对于数组的遍历,
for...in
会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for...of
只返回数组的下标对应的属性值;
使用for...of遍历对象
使用 Object.keys()
、Object.values()
或 Object.entries()
方法:
使用 Object.keys()
遍历对象的键
JS
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// 使用 Object.keys() 获取对象的键数组
for (const key of Object.keys(person)) {
console.log(key);
}
使用 Object.values()
遍历对象的值
JS
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// 使用 Object.values() 获取对象的值数组
for (const value of Object.values(person)) {
console.log(value);
}
使用 Object.entries()
遍历对象的键值对
JS
const person = {
name: 'John',
age: 30,
city: 'New York'
};
// 使用 Object.entries() 获取对象的键值对数组
for (const [key, value] of Object.entries(person)) {
console.log(`${key}: ${value}`);
}
forEach & map
forEach
没有返回值,是一个纯粹的操作方法,用于执行副作用;map
有返回值,用于根据原数组通过处理生成新数组。
forEach
不同于map
,缺少返回值,不支持链式调用。forEach
可以通过传入特定的回调函数来实现修改原数组,但map
不行。
尾调用
尾调用(Tail Call)是编程领域,特别是函数式编程中的一个重要概念,在 JavaScript 等支持该特性的语言中有着广泛应用。尾调用就是在函数的最后一步调用函数 ,在一个函数里调用另外一个函数会保留当前执行的上下文 ,如果在函数尾部调用,因为已经是函数最后一步,所以这时可以不用保留当前的执行上下文,从而节省内存 。但是ES6的尾调用只能在严格模式下开启,正常模式是无效的。
深/浅拷贝
浅拷贝
定义
浅拷贝创建一个新对象或数组,新对象或数组会复制原始对象或数组的一层属性。如果属性是基本数据类型(如 number
、string
、boolean
等),则会复制其值;但如果属性是引用数据类型(如对象、数组等),则只会复制引用,即新对象和原始对象会共享同一个引用数据类型的实例。
实现方式
Object.assign()
:用于将一个或多个源对象的所有可枚举属性复制到目标对象。
JS
const originalObj = {
name: 'John',
hobbies: ['reading', 'swimming']
};
const shallowCopyObj = Object.assign({}, originalObj);
console.log(shallowCopyObj);
// 修改浅拷贝对象的基本类型属性
shallowCopyObj.name = 'Jane';
console.log(originalObj.name);
// 修改浅拷贝对象的引用类型属性
shallowCopyObj.hobbies.push('running');
console.log(originalObj.hobbies);
- 扩展运算符 (
...
) :可以用于对象和数组的浅拷贝。
JS
const originalArray = [1, [2, 3]];
const shallowCopyArray = [...originalArray];
console.log(shallowCopyArray);
// 修改浅拷贝数组的引用类型元素
shallowCopyArray[1].push(4);
console.log(originalArray[1]);
特点
- 浅拷贝只复制对象的一层属性,对于嵌套的引用类型属性,新对象和原始对象会指向同一个内存地址。
- 操作浅拷贝对象的基本类型属性不会影响原始对象,但操作其引用类型属性会影响原始对象。
深拷贝
定义
深拷贝会递归地复制对象的所有属性,包括嵌套的对象和数组。这意味着深拷贝会创建一个完全独立的新对象,新对象和原始对象在内存中是完全分离的,修改新对象不会影响原始对象,反之亦然。
实现方式
JSON.parse(JSON.stringify())
:这是一种简单的深拷贝方法,适用于不包含函数、正则表达式、Symbol
等特殊类型的对象。
JS
const originalObj = {
name: 'John',
hobbies: ['reading', 'swimming']
};
const deepCopyObj = JSON.parse(JSON.stringify(originalObj));
console.log(deepCopyObj);
// 修改深拷贝对象的引用类型属性
deepCopyObj.hobbies.push('running');
console.log(originalObj.hobbies);
- 手动递归实现:通过递归遍历对象的所有属性,复制每个属性的值,如果属性是引用类型,则继续递归复制。
JS
function deepCopy(obj) {
if (typeof obj!== 'object' || obj === null) {
return obj;
}
let newObj = Array.isArray(obj)? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) { //判断是否是自身的属性,而非原型链上的
newObj[key] = deepCopy(obj[key]);
}
}
return newObj;
}
const originalArray = [1, [2, 3]];
const deepCopyArray = deepCopy(originalArray);
console.log(deepCopyArray);
// 修改深拷贝数组的引用类型元素
deepCopyArray[1].push(4);
console.log(originalArray[1]);
特点
- 深拷贝会完全复制对象的所有属性,包括嵌套的引用类型,新对象和原始对象在内存中是相互独立的。
- 操作深拷贝对象的任何属性都不会影响原始对象。
let、const、var
作用域
-
var
:使用var
声明的变量具有函数作用域。这意味着变量在整个函数内部都是可见的,无论它是在函数的哪个位置声明的。如果在函数外部声明,那么它具有全局作用域,在整个全局环境中都可以访问。 -
let
和const
:let
和const
具有块级作用域。块级作用域是指由一对花括号{}
包裹的代码块,如if
语句、for
循环、while
循环等。在块级作用域内声明的变量只能在该块内访问。
变量提升
var
:使用var
声明的变量会发生变量提升,即变量的声明会被提升到当前作用域的顶部,但赋值不会提升。在变量声明之前访问该变量,会得到undefined
。
JS
console.log(a);
var a = 10;
上述代码相当于:
JS
var a;
console.log(a);
a = 10;
所以输出结果为 undefined
。
let
和const
:let
和const
也存在变量提升,但它们在声明之前处于 "暂时性死区"(Temporal Dead Zone,TDZ)。在暂时性死区内访问变量会抛出ReferenceError
错误。
JS
console.log(b);
let b = 20;
这段代码会抛出 ReferenceError
错误,因为 b
处于暂时性死区,不能在声明之前访问。
可修改性
-
var
和let
:使用var
和let
声明的变量可以被重新赋值。 -
const
:使用const
声明的常量必须在声明时进行初始化,并且一旦初始化后,就不能再重新赋值。不过,如果const
声明的是一个对象或数组,对象的属性或数组的元素是可以修改的。
重复声明
-
var
:使用var
可以在同一作用域内重复声明同一个变量,后面的声明会覆盖前面的声明。 -
let
和const
:在同一作用域内,使用let
和const
不能重复声明同一个变量,否则会抛出SyntaxError
错误。
箭头函数
-
箭头函数是匿名函数 ,不能作为构造函数,使用
new
关键字。 -
箭头函数没有
arguments
-
箭头函数没有自己的
this
,会获取所在的上下文作为自己的this
-
call()
、applay()
、bind()
方法不能改变箭头函数中的this
指向 -
箭头函数没有
prototype
-
箭头函数不能用作
Generator
函数,不能使用yeild
关键字
Set & Map & weakMap & Object
Set
Set
是一种无序且唯一的数据集合,它类似于数组,但成员的值都是唯一的,没有重复的值。它只存储值,不存储键值对。- 创建:
new Set([1, 1, 2, 3, 3, 4, 2])
add(value)
:添加某个值,返回Set结构本身。delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。has(value)
:返回一个布尔值,表示该值是否为Set的成员。clear()
:清除所有成员,没有返回值。
Map
Map
是一种键值对的集合,其中键和值可以是任意类型的数据,并且键是唯一的。其中的键和值是一一对应的关系。set(key, val):
向Map
中添加新元素get(key):
通过键值查找特定的数值并返回has(key):
判断Map
对象中是否有Key
所对应的值,有返回true
,否则返回false
delete(key):
通过键值从Map
中移除对应的数据clear():
将这个Map
中的所有元素删除
weakMap
WeakMap
是一种键值对的集合,其中键必须是对象类型 ,而值可以是任意类型。与普通的 Map
不同,WeakMap
的键是弱引用。这意味着当这些键在其他地方不再有强引用时,它们会被垃圾回收机制自动回收,而不会像普通 Map 那样阻止垃圾回收。
Object
Object
是无序的数据集合,由键值对(也称为属性)组成。键通常是字符串(在 ES6 中也可以是 Symbol
类型),值可以是任意数据类型,包括基本数据类型(如 Number
、String
、Boolean
等)和引用数据类型(如 Object
、Array
、Function
等)。它也是所有对象的基类。
Promise
在 JavaScript 里,异步操作(如网络请求、文件读取)不会立即返回结果。Promise
代表一个异步操作的最终完成或失败,并返回其结果。它有三种状态:
-
pending(进行中) :初始状态,既不是成功,也不是失败状态。
-
fulfilled(已成功) :意味着操作成功完成。
-
rejected(已失败) :意味着操作失败。
一旦 Promise
状态变为 fulfilled
或 rejected
,就不会再改变,这种特性被称为 "不可变性"。
基本语法
创建一个 Promise
对象时,需要传入一个执行器函数,该函数接收两个参数:resolve
和 reject
。
JS
const myPromise = new Promise((resolve, reject) => {
// 模拟异步操作
setTimeout(() => {
const success = true;
if (success) {
resolve('操作成功');
} else {
reject('操作失败');
}
}, 1000);
});
resolve
:一个函数,当异步操作成功时调用,将Promise
的状态从pending
变为fulfilled
,并传递操作结果。reject
:一个函数,当异步操作失败时调用,将Promise
的状态从pending
变为rejected
,并传递错误信息。
处理 Promise 结果
使用 then
和 catch
方法来处理 Promise
的结果。
JS
myPromise
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
then
:用于处理Promise
成功的情况,接收一个回调函数,该回调函数的参数是resolve
传递的值。catch
:用于处理Promise
失败的情况,接收一个回调函数,该回调函数的参数是reject
传递的错误信息。
链式调用
Promise
支持链式调用,允许按顺序执行多个异步操作。
JS
function asyncOperation1() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('操作 1 完成');
}, 1000);
});
}
function asyncOperation2() {
return new Promise((resolve) => {
setTimeout(() => {
resolve('操作 2 完成');
}, 1000);
});
}
asyncOperation1()
.then((result1) => {
console.log(result1);
return asyncOperation2();
})
.then((result2) => {
console.log(result2);
})
.catch((error) => {
console.error(error);
});
其他方法
Promise.all
:接收一个Promise
数组,当所有Promise
都成功时,返回一个新的Promise
,其结果是所有Promise
结果组成的数组;如果有任何一个Promise
失败,则整个Promise
立即失败。
JS
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log(results);
})
.catch((error) => {
console.error(error);
});
Promise.race
:接收一个Promise
数组,返回一个新的Promise
,只要数组中的任何一个Promise
率先改变状态(成功或失败),就以该Promise
的结果作为最终结果。
缺点
- 无法取消
Promise
,一旦新建它就会立即执行,无法中途取消。 - 如果不设置回调函数,
Promise
内部抛出的错误,不会反应到外部。 - 当处于
pending
状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。