16

4 阅读27分钟

微任务的优先级并不是大于宏任务,只是当前这一轮的微任务大于宏任务。当前这一轮的宏任务大于下一轮的微任务,

Promise.resolve().then(() => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0)
})

setTimeout(() => {
    console.log(3);
    return Promise.resolve().then(() => {
        console.log(4);
    })
}, 0) // 1, 3, 4, 2

这段代码展示了JavaScript中事件循环和Promise的执行顺序,涉及到宏任务(macro tasks)和微任务(micro tasks)的概念。下面是详细的执行步骤:

  1. 初始执行
    • 整段代码开始执行,首先遇到Promise.resolve().then(),这是一个微任务,会被添加到当前执行栈的微任务队列中。
  1. 第一轮事件循环
    • 当前执行栈为空,开始处理微任务队列。
    • 执行微任务队列中的任务:console.log(1)打印出1。
    • 在微任务的回调中遇到setTimeout,它是一个宏任务,因此将console.log(2)封装的任务添加到宏任务队列。
    • 此时微任务队列为空,第一轮事件循环结束。
  1. 第二轮事件循环开始
    • 开始处理宏任务队列。由于有两个setTimeout设置的宏任务,但这里只关注第一个(实际运行时它们可能交错,但为简化理解,我们按序处理)。
    • 执行第一个setTimeout的回调,console.log(3)打印出3。
    • 在这个宏任务的回调里,又遇到了一个Promise.resolve().then(),这是另一个微任务,因此将console.log(4)的逻辑添加到微任务队列中。
  1. 第二轮事件循环继续
    • 完成当前宏任务后,再次检查微任务队列。
    • 执行微任务队列中的任务:console.log(4)打印出4。
    • 微任务队列此时为空。
  1. 后续事件循环
    • 接下来,如果事件循环继续,会处理剩下的宏任务队列中的任务,即执行第二个setTimeout的回调,打印出2。注意,实际打印顺序可能会因为浏览器或环境的实现差异而略有不同,但根据标准事件循环机制和这段代码的逻辑,理论上应该是先打印4,最后打印2。

总结执行顺序为:1 -> 3 -> 4 -> 2 (根据标准事件循环机制解释,实际环境中可能受到具体实现的细微影响)

Promise.resolve().then(() => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0)
})

setTimeout(() => {
    console.log(3);
    return Promise.resolve().then(() => {
        console.log(4);
    })
}, 10) // 1,2,3,4 
setTimeout(() => {
    console.log(0);
}, 1)


Promise.resolve().then(() => {
    console.log(1);
    setTimeout(() => {
        console.log(2);
    }, 0)
})

setTimeout(() => {
    console.log(3);
    return Promise.resolve().then(() => {
        console.log(4);
    })
}, 0)  // 1 0 3 4 2

对对象或数组的复杂数据类型去重

const a = [1, 4, 3, 1]
console.log(Array.from(new Set(a))); // 1,4,3

const obj = {
    a: 10,
    b: 20,
    a: 10,
}
const newObj = JSON.stringify(obj) // string,  {"a":10,"b":20}
const trueOBj = JSON.parse(newObj) // { a: 10, b: 20 }
console.log(newObj, trueOBj);


const b = [1, 2, 3, 1]
const aa = JSON.stringify(b) // string, [ 1, 2, 3, 1 ]
const bb = new Set(aa) // Set(6) { '[', '1', ',', '2', '3', ']' }

css实现,子元素宽度超出父元素宽度,用省略号代替

HTML结构示例:

<div class="parent">
  <div class="child">
    这是一段很长很长的文本,它将会超出其父元素的宽度,我们希望超出部分能用省略号表示...
  </div>
</div>

CSS样式:

.parent {
  width: 300px; /* 父元素固定宽度 */
  overflow: hidden; /* 隐藏超出部分 */
  white-space: nowrap; /* 强制文本在同一行显示 */
}

.child {
  text-overflow: ellipsis; /* 超出部分用省略号显示 */
  overflow: hidden; /* 隐藏超出容器的内容 */
  display: block; /* 确保是块级元素或inline-block */
}

