Promise优缺点
优点:
- 更优雅的异步编程:Promise提供了一种更优雅的方式来处理异步操作,避免了
回调地狱
(Callback Hell)的问题。通过使用链式调用
的方式,可以更清晰地表达异步操作的顺序和依赖关系。 - 更好的错误处理:Promise可以通过
catch
方法捕获异步操作的错误,使错误处理变得更加简洁和一致。在链式调用中,可以在任何一个then
方法中捕获错误,而不需要在每个异步操作的回调函数中单独处理错误。 - 更好的可读性和可维护性:Promise的链式调用可以使异步代码更易于阅读和理解。每个
then
方法都可以返回一个新的Promise对象,使得代码的逻辑更加清晰,减少了嵌套的层级。 - 更好的错误追踪:Promise可以捕获和传递错误,使得错误的来源更加明确。在链式调用中,每个
then
方法都可以单独处理错误,可以更精确地定位错误发生的位置。 - 支持并行和串行操作:Promise提供了
Promise.all
和Promise.race
方法,可以方便地处理多个异步操作的并行和串行执行。Promise.all
可以等待多个Promise都完成后再执行后续操作,Promise.race
可以在任意一个Promise完成后立即执行后续操作。
缺点:
- 不支持取消:一旦创建了一个Promise对象,就无法取消它。这意味着一旦启动了一个异步操作,就无法中途停止或取消它。
- 无法处理同步操作:Promise主要用于处理异步操作,对于同步操作来说,使用Promise可能会增加代码的复杂性和冗余性。
- 不支持错误重试:Promise本身不提供错误重试的机制,需要额外的代码来实现错误重试的逻辑。
- 需要兼容性处理:Promise是ES6引入的特性,不支持ES6的环境需要使用polyfill或转译工具来兼容。
谈谈Promise.all方法、Promise.race方法以及使用
Promise.all
和Promise.race
是Promise对象提供的两个静态方法,用于处理多个Promise对象的并行
和串行
执行。
- Promise.all:
Promise.all
方法接收一个Promise对象数组作为参数,并返回一个新的Promise对象。这个新的Promise对象在所有传入的Promise对象都成功完成时才会被解决(Fulfilled),并将所有Promise对象的结果以数组的形式传递给then
方法。如果任何一个Promise对象失败(Rejected),则新的Promise对象会立即被拒绝(Rejected),并将第一个失败的Promise对象的原因传递给catch
方法。 - Promise.race:
Promise.race
方法接收一个Promise对象数组作为参数,并返回一个新的Promise对象。这个新的Promise对象在传入的Promise对象中有任何一个成功完成时就会被解决(Fulfilled),并将第一个
成功的Promise对象的结果传递给then
方法。如果所有的Promise对象都失败(Rejected),则新的Promise对象会立即被拒绝(Rejected),并将第一个
失败的Promise对象的原因传递给catch
方法。
浏览器缓存机制
浏览器缓存机制是指浏览器在加载网页时,将一些资源(如HTML、CSS、JavaScript、图像等)保存在本地的缓存中,以便在后续访问同一网页时可以直接从缓存中获取资源,而不需要再次从服务器下载。
- 缓存位置:浏览器缓存可以分为多个位置,包括
内存缓存
和磁盘缓存
。内存缓存速度更快,但容量较小,而磁盘缓存速度较慢,但容量较大。 - 缓存策略:浏览器根据资源的缓存策略来确定是否缓存资源以及缓存的
有效期
。常见的缓存策略包括强缓存和协商缓存。- 强缓存:通过设置
Cache-Control
和Expires
响应头来控制资源的缓存。Cache-Control
可以指定缓存的最大有效时间,而Expires
是一个具体的过期时间。当资源的缓存有效期内,浏览器会直接从缓存中获取资源,而不发送请求到服务器。 - 协商缓存:通过设置
ETag
和Last-Modified
响应头来控制资源的缓存。服务器在响应中返回资源的唯一标识符(ETag)和最后修改时间(Last-Modified)。当浏览器再次请求资源时,会将这些标识符发送给服务器,服务器根据标识符判断资源是否发生了变化。如果资源没有变化,服务器返回304状态码,浏览器直接从缓存中获取资源;如果资源发生了变化,服务器返回新的资源,浏览器将新的资源缓存起来。
- 强缓存:通过设置
- 缓存控制:开发人员可以通过设置响应头来控制资源的缓存行为。常见的响应头包括
Cache-Control
、Expires
、ETag
和Last-Modified
等。Cache-Control
:用于设置缓存的行为,如max-age
(指定缓存的最大有效时间),public
(可被所有用户缓存)、private
(仅限个人用户缓存)、no-cache
(不缓存,每次都向服务器请求)等。Expires
:用于指定资源的过期时间,是一个具体的日期时间。ETag
:用于标识资源的唯一标识符,由服务器生成,用于判断资源是否发生变化。Last-Modified
:用于指示资源的最后修改时间,由服务器设置。
- 缓存清除:浏览器提供了清除缓存的功能,用户可以手动清除缓存,或者开发人员可以通过修改资源的URL或添加版本号等方式来强制浏览器重新下载资源。
对async/await的理解
async/await
是 JavaScript 中处理异步操作的一种语法糖,它基于 Promise 对象,使得异步代码的编写和阅读更加简洁和直观。它使得异步操作的流程控制更加类似于同步代码,提高了代码的可读性和可维护性。
async
关键字用于定义一个函数,表示该函数是一个异步函数,函数内部可以使用 await
关键字来等待一个 Promise 对象的解析结果。当函数执行到 await
关键字时,它会暂停执行并等待 Promise 对象的状态变为 resolved(解决)或 rejected(拒绝),然后继续执行后续代码。
- 异步函数:使用
async
关键字定义的函数被称为异步函数。异步函数内部可以包含异步操作,如异步请求、定时器等。 - 等待 Promise 对象:使用
await
关键字可以等待一个 Promise 对象的解析结果。在等待期间,函数会暂停执行,直到 Promise 对象的状态变为 resolved 或 rejected。 - 返回 Promise 对象:异步函数内部可以使用
return
关键字返回一个值,这个值会被包装成一个 Promise 对象。如果没有显式使用return
返回值,则函数会隐式返回一个 resolved 状态的 Promise 对象。 - 错误处理:在异步函数中,可以使用
try/catch
语句来捕获和处理 Promise 对象的拒绝状态。在try
块中使用await
等待 Promise 对象,如果 Promise 对象被拒绝,则会跳转到catch
块中进行错误处理。
await到底在等什么?
await
关键字用于等待一个 Promise 对象的解析结果。当遇到 await
关键字时,它会暂停当前函数的执行,直到等到的 Promise 对象的状态变为 resolved(解决)或 rejected(拒绝)。
具体来说,await
等待的是一个表达式,这个表达式可以是一个 Promise 对象,也可以是任何返回 Promise 对象的表达式。当遇到 await
关键字时,JavaScript 引擎会暂停当前函数的执行,并将控制权交给调用者,直到等到的 Promise 对象的状态发生变化。
async/await如何捕捉异常?
在使用 async/await
进行异步操作时,可以使用 try/catch
语句来捕捉和处理 Promise 对象的拒绝状态。
具体的步骤如下:
- 在异步函数内部使用
try
关键字开始一个代码块。 - 在
try
块中使用await
关键字等待一个 Promise 对象的解析结果。 - 如果等待的 Promise 对象的状态变为 resolved(解决),则
await
表达式会返回 Promise 对象的解析值。 - 如果等待的 Promise 对象的状态变为 rejected(拒绝),则
await
表达式会抛出一个异常。 - 使用
catch
关键字定义一个代码块,用于捕捉和处理异常。
下面是示例代码,演示如何使用 async/await
捕捉异常:
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Error:', error);
}
}
fetchData();
哪些情况会导致内存泄漏?
未正确清理定时器
:如果创建了一个定时器(setTimeout或setInterval),但没有及时清除它,定时器会一直保持对函数的引用,导致相关的内存无法释放。未正确解除事件监听器
:如果在DOM元素上添加了事件监听器(如click、keydown等),但在元素被移除之前没有解除监听器,那么该元素及其相关的内存将无法被垃圾回收。循环引用
:如果两个或多个对象之间存在相互引用,并且这些对象都不再被使用,那么它们将无法被垃圾回收。这种情况通常发生在对象之间建立了强引用关系,但后续没有及时解除这些引用。大量缓存数据
:如果在JavaScript中使用了缓存机制,但没有限制缓存数据的大小或过期时间,那么缓存数据会不断增加,导致内存占用过高。闭包引用
:闭包是指一个函数可以访问其外部作用域中的变量。如果在函数内部创建了一个闭包,并且该闭包引用了外部作用域中的变量,那么这些变量将无法被垃圾回收,直到闭包被释放。
深拷贝和浅拷贝的区别?
浅拷贝:
- 浅拷贝是指创建一个新对象,新对象的属性值是
原对象的引用
。也就是说,新对象和原对象共享同一块内存地址,修改其中一个对象的属性会影响到另一个对象。 - 浅拷贝只复制了对象的第一层属性,如果原对象的属性是引用类型(如数组、对象等),则新对象仍然引用原对象的属性。
- 浅拷贝通常使用一些简单的方法来实现,如
简单赋值
、扩展运算符
、对象的Object.assign()
方法或数组的slice()
方法。
深拷贝:
- 深拷贝是指创建一个新对象,新对象的属性值是原对象属性值的
完全副本
。也就是说,新对象和原对象拥有不同的内存地址,彼此之间互不影响
。 - 深拷贝会递归复制对象的所有层级,包括对象的属性和属性值。即使原对象的属性是引用类型,深拷贝也会创建一个新的引用类型对象。
- 深拷贝通常需要使用递归或其他复杂的方法来实现,如
JSON 序列化和反序列化
、深度递归遍历
对象、第三方库lodash
等。
如何实现一个深拷贝?
实现一个深拷贝可以使用递归或其他复杂的方法来遍历对象的所有层级,并创建一个新的对象,下面是一个使用递归实现深拷贝的示例:
// 递归深拷贝内部数组和变量
function deepCopy(obj) {
let result = Array.isArray(obj) ? [] : {}
if (obj && typeof obj == 'object') {
for (let key in obj) {
if (obj[key] && typeof obj[key] == 'object') {
result[key] = deepCopy(obj[key])
} else {
result[key] = obj[key]
}
}
return result
} else {
return obj
}
}
//定义一个对象obj
var res = deepCopy(obj)
console.log(obj == res) // false
⭐️需要注意的是,上述的深拷贝函数只能处理普通的对象和数组,对于包含函数、正则表达式、Date 对象等特殊类型的属性,需要根据具体情况进行额外处理。另外,循环引用的情况也需要特别注意,避免进入无限循环的递归。
对原型和原型链的理解
在JavaScript中,每个对象都有一个原型(prototype
)属性,它指向另一个对象,这个对象被称为原型对象
。原型对象可以包含共享的属性和方法,它充当了对象之间共享属性和方法的模板。
原型链是一种通过原型对象链接起来的对象层级结构。当我们访问一个对象的属性或方法时,如果该对象本身没有这个属性或方法,JavaScript引擎会通过__proto__
沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端(即Object.prototype
)。
又是10个☀️☀️,后续持续更新❕ ❕ ❕
个人收集整理、创作不易, 若有帮助🉑, 请帮忙点赞👍➕收藏❤️, 谢谢!✨✨🚀🚀