跨浏览器兼容问题
现代前端开发中,跨浏览器兼容问题主要集中在Chrome(Blink)、Firefox(Gecko)、Safari(WebKit)、Edge(Blink) 及移动端 iOS Safari/Android Chrome 的特性差异上
1、CSS布局、渲染类
问题1:Safari下 flex:1 子元素宽度未占满父容器
PC端Chrome(Blink)、Firefox(Gecko)子元素用 flex:1 可占满剩余宽度,但Safari中子元素宽度仅包裹内容,未拉伸(原因:Safari对flex缩写的解析差异,flex:1等价于flex-grow: 1; flex-shrink: 1; flex-basis: auto(Chrome 中 flex-basis 默认 0%),auto 会导致子元素以自身内容宽度为基准,而非父容器剩余宽度)
解决方案:显示声明flex-basis: 0%,将 flex: 1 改为 flex: 1 1 0%;
/* 修复前 */
.flex-item { flex: 1; }
/* 修复后 */
.flex-item { flex: 1 1 0%; /* 或 flex-grow: 1; flex-basis: 0%; */ }
问题2:Safari 下 min-height 继承失效(嵌套 Flex 场景)
父容器设置 min-height: 100vh + display: flex + flex-direction: column,子元素用 flex: 1 期望占满剩余高度,但 Safari 中子元素高度仅包裹内容,Chrome/Firefox 正常(原因:Safari 对 min-height 结合 Flex 布局的高度计算逻辑差异 —— 父容器 min-height 不会被 Flex 子元素继承为 “可用高度”,而 Chrome/Firefox 会将 min-height 视为 Flex 容器的基准高度)
解决方案:父容器补充 height: 100vh(若允许固定高度),或用 height: -webkit-fill-available 适配 Safari;1. 嵌套 Flex 时,给中间层容器也设置 display: flex + flex-direction: column;
/* 父容器:修复Safari min-height继承问题 */
.parent {
display: flex;
flex-direction: column;
min-height: 100vh;
height: -webkit-fill-available; /* Safari 专属适配 */
height: 100vh; /* 现代浏览器兜底 */
}
/* 子元素:占满剩余高度 */
.child {
flex: 1 1 0%;
}
问题3:Safari下overflow: scroll 滚动条占用宽度导致布局错位
Chrome/Firefox 的滚动条默认 “悬浮” 不占用容器宽度,但 Safari(桌面端)的滚动条会占用固定宽度(约 15px),导致带滚动的容器宽度比预期小,布局错位(原因:Safari 未实现滚动条 “overlay” 模式,默认使用 “classic” 模式(占用宽度))
解决方案:自定义 Safari 滚动条样式,缩窄滚动条宽度;为容器预留滚动条宽度,或用 padding-right 补偿
/* 自定义Safari滚动条,减少宽度占用 */
.scroll-container::-webkit-scrollbar {
width: 6px; /* 缩窄滚动条 */
height: 6px;
}
.scroll-container::-webkit-scrollbar-thumb {
background-color: #e0e0e0;
border-radius: 3px;
}
/* 兜底:为容器预留滚动条宽度 */
.scroll-container {
padding-right: 6px;
}
问题4:Firefox 下 Grid 布局 minmax() 计算异常
Grid 布局中用 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) 实现自适应列数,Chrome/Edge 中列宽会自动适配,但 Firefox 中列宽固定为 200px,未拉伸至 1fr(原因:Firefox 对 minmax() 结合 auto-fit/auto-fill 的解析逻辑差异,当容器宽度足够时,未触发 1fr 的拉伸逻辑)
解决方案:添加 width: 100% 强制容器宽度,或用 flex 替代 Grid 实现自适应(Firefox 对 Flex 支持更稳定)
/* 修复前 */
.grid-container {
display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
}
/* 修复后 */
.grid-container {
display: grid;
width: 100%; /* 强制容器宽度 */
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
/* 兜底:Firefox 低版本兼容 */
@supports (-moz-appearance: none) {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}
问题5:Firefox 下 box-sizing: border-box 对 <input> 无效
给 <input type="text"> 设置 width: 100% + padding: 0 10px + box-sizing: border-box,Chrome/Safari 中输入框宽度刚好占满父容器,但 Firefox 中输入框宽度超出父容器(padding 被计入宽度外)(原因:Firefox 对表单元素的 box-sizing 初始值为 content-box,且默认不继承父元素的 box-sizing 配置,而 Chrome/Safari 中表单元素会继承 border-box)
解决方案:显式为表单元素设置 box-sizing: border-box,并统一重置所有表单控件的盒模型;
/* 全局重置表单元素盒模型 */
input, select, textarea, button {
box-sizing: border-box;
-moz-box-sizing: border-box; /* Firefox 低版本兜底 */ }
/* 具体输入框样式 */
.input {
width: 100%;
padding: 0 10px;
border: 1px solid #ccc;
}
2、定位与层叠类
问题1:Safari 下 position: sticky 失效(多场景触发)
设置 position: sticky + top: 0 的元素,在 Chrome/Firefox 中滚动时可固定,但 Safari 中直接随页面滚动,无粘性效果(原因:Safari 对 sticky 有严格的生效条件,常见触发失效的场景:1.父容器设置了 overflow: hidden/auto/scroll(嵌套滚动容器会阻断 sticky);2.父容器高度为 auto 且无明确的 “滚动高度”;3:元素自身有 float 属性;)
解决方案:1. 移除 sticky 元素父容器的 overflow 非默认值;2. 确保 sticky 元素的父容器有明确的高度(或滚动高度);3. 移除元素的 float 属性,改用 Flex 布局;
/* 错误示例:父容器overflow: hidden导致sticky失效 */
.sticky-parent {
overflow: hidden; /* Safari 下阻断sticky */
}
.sticky-element {
position: sticky;
top: 0;
}
/* 修复后 */
.sticky-parent {
overflow: visible; /* 恢复默认值 */
height: 100%; /* 确保父容器有滚动高度 */
}
.sticky-element {
position: sticky;
top: 0;
float: none; /* 移除float */
}
问题2:Firefox 下 z-index 层叠异常(Flex/Grid 容器内)
Flex/Grid 容器内的子元素设置 z-index,Chrome/Safari 中层叠顺序符合预期,但 Firefox 中 z-index 失效(元素被同级覆盖)(原因:Firefox 对 Flex/Grid 子元素的 z-index 解析逻辑差异 ——Flex/Grid 子元素默认是 “定位容器”,Chrome/Safari 中 z-index 直接生效,而 Firefox 中需显式设置 position(如 relative)才生效。)
解决方案:给 Flex/Grid 子元素补充 position: relative,确保 z-index 生效;
/* 修复前:Firefox下z-index失效 */
.flex-item {
z-index: 10;
}
/* 修复后 */
.flex-item {
position: relative; /* 显式定位 */
z-index: 10;
}
跨浏览器开发
1.核心目标
跨浏览器开发的核心目标,是让网页在不同浏览器(Chrome/Firefox/Safari/Edge/IE)、不同版本、不同设备(PC / 移动端)下表现一致且功能正常
需要重点关注的维度:
(1)渲染引擎差异(最核心)
不同浏览器基于不同渲染引擎(Chrome/Edge→Blink、Firefox→Gecko、Safari→WebKit、IE→Trident),导致CSS渲染、HTML解析行为不同:
- CSS前缀兼容:私有前缀(
-webkit-/-moz-/-ms-/-o-),transform/transition/border-radius,可以通过Autoprefixer 自动处理; - 盒模型差异:IE6/7在怪异模式下盒模型为border-box(容器宽度包括width+border+padding),标准模式为content-box(容器宽度为width),需要通过box-sizing统一;
- 布局兼容性:
- Flex/Grid在低版本浏览器(如IE10-)支持不全,需要降级方案(使用float)
- 浮动、定位的细微差异(如IE的hasLayout问题) hasLayout是IE6/7独有的布局上下文机制,未触发时会导致浮动、定位渲染错乱; 解决办法:用zoom:1或者overflow:hidden,主动触发元素的hasLayout=true;全局规避:统一触发hasLayout,减少零散hack(* {* zoom:1;针对IE6/7的全局布局重置},或者仅针对块级容器,div,p,ul,li,dl,dt,dd{zoom:1})
浮动的兼容性问题(父元素高度塌陷、IE6双倍margin、浮动元素错位/重叠、文字环绕异常)
解决方法(除了hasLayout外):清除浮动(浮动子元素后面添加额外的标签clear:both,不建议;伪元素清除法,模拟空标签.clearfix::after{content:'';display:block;clear:both;height:0;visibility:hidden;},是现代布局的首选;overflow法,给父元素设置overflow:hidden/auto,既触发IE6/7的hasLayout,又触发现代浏览器的BFC(块级格式化上下文),自动包裹浮动元素,但hidden会隐藏超出部分,部分场景不适用)
IE6浮动元素双倍margin问题:margin-left/right会被双倍计算
解决办法:除了设置display:inline,还可以改为用padding替代margin;负margin抵消;
浮动元素错位问题:浮动元素未设置宽高,或行内元素混排,易错位
解决办法:给浮动元素显示设置宽高;行内元素与浮动元素混排时,设置vertical-align:top避免基线对齐异常
定位兼容性问题:IE6不支持fixed、绝对定位参考系错误、z-index失效、定位元素溢出/偏移异常
解决办法:IE专属fixed模拟(利用expression),会影响性能,仅在必要时使用;嵌套绝对定位:给body或html标签设置overflow:auto,父容器绝对定位,模拟fixed效果;IE6/7中,若父元素未设置position:relative/absolute/fixed,绝对定位元素会错误的以body为参考系,需要给定位元素的父容器设置position:relative;解决z-index失效:给父元素设置z-index:1,建立独立的堆叠上下文;定位元素偏移异常:重置默认样式,给定位元素设置margin:0;padding:0;
3. safari对overflow:scroll的滚动样式默认占用宽度,需要单独适配
通过 WebKit 私有属性自定义滚动条样式,使其从「占用宽度」变为「悬浮式」(和 Chrome 一致),彻底解决布局问题:
完整兼容样式(Safari + Chrome 通用)
- 样式重置/归一化 使用 Reset.css(重置所有默认样式)或 Normalize.css(统一默认样式,保留有用的默认值)
(2)JavaScript语法/API兼容
- ES6+语法兼容:低版本浏览器(IE、老版Safari)不支持箭头函数、let/const\promise等,需要Babel转译,配合core-js/polyfill补充API;
- DOM API差异:IE(低版本)不支持querySelectorAll、classList,需要使用getElementsByClassName或者手动封装;Safari对Date.parse的兼容(不支持某些日期格式);事件对象差异:IE的event.srcElement vs 标准event.target,attachEvent vs addEventListener;
- 内置对象/方法差异:IE的ActiveXObject vs 标准XMLHttpRequest(AJAX兼容);Array.prototype.includes、Object.assign等方法在低版本缺失;
- Cookie/存储差异:IE的localStorage支持不全,需要降级到Cookie;
- Canvas/SVG兼容:部分API在不同浏览器实现不一致(如SVG的viewBox);
(3)事件模型兼容
- 事件绑定:标准addEventListener vs IE的attachEvent(需要封装兼容函数)
- 事件冒泡/捕获:IE仅支持冒泡、无捕获阶段;
- 鼠标/键盘事件:IE的event.button值与标准不同,keydown事件的keycode映射差异;
- 触摸事件:移动端Safari的touchstart/touchend与Chrome的细微差异,需要注意300ms延迟(可以通过meta viewport关闭);
(4)表单/交互兼容
- 表单控件样式:不同浏览器的input/button/select默认样式差异大,需要重置或自定义;
- 表单验证:HTML5表单验证(required/pattern)在IE中不支持,需要JS手动验证;
- 文件上传:IE的input[type=file]不支持多文件、拖拽上传;
- 弹窗/新窗口:IE的windoe.open参数差异,弹窗拦截机制不同;
(5)资源加载/网络兼容
- 图片格式:WebP在IE/Safari低版本不支持,需要提供jpg/png降级;
- 字体兼容:WOFF2在IE11不支持,需要同时提供WOFF/TTF格式;
- AJAX跨域:IE8/9不支持CORS,需要使用XDomainRequest;
- 缓存策略:不同浏览器对缓存头(Cache-Control/Expires)的解析差异
(6)其他细节
- DOCTYPE声明:缺失DOCTYPE会导致IE进入怪异模式,渲染规则混乱;
- 字符编码:确保,避免IE乱码;
- 打印样式:不同浏览器的打印预览样式差异;
- 性能差异:老浏览器(IE)对JS执行、DOM操作的性能极差,需要优化;
浏览器安全部分
1.什么是XSS攻击?
XSS攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,攻击者可以通过这种攻击方式可以进行以下操作:
- 获取页面的数据,如DOM、cookie、localStorage等
- DOS攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器
- 破坏页面结构
- 流量劫持(将链接指向某网站)
XSS可以分为存储型、反射型和DOM型:
- 存储型指的是恶意脚本会存储在目标服务器上,当浏览器请求数据时,脚本从服务器传回并执行。
- 反射型指的是攻击者诱导用户访问一个带有恶意代码的URL后,服务器端接收数据后处理,然后把带有恶意代码的数据发送到浏览器端,浏览器端解析这段带有XSS代码的数据后当做脚本执行,最终完成XSS攻击
- DOM型指的通过修改页面的DOM节点形成的XSS
2.如何防御XSS攻击?
比如对输入框内容进行过滤和用转义符进行转码、添加白名单、关闭 Cookie 访问权限、使用验证码防止脚本伪装成用户执行操作等

预防 DOM 型 XSS 攻击
DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。
在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。
如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。
DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免
3.什么是CSRF攻击?
CSRF攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作
CSRF攻击的本质是利用cookie会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充
常见的CSRF攻击有三种:
- GET类型的CSRF攻击,比如在网站中的一个img标签里构建一个请求,当用户打开这个网站的时候就会自动发起提交
- POST类型的CSRF攻击,比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单
- 链接类型的CSRF攻击,比如在a标签的href属性里构建一个请求,然后诱导用户去点击
4.如何防御CSRF攻击?
进行同源检测,服务器根据http请求头中origin或者referer信息来判断请求是否为允许访问的站点,从而对请求进行过滤。当origin或者referer信息都不存在的时候,直接阻止请求
设置http-only:true,禁止cookie被浏览器通过js访问到
验证Token:浏览器发送请求时,携带token,因为token存放在sessionStorage中,不会被其它网站所窃取到
5.有哪些可能引起前端安全的问题?
跨站脚本(Cross-SiteScripting,XSS):⼀种代码注⼊⽅式,为了与CSS区分所以被称作XSS。早期常⻅于⽹络论坛,起因是⽹站没有对⽤户的输⼊进⾏严格的限制,使得攻击者可以将脚本上传到帖⼦让其他⼈浏览到有恶意脚本的⻚⾯,其注⼊⽅式很简单包括但不限于JavaScript/CSS/Flash等
iframe的滥⽤: iframe中的内容是由第三⽅来提供的,默认情况下他们不受控制,他们可以在iframe中运⾏JavaScirpt脚本、Flash 插件、弹出对话框等等,这可能会破坏前端用户体验
跨站点请求伪造(Cross-SiteRequestForgeries,CSRF):指攻击者通过设置好的陷阱,强制对已完成认证的⽤户进⾏⾮预期的个⼈信息或设定信息等某些状态更新,属于被动攻击
恶意第三⽅库:⽆论是后端服务器应⽤还是前端应⽤开发,绝⼤多数时候都是在借助开发框架和各种类库进⾏快速开发,⼀旦第三⽅库被植⼊恶意代码很容易引起安全问题
6.网络劫持有哪几种,如何防范?
(1)DNS劫持:(输⼊京东被强制跳转到淘宝这就属于dns劫持)
DNS强制解析:通过修改运营商的本地DNS记录,来引导⽤户流量到缓存服务器
302跳转的⽅式:通过监控⽹络出⼝的流量,分析判断哪些内容是可以进⾏劫持处理的,再对劫持的内存发起302跳转的回复,引导⽤户获取内容
(2)HTTP劫持:(访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告)
DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容
7.浏览器渲染进程的线程有哪些
浏览器的渲染进程的线程总共有五种:

(1)GUI渲染线程
负责渲染浏览器页面,解析HTML、CSS,构建DOM树、构建CSSOM树、构建渲染树和绘制页面;当界面需要重绘或由于某种操作引发回流时,该线程就会执行
注意:GUI渲染线程和JS引擎线程是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。
(2)JS引擎线程
JS引擎线程也称为JS内核,负责处理Javascript脚本程序,解析Javascript脚本,运行代码;JS引擎线程一直等待着任务队列中任务的到来,然后加以处理,一个Tab页中无论什么时候都只有一个JS引擎线程在运行JS程序
注意:GUI渲染线程与JS引擎线程的互斥关系,所以如果JS执行的时间过长,会造成页面的渲染不连贯,导致页面渲染加载阻塞
(3)事件触发线程
时间触发线程属于浏览器而不是JS引擎,用来控制事件循环;当JS引擎执行代码块如setTimeOut时(也可是来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件触发线程中;当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
注意:由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)
(4)定时器触发进程
定时器触发进程即setInterval与setTimeout所在线程;浏览器定时计数器并不是由JS引擎计数的,因为JS引擎是单线程的,如果处于阻塞线程状态就会影响记计时的准确性;因此使用单独线程来计时并触发定时器,计时完毕后,添加到事件队列中,等待JS引擎空闲后执行,所以定时器中的任务在设定的时间点不一定能够准时执行,定时器只是在指定时间点将任务添加到事件队列中
注意:W3C在HTML标准中规定,定时器的定时时间不能小于4ms,如果是小于4ms,则默认为4ms
(5)异步http请求线程
XMLHttpRequest连接后通过浏览器新开一个线程请求;检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将回调函数放入事件队列中,等待JS引擎空闲后执行
8.点击刷新按钮或者按F5、按Ctrl+F5(强制刷新)、地址 栏回车有什么区别?
点击刷新按钮或者按F5:浏览器直接对本地的缓存文件过期,但是会带上If-Modifed-Since,If-None-Match,这就意味着服务器会对文件检查新鲜度,返回结果可能是304,也有可能是200。
用户按Ctrl+F5(强制刷新):浏览器不仅会对本地文件过期,而且不会带上If-Modifed-Since,If-None-Match,相当于之前从来没有请求过,返回结果是200。
地址栏回车:浏览器发起请求,按照正常流程,本地检查是否过期,然后服务器检查新鲜度,最后返回内容。
9.浏览器的渲染过程
首先解析收到的文档,根据文档定义构建一棵DOM树,DOM树是由DOM元素及属性节点组成的,然后对CSS进行解析,生成CSSOM规则树。
根据DOM树和CSSOM规则树构建渲染树。渲染树的节点被称为渲染对象,渲染对象是一个包含有颜色和大小等属性的矩形,渲染对象和DOM元素相对应,但这种对应关系不是一对一的,不可见的DOM元素不会被插入渲染树。还有一些DOM元素对应几个可见对象,它们一般是一些具有复杂结构的元素,无法用一个矩形来描述。
当渲染对象被创建并添加到树中,它们并没有位置和大小,所以当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
布局阶段结束后是绘制阶段,遍历渲染树并调用渲染对象的paint方法将它们的内容显示在屏幕上,绘制使用UI基础组件。

注意:这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容
10.渲染过程中遇到JS文件如何处理?
JavaScript的加载、解析与执行会阻塞文档的解析,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停文档的解析,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。也就是说,如果想要首屏渲染的越快,就越不应该在首屏就加载JS文件,这也是都建议将script标签放在body标签底部的原因。当然在当下,并不是说script标签必须放在底部,因为你可以给script标签添加 defer或者async属性。
11.前端储存的⽅式有哪些?
localStorage:HTML5加⼊的以键值对(Key-Value)为标准的⽅式,优点是操作⽅便,永久性储存(除⾮⼿动删除),⼤⼩为5M,兼容 IE8+
sessionStorage:与localStorage基本类似,区别是sessionStorage当⻚⾯关闭后会被清理,⽽且与cookie、localStorage不同,他不能在所有同源窗⼝中共享,是会话级别的储存⽅式
WebSQL:2010年被W3C废弃的本地数据库数据存储⽅案,但是主流浏览器(⽕狐除外)都已经有了相关的实现,websql类似于SQLite,是真正意义上的关系型数据库,⽤sql进⾏操作,当我们⽤JavaScript 时要进⾏转换,较为繁琐;
IndexedDB:是被正式纳⼊HTML5标准的数据库储存方案,它是NoSQL数据库,⽤键值对进⾏储存,可以进⾏快速读取操作,⾮常适合web场景,同时⽤JavaScript进⾏操作会⾮常方便。
12.对事件循环的理解
因为js是单线程运行的,在代码执行时,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行。在执行同步代码时,如果遇到异步事件,js引擎并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当异步事件执行完毕后,再将异步事件对应的回调加入到一个任务队列中等待执行。任务队列可以分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js引擎首先会判断微任务队列中是否有任务可以执行,如果有就将微任务队首的事件压入栈中执行。当微任务队列中的任务都执行完成后再去执行宏任务队列中的任务
首先执行同步代码,这属于宏任务
当执行完所有同步代码后,执行栈为空,查询是否有异步代码需要执行
执行所有微任务
当执行完所有微任务后,如有必要会渲染页面
然后开始下一轮EventLoop,执行宏任务中的异步代码