这段CSS代码的工作原理是:

  • white-space: nowrap;确保文本不会换行,始终保持在一行内。
  • overflow: hidden;在.parent和.child中都使用,用于隐藏超出容器边界的内容。
  • text-overflow: ellipsis;在.child中使用,当内容溢出时,用省略号代替溢出的部分。
  • 确保.child是一个块级元素(如使用display: block;或display: inline-block;),这样才能应用上述的溢出处理样式。

这样设置后,当.child内的文本内容宽度超过.parent所设定的宽度时,超出部分就会被替换为省略号。

前端中的硬件加速

前端中的硬件加速,通常指的是利用图形处理器(GPU)而不是仅依赖于中央处理器(CPU)来执行某些图形渲染和动画计算任务,以此来提高网页或Web应用程序的性能和流畅度。这一技术对于提升用户界面的交互体验特别重要,尤其是在处理复杂的动画、3D变换、视频播放和大量图形渲染等场景下。

硬件加速的工作原理

  1. 分担计算任务:CPU负责程序的逻辑运算和一般的任务处理,而GPU专为并行计算和图形处理设计,能高效地处理大量的像素和纹理数据。通过硬件加速,可以将一些计算密集型的任务(如图形渲染)从CPU转移到GPU上执行。
  2. 合成器和光栅化:现代浏览器使用了合成器和光栅化技术来加速渲染。合成器负责管理图层,并决定哪些部分需要重绘或重新布局。光栅化是将矢量图形转换为像素的过程,这一步骤也可以由GPU加速完成。
  3. CSS硬件加速:在CSS中,某些属性能够触发硬件加速,例如transform、opacity和filter。当这些属性应用于元素时,浏览器可能会将该元素提升至独立的复合图层,并利用GPU进行渲染。例如,使用translate3d(0,0,0)或will-change属性可以提示浏览器对元素进行硬件加速。
  4. WebGL:WebGL是一种可以在网页上绘制3D图形和创建复杂动画的技术,它直接与GPU通信,允许开发者利用硬件加速功能来创建高性能的图形应用。

优缺点

优点

  • 提升性能:减轻CPU负担,提供更流畅的用户体验。
  • 改善动画和滚动效果:减少卡顿,使页面更加响应迅速。
  • 支持复杂图形处理:对于3D图形和大量图像操作尤为重要。

缺点

  • 资源消耗:过度使用硬件加速可能导致更高的内存和电池消耗。
  • 兼容性问题:不同的设备和浏览器对硬件加速的支持程度不一。
  • 可能的渲染异常:在某些情况下,硬件加速可能导致视觉上的小问题,如图层混合异常。

在实施硬件加速时,前端开发者需要权衡性能提升与潜在的资源消耗,合理地应用硬件加速策略,以达到最佳的用户体验。

weakmap

WeakMap是JavaScript中一种特殊的键值对集合数据结构,它与常规的Map相似,但存在几个关键的区别,主要在于其所持对象的引用方式。以下是关于WeakMap的一些核心特性:

  1. 弱引用(Weak Reference) :WeakMap中的键是弱引用的。这意味着如果某个对象作为WeakMap的键,而没有其他地方引用这个对象,那么垃圾回收机制可以回收这个对象,即使它还在WeakMap中作为键。相比之下,普通的Map会维持对键的强引用,阻止垃圾回收机制回收这些键对象。
  2. 不可遍历:WeakMap实例是不可遍历的,也就是说没有像Map那样的forEach方法,也不能用for...of循环遍历。这是因为它的设计初衷是为了关联数据而尽量不影响垃圾回收,避免因遍历导致本应被回收的对象继续留在内存中。
  3. 私有性:由于上述特点,WeakMap常用于实现数据的私有性。例如,在类的实现中,可以用WeakMap存储那些不希望外部访问的实例属性,从而模拟私有字段。
  4. 应用场景:WeakMap非常适合用来关联额外的数据到DOM元素或其他对象上,而又不用担心这些数据会阻止目标对象被垃圾回收。例如,在一些库或框架中,可能需要跟踪元素上的附加信息,但又不想因这种跟踪影响到正常的内存管理。

WeakMap的键必须是对象类型,包括普通对象、数组、函数等。它不能是基本数据类型,如字符串、数字、布尔值等。这一限制是因为WeakMap设计为通过引用计数来管理其键,只有对象类型才能被引用计数,从而实现弱引用的特性。如果键不是对象,就无法应用弱引用机制,因此基本类型被排除在外作为WeakMap的键。

option请求

在HTTP协议中,OPTIONS请求是一种预检请求(preflight request),主要用于跨域资源共享(Cross-Origin Resource Sharing, CORS)的场景中。当浏览器需要确认是否有权限请求一个不同源的资源时,会首先自动发送一个OPTIONS请求到服务器,询问服务器是否允许接下来的实际请求发生。这个过程遵循CORS机制,目的是为了安全地控制资源的访问权限,防止恶意网站进行跨站请求伪造(CSRF)等攻击。

OPTIONS请求的主要作用包括:

  1. 检测跨域权限:浏览器在发送实际的GET、POST、PUT等请求之前,如果请求满足预检条件(比如使用了自定义的HTTP头或特定的HTTP方法),会先发送一个OPTIONS请求,询问服务器是否支持跨域请求。
  2. 获取服务器允许的请求方法:服务器通过Access-Control-Allow-Methods响应头告诉浏览器,对于这个资源,允许使用哪些HTTP方法(如GET、POST等)进行请求。
  3. 检查其他CORS相关头部:服务器还可以通过其他CORS相关的响应头,如Access-Control-Allow-Headers、Access-Control-Allow-Origin等,来告知浏览器允许的请求头、来源等信息。
  4. 无副作用:OPTIONS请求本身应该是幂等的,意味着它不应该对服务器上的资源产生任何影响,仅仅用于获取信息。

客户端(通常是浏览器)会根据OPTIONS请求的响应来判断是否继续发起实际的请求。如果服务器不允许该跨域请求,浏览器将不会发送实际请求,从而保护用户的隐私和安全。

简单请求和复杂请求

在跨域资源共享(CORS)的背景下,HTTP请求被分为两类:简单请求(Simple Request)和复杂请求(Complex Request,也称为预检请求或非简单请求)。

简单请求(Simple Request)

简单请求是指那些不会触发浏览器跨域预检(OPTIONS请求)的请求。它们满足以下条件:

  1. 使用下列方法之一:GET、HEAD、POST。
  2. HTTP头限制:除了标准的缓存控制、用户代理、以及其他几种不携带个人信息的头外,不包含自定义的HTTP头。
  3. Content-Type限制(如果发送了Content-Type头):只能是application/x-www-form-urlencoded、multipart/form-data或text/plain这三种中的一种。

对于简单请求,浏览器会直接发送请求到服务器,并在请求头中带上Origin字段,服务器根据CORS策略决定是否允许该请求。如果允许,会在响应头中包含Access-Control-Allow-Origin等CORS相关的字段。

复杂请求(Complex Request)

不符合简单请求条件的跨域请求被视为复杂请求。这类请求在实际发送数据之前,会先发送一个 OPTIONS 请求作为预检请求(Preflight request),以询问服务器是否允许这样的跨域请求。预检请求会包含以下信息:

  • 使用OPTIONS方法。
  • 包含特殊头Origin,表示请求来源。
  • 可能包含特殊头Access-Control-Request-Method,指示实际请求将使用的HTTP方法(如PUT、DELETE等)。
  • 可能包含Access-Control-Request-Headers头,列出将伴随实际请求一起发送的非默认头信息。

服务器根据预检请求返回的响应中,通过Access-Control-Allow-Methods、Access-Control-Allow-Headers等头来告知客户端允许的请求方法、头信息等。如果预检请求得到许可,浏览器才会继续发送实际的请求。

简单来说,简单请求不需要预检,直接进行;复杂请求则需要先进行预检,得到服务器许可后才继续。

finally

Promise.finally() 是 JavaScript 中 Promise 对象的一个方法,它用于指定一个回调函数,无论这个 Promise 最终的状态是 fulfilled(已成功)还是 rejected(已失败),该回调函数都会被执行。这意味着,finally 提供了一个统一处理诸如清理操作、资源释放或通知用户等场景的便捷方式,而无需在 then 和 catch 方法中重复相同的代码。

基本语法

promise.finally(callback);
  • promise: 是你想要在其最终操作后执行某些功能的 Promise 对象。
  • callback: 是一个函数,当 Promise 结束(不论是 resolved 还是 rejected)后会被调用。这个函数不接受任何参数,因为它主要用来执行那些不依赖于 Promise 结果的操作。

例子

let promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("操作成功");
  }, 1000);
});

promise.then(result => {
  console.log(result); // 输出: "操作成功"
}).catch(error => {
  console.error(error);
}).finally(() => {
  console.log("无论成功还是失败,这里都会执行");
});

在这个例子中,不论 promise 是成功解决还是失败拒绝,finally 中的回调函数都会执行,打印出 "无论成功还是失败,这里都会执行"。这对于执行一些必要的清理工作非常有用,比如关闭加载指示器、释放不再需要的资源等。

注意事项

  • 如果 finally 中的回调抛出了异常,这会导致一个新的处于 rejected 状态的 Promise 被返回,但这不会改变原始 Promise 的状态。
  • finally 不会影响 Promise 链的结果传递,即它不会改变前一个 Promise 处理结果,后续的 then 或 catch 依然能接收到未被 finally 干扰的结果。

总之,Promise.finally() 提供了一种更加优雅和简洁的方式来处理那些与 Promise 结果无关但又必须执行的逻辑。

console.log(14 > 9); // true
console.log('14' > '9'); // f
console.log('14' > 9); // true
console.log(14 > '9'); // t

git stash

git stash 是一个非常实用的Git命令,它允许你将当前工作目录中未提交的改动(包括已添加到暂存区的和未添加的改动)临时保存起来,从而使得工作目录恢复到最近的一次提交状态。这对于需要快速切换到另一个分支、清理工作目录或者在不提交当前改动的情况下查看项目状态等情况非常有用。下面是 git stash 常用的一些子命令及其解释:

解释下git status

git status 命令在 Git 中用于检查仓库的当前状态。当你在一个 Git 仓库中运行这个命令时,它会提供以下几种信息:

  1. 当前分支:它会告诉你当前你在哪个分支上工作。
  2. 暂存区状态:暂存区(也称为索引)是 Git 中的一个临时区域,保存着你准备在下次提交中包含的文件的快照。git status 将显示哪些文件已经被暂存(即使用 git add 命令标记的文件),准备在下次提交时加入到仓库的历史记录中。
  3. 工作目录状态:工作目录是你在其中工作的实际文件系统目录。git status 将显示哪些文件在工作目录中有未暂存的更改,这意味着这些文件自从最后一次提交后已被修改,但还没有使用 git add 添加到暂存区。

push, pop, some, every

const arr = [9, 2, 7]
console.log(arr.push(8));  // 4, 返回新的数组的长度
console.log(arr.pop()) // 7, 返回扔掉的值。


const arr = [9, 2, 7]
const bb = arr.filter((item) => item % 2 === 1)
console.log(arr); // [9,2,7]
console.log(bb); // [9,7]
const cc = arr.find((item) => item % 2 === 1)
console.log(cc); // 9
const dd = arr.findIndex((item) => item % 2 === 1)
console.log(dd);  // 0

const ii = arr.some((item) => item % 2 === 1)
console.log(ii); // true
const uu = arr.every((item) => item % 2 === 1)
console.log(uu); // false




const aa = [1, 2, 3, 4]
console.log(aa.slice(1, 3)); // [2,3]
console.log(aa.splice(1, 3)); // [2,3,4]
console.log(aa); // [1]



slice === substring // slice(start, end) 左闭右开

splice === substr   // splice(start, length)
const a = b = 1
console.log(`a: ${typeof a}, b: ${typeof b}`); // a: number, b: number	
const arr = [1, 2, 3]
let map = arr.map((value) => value % 2 === 1)
let filter = arr.filter((value) => value % 2 === 1)
let find = arr.find((item) => item % 2 === 1)

console.log(map, filter, find); // [true, false, true], [1,3], 1


console.log(1 + '1'); // '11', string
console.log('11' - 1); // 10, 是Number
console.log(11 - '1'); // 10, 是Number
const obj = {
    a: 1,
    b: 2
}
console.log(Object.entries(obj)); // [ [ 'a', 1 ], [ 'b', 2 ] ]
// 将对象所有可枚举的键值对组合成数组


const map = new Map()
map.set('a', 1)
map.set('b', 2)
console.log(Array.from(map)); // [ [ 'a', 1 ], [ 'b', 2 ] ]

1千万个数据去重

对于处理一千万条数据的去重问题,可以采用以下几种策略:

  1. 哈希表(HashSet 或 HashMap) : 利用哈希表的特性,可以高效地进行数据去重。将数据逐条读入,以数据本身(或者其唯一标识,如身份证号)为键存储到哈希表中。由于哈希表的查找、插入和删除操作平均时间复杂度接近O(1),因此这种方法非常高效。最后,哈希表中存储的元素数量就是去重后数据的数量。
  2. MD5 或其他哈希算法: 对于数据内容较大的情况,可以先计算每条数据的MD5或其他哈希值(如SHA系列),然后使用哈希表进行去重。这种方法适用于数据内容可能不完全相同,但经过哈希处理后认为是“重复”的情况。注意,尽管MD5抗碰撞能力较强,但在极大量数据下仍有可能发生哈希碰撞,因此在某些场景下可能需要额外的校验步骤。

weakmap的弱引用表现为什么

WeakMap的弱引用特性表现为,当WeakMap中作为键的对象没有任何其他引用时,垃圾回收机制(GC)会自动回收这个键对象及其关联的值,而不需要额外的手动清理工作。这意味着WeakMap中的键并不阻止其引用的对象被垃圾回收,即使WeakMap本身仍然存在对该键的引用。

具体来说,弱引用有以下几点表现:

  1. 不阻止垃圾回收:如果一个对象仅被WeakMap引用,而没有其他强引用指向它,那么在进行垃圾回收时,这个对象会被视为不再可达,进而被回收。相比之下,普通Map中的键是强引用,会阻止对象被回收,即使没有其他地方使用该对象。
  2. 自动清理:由于弱引用的这一特性,使用WeakMap可以有效避免内存泄漏。对于那些生命周期不确定且希望在不再需要时自动释放资源的对象来说,WeakMap是一个很好的选择。
  3. 不可枚举性:WeakMap不像普通的Map或对象那样可以被遍历,也无法通过for...in循环或Object.keys/values/entries方法访问其内容。这进一步体现了WeakMap设计上对隐私和非干涉性的重视,防止外部代码干扰其内部数据。
  4. 用途:WeakMap常用于存储与对象关联的信息,而不用担心这些信息会阻止对象的垃圾回收。例如,它可以用来存储DOM元素与其相关数据的映射,当DOM元素被移除时,相关的数据也会自动被回收,无需担心内存泄露。

Redux Thunk: 用来处理异步操作,允许action creator返回一个函数而不是普通对象。这个函数会接收dispatchgetState作为参数,可以用来调度多个action,执行异步操作如API调用等。

如何实现setTimeout

使用requestAnimationFrame让浏览器在下一次重绘前再次检查

requestAnimationFrame通常更适合于动画和性能敏感的场景,因为它与屏幕刷新率同步

setTimeout和requestAnimationFrame记时哪个更准确

在JavaScript中,requestAnimationFrame相比于setTimeout在实现动画和需要更高时间精确度的场景下通常更为准确。

  1. requestAnimationFrame
    • 设计初衷是为了动画渲染,它会在浏览器下一次重绘之前调用回调函数。
    • 与显示器的刷新率同步(通常为60Hz,即大约每16.7ms调用一次),因此能确保动画平滑且不会浪费不必要的CPU周期。
    • 浏览器会自动优化调用时机,例如,当标签页被隐藏时,大多数浏览器会暂停requestAnimationFrame回调的执行,从而节省资源。
    • 适合于执行与UI渲染相关的定时任务。
  1. setTimeout
    • 是一个更为通用的延迟执行函数,可以设置具体的延迟时间(以毫秒为单位)。
    • 不保证精确的执行时间,实际执行可能会因为页面上的其他脚本执行或页面重新布局等因素而延迟。
    • 最小延迟时间通常不会少于4ms(在一些浏览器中可能是10ms或更多),而且实际触发时间可能有较大波动。
    • 更适用于那些对时间精度要求不高,或者不需要与浏览器渲染循环同步的定时任务。
    • 比如setTimeout的时间设置成4s,不是说4s后就一定会执行回调函数,而是说4s后会将回调函数推进执行栈里执行。但是有可能4s后执行栈并不是空的,这时候就要等执行栈空了才会推送,记时就不准确了。

总结来说,如果你的目标是创建流畅的动画或者需要利用浏览器的最佳绘制时机,requestAnimationFrame是更佳的选择。而对于需要在特定时间后执行一次性任务,或者执行周期性的但对时间精度要求不高的任务,setTimeoutsetInterval可能就足够了。

在HTTP/1.1中,虽然引入了长连接(Keep-Alive)特性,允许在一个TCP连接上发送多个请求和接收多个响应,但是这些请求和响应仍然是顺序处理的。这意味着在网络请求层面,并不是真正的并行执行。具体来说:

第一条请求发送之后,可以在服务器完全响应第一条请求之前就开始发送第二条乃至后续的请求。也就是第二条可以在第一条发送了以后就发送,即使第二条早到了服务器,也必须等待第一条到达服务器并响应完,才能处理第二条。

  • 然而,服务器处理这些请求时,仍然会遵循先进先出的原则,即第一个请求的响应必须开始传输后,第二个请求的响应才能开始,依此类推。这就导致了所谓的“队头阻塞”(Head-of-line blocking)现象:如果前面的请求响应慢,后面的请求即使已经准备好也会被阻塞,等待前面的请求完成。

解释下http1.0的短连接,1.1的长连接,2.0的多路复用。 这三者的区别

HTTP协议的不同版本在连接管理上有显著差异,这些差异影响着网页加载速度、服务器资源利用率及整体性能。下面是HTTP 1.0的短连接、HTTP 1.1的长连接,以及HTTP/2的多路复用三个概念及其区别:

HTTP 1.0的短连接

在HTTP 1.0中,默认模式是短连接(也称作非持久连接)。这意味着每个HTTP请求和响应完成后,客户端和服务器之间的TCP连接就会关闭。如果一个网页包含多个资源(如图片、CSS文件、JavaScript文件等),浏览器必须为每个资源单独建立一个新的TCP连接。这导致了几个问题:

  • 延迟增加:每次连接都需要经历TCP的三次握手过程,增加了延迟。
  • 资源消耗:频繁的建立和销毁连接消耗了额外的服务器资源。
  • 降低效率:每个请求都需要新的连接,无法利用已有的连接进行快速传输。

HTTP 1.1的长连接

为了解决短连接带来的问题,HTTP 1.1引入了持久连接(长连接)的概念。在HTTP头中通过Connection: keep-alive指示,客户端和服务器之间的TCP连接可以在多个请求之间保持打开状态。这带来了以下好处:

  • 减少延迟:由于减少了TCP握手的次数,提高了响应速度。
  • 提高效率:多个请求可以复用同一个连接,减少连接建立和关闭的开销。
  • 更好的资源利用率:服务器可以服务更多的客户端,因为连接不再频繁关闭。

然而,HTTP 1.1的长连接依然存在一些问题,比如“队头阻塞”现象,即在一个连接上,如果有请求没有完成,后续的请求必须等待,即使它们可以并行处理。

HTTP/2的多路复用

HTTP/2解决了HTTP 1.x中的“队头阻塞”问题,通过引入多路复用技术。多路复用允许在一个单一的TCP连接上同时处理多个请求和响应,每个请求和响应都是独立的流。这意味着:

  • 更高的并发性:多个请求和响应可以同时发送和接收,无需等待,极大地提高了页面加载速度。
  • 资源优化:仍然保持了长连接的优点,同时避免了多个TCP连接的开销。
  • 更细粒度的控制:HTTP/2允许优先级分配,让更重要的资源优先传输,提高了用户体验。

总结来说,从HTTP 1.0的短连接到HTTP 1.1的长连接,再到HTTP/2的多路复用,HTTP协议在逐步优化网络资源的使用效率和提升用户体验。每一代升级都针对前一代存在的问题进行了改进,特别是在减少延迟、提高并发处理能力和资源利用率方面。

1.0和1.1都是单路的,也就是只有一条数据流,传递的数据只能在这一条数据流里进行传递。 而2.0是多路服用的,也就是有多条数据流,传递的数据可以在这多条流里并行传递多条数据,可以这样理解吗

是的,您的理解是正确的。

在HTTP 1.0和1.1中,虽然HTTP 1.1引入了长连接,允许在一个连接上连续发送多个请求和接收多个响应,但这仍然是基于单个请求/响应模型的序列化处理。这意味着即使是在一个保持活动的连接上,请求和响应依然是按顺序进行的。如果有多个请求同时发出,它们必须排队等待前一个请求完成之后才能开始传输,这就是所谓的“队头阻塞”现象。

而HTTP/2通过多路复用技术彻底改变了这一点。在HTTP/2中,虽然仍使用一个TCP连接,但这个连接可以同时承载多个双向数据流。每个数据流对应一个请求-响应对,这些数据流是独立的,可以并行发送和接收数据,互不影响。这意味着即使某个数据流(请求-响应对)遭遇延迟,也不会阻塞其他数据流的传输。因此,HTTP/2在单个连接上实现了更高的并发性和效率,大大减少了页面加载时间和提高了用户体验。

http2.0也存在对头堵塞吗

HTTP/2通过多路复用(Multiplexing)机制显著减少了传统HTTP/1.x中的“队头阻塞”(Head-of-Line Blocking,HoL blocking)问题,但并不能说完全消除了所有形式的阻塞。在HTTP/2中,多个请求和响应可以在同一个TCP连接上并行传输,这意味着单个请求或响应的延迟不会阻塞其他请求的发送或接收,这是对HTTP/1.x的重大改进。

然而,在某些特定情况下,HTTP/2仍可能遇到轻微的阻塞现象,尽管这与HTTP/1.x中的队头阻塞不同。例如:

  1. 流控制(Flow Control) :HTTP/2实现了流控制,接收端可以根据自己的处理能力控制发送端的发送速率。如果一个流因为接收端资源限制被暂停,虽然不会阻止其他流的数据传输,但这仍是一种形式上的“局部阻塞”。
  2. 优先级相关的阻塞(Priority Blocking) :虽然HTTP/2允许为各个请求分配优先级,以优化资源加载顺序,但在极端情况下,高优先级的请求可能“饿死”低优先级的请求,如果高优先级请求占用带宽过大,低优先级的请求可能会经历显著的延迟。
  3. TCP拥塞控制:虽然这不是HTTP/2协议本身的问题,但在网络层面上,如果TCP连接遭遇拥塞,可能会影响所有通过该连接传输的数据,间接影响HTTP/2的性能。

总的来说,HTTP/2在设计上极大降低了传统意义上的队头阻塞问题,但仍然存在一些潜在的阻塞场景,这些更多是与实现细节、网络状况以及资源管理相关。

chrome浏览器对同一时间内的网络请求书有限制吗?

Chrome浏览器确实对同一时间内发起的网络请求数量有所限制,这是为了防止资源滥用,保护服务器免受过大压力,同时也考虑到了用户体验和系统资源管理。具体限制的数量可能会随着浏览器版本、操作系统以及用户的网络状况有所不同,但一般来说,Chrome会对每个域名的并发请求数量设置一个上限。

历史上,根据HTTP/1.1规范的精神,大多数浏览器默认对单个域名的并发请求数量设置了约6-8个的限制。然而,随着技术进步和HTTP/2及HTTP/3的引入,这些限制有了变化,因为这些新协议支持多路复用,理论上可以在单个TCP连接上处理更多的并发流,从而可能绕过了早期的严格并发请求限制。

在离线情况下,本地可以拿到强制缓存吗

在离线情况下,如果网页或资源已经被浏览器强制缓存过,那么在没有网络连接时,理论上是可以从本地缓存中获取这些资源的。强制缓存是通过HTTP响应头中的特定指令来实现的,如Cache-ControlExpires头部字段,这些指令告诉浏览器资源可以被缓存多久以及何时应该认为它们过期。

当浏览器加载一个页面或资源时,它首先会在本地缓存中查找是否有相应且未过期的缓存副本。如果有,并且缓存策略允许(即设置了强制缓存指令),浏览器就会直接使用这个本地副本,而不会尝试从网络重新下载。这种情况下,即使设备处于离线状态,只要缓存还在有效期内,就能成功加载这些资源。

例如,使用Cache-Control: max-age=3600指令意味着资源可以被缓存一个小时,在这期间,即使用户离线,浏览器也能提供缓存中的版本。同样,Expires头部也可以设置一个具体的过期日期和时间,实现类似的效果。