继续更新啦,预计分为三篇文章,可放心食用~
以下问题均为个人整理回答,码字不易,转发和发布请携带发布者
面试问题有误可联系修改
一、web网页如何禁止别人移除水印?
为了防止网页水印被他人移除,可以利用MutationObserver API对DOM元素进行监听。具体实现步骤如下:
- 定义目标节点:将需要观察变动的节点(如document.body)赋值给变量,表示MutationObserver将观察这个节点的变化。
- 创建MutationObserver实例:创建一个MutationObserver实例,并传入一个回调函数。当监测到DOM变化时,会调用这个回调函数,并传入一个包含所有变化的MutationRecord对象的数组。
- 配置MutationObserver:设置MutationObserver的配置对象,指定需要观察的变动类型,如子节点的增减、属性的修改等。
- 监听并复原:在回调函数中,判断是否有水印被删除的操作,如果有,则执行相应的复原操作,如重新插入水印的DOM元素到目标节点。
// 代码示例
// 假设你的水印是一个具有特定ID的DOM元素,例如id为"watermark"
const watermark = document.getElementById('watermark');
// 创建一个回调函数来接收变动通知
const callback = function(mutationsList, observer) {
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
// 检查是否有子节点被移除
mutation.removedNodes.forEach(node => {
if (node === watermark) {
// 如果水印被移除了,就复原它
document.body.appendChild(watermark);
}
});
} else if (mutation.type === 'attributes') {
// 检查是否有属性被修改,这里可以根据需要添加更多逻辑
}
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 配置观察选项:
const config = { attributes: true, childList: true, subtree: true };
// 选择需要观察变动的节点
const targetNode = document.body;
// 开始观察已配置的变动
observer.observe(targetNode, config);
二、用户访问页面白屏了,原因是啥,如何排查?
Web前端用户访问页面白屏,可能原因包括资源访问错误、代码执行错误、路由配置问题、浏览器兼容问题等。排查步骤如下:
- 检查资源加载:确认CSS、JS、图片等关键资源是否加载失败或延迟。利用浏览器的开发者工具查看网络请求,确认资源是否正确加载。
- 审查代码执行:检查JavaScript代码是否有执行错误,如访问未定义变量、类型错误等。使用浏览器的控制台查看是否有错误信息输出。
- 验证路由配置:如果是单页面应用,检查路由配置是否正确。确保服务器正确配置以支持SPA的路由模式。
- 考虑浏览器兼容:检查页面是否在特定浏览器(如IE)中出现白屏。尝试在不同浏览器中访问页面以验证兼容性。
三、为什么有的请求有两个请求,有的只有一个请求?
在前端项目中,有的请求出现两次,而有的只有一次,这主要是由于跨域请求和请求类型的不同导致的。当进行跨域请求时,浏览器会先发送一个OPTIONS请求进行预检(CORS预检),以确认服务器是否允许跨域请求,然后再发送实际的请求(如POST请求)。这种情况下,就会出现两次请求。而简单请求,即满足特定条件的GET、HEAD或POST请求,不会触发CORS预检,浏览器会直接发送实际的请求,因此只会出现一次请求。
四、js中如何实现大对象深度对比?
需要实现这一功能,可以手动编写一个递归函数,或者使用现有的库,如lodash的isEqual方法。
- 手动实现深度对比:深度对比大对象可能会非常耗时,特别是当对象结构复杂或包含大量数据时。在性能敏感的应用中,你可能需要考虑优化你的比较逻辑,或者避免频繁进行深度对比。
// 简单版本demo
function deepEqual(obj1, obj2) {
if (obj1 === obj2) return true
if (typeof obj1 != "object" || typeof obj2 != "object" || obj1 == null || obj2 == null) return false;
let keysA = Object.keys(obj1), keysB = Object.keys(obj2);
if (keysA.length != keysB.length) return false;
for (let key of keysA) {
if (!keysB.includes(key) || !deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
- 使用lodash库:
import isEqual from 'lodash/isEqual';
const object1 = { a: 1, b: { c: 1 } };
const object2 = { a: 1, b: { c: 1 } };
console.log(isEqual(object1, object2)); // 输出:true
五、如何理解数据驱动视图,有哪些核心要素?
- 响应式系统:这是数据驱动视图的基础,它确保数据的变化能够被自动检测到,并触发视图的更新。在Vue等框架中,这一系统通过依赖追踪和异步更新队列等技术实现。
- 数据变化检测:当数据发生变化时,响应式系统会检测到这一变化,并通知视图进行更新。这通常通过对比新旧数据值来实现,vue中的diff算法。
- 视图自动更新:一旦数据变化被检测到,视图层会根据最新的数据自动更新,无需开发者手动操作DOM。
六、vue-cli做了哪些事,有哪些功能?
- 项目初始化:Vue Cli可以快速创建一个基于Vue.js的项目结构,包括默认的配置和目录结构,支持选择不同的预设选项,如特性、CSS预处理器等。
- 开发服务器:Vue Cli集成了开发服务器,支持热模块替换,可以在本地快速启动一个开发环境,实时预览和调试项目。
- 构建和打包:Vue Cli提供了一些配置选项,可以定制项目的构建和打包过程,生成用于生产环境的静态文件。
- 插件系统:Vue Cli支持插件系统,允许通过安装插件来扩展Vue项目的功能。
七、JS执行100万个任务,如何保证浏览器不卡顿?
- 使用Web Worker:通过Web Worker开辟新的线程,处理复杂的长任务,避免主线程被阻塞导致的页面卡顿。
- 任务分割:利用generator函数或类似机制,将长任务分割成多个宏任务,避免全挤在微任务队列中影响页面渲染。
- 优化数据操作:对于大数据量的操作,考虑使用优化的数据结构和算法,比如使用Map进行去重操作,减少执行时间。
- 延迟执行:尽可能延迟执行不必要的代码,直到真正需要时才执行,以减少资源占用和提升性能。
八、JS放在head和body里有什么区别?
- 加载顺序与执行时机:放在head中的JS会在HTML文档解析过程中被加载,此时body还未解析,可能导致JS操作HTML元素时找不到对应元素。而放在body中的JS会在页面加载完成后执行,可确保HTML元素已加载完毕。
- 页面渲染与性能:head中的JS可能会阻塞页面渲染,导致页面显示滞后,影响用户体验。因此,推荐将JS放在body底部,以减少对整个页面下载和渲染的影响。
- 用途与适用场景:head中的JS适合定义函数、全局变量等,而body中的JS更适合生成页面内容或进行页面加载后的操作。
九、Eslint代码检测的过程是什么?
- 安装ESLint:首先,需要在项目中安装ESLint,可以使用npm或yarn进行安装。
- 初始化配置文件:安装完成后,需要在项目根目录下初始化一个ESLint配置文件,可以通过命令行工具或图形化界面工具进行初始化。
- 配置ESLint规则:在初始化配置文件后,可以进入.eslintrc.js文件中,根据项目需求自定义配置ESLint规则。
- 代码检查:配置完成后,ESLint会使用解析器将JavaScript代码解析成抽象语法树(AST),然后遍历AST,应用配置的规则进行检查,识别代码中的问题。
十、虚拟滚动加载的原理是什么?用代码简单实现一个虚拟滚动加载
其核心原理是只渲染可视区域内的元素,并在滚动时动态加载和卸载数据,从而显著提高性能。虚拟滚动加载的原理:
- 计算可视区域:确定用户当前可视区域的大小和位置。
- 数据切片:根据可视区域的位置和大小,从大量数据中切分出需要显示的数据片段。
- 渲染切片:仅渲染切分出的数据片段到DOM中。
- 监听滚动事件:当用户滚动时,重新计算可视区域,并更新需要显示的数据切片和对应的DOM元素。
// 代码示例
<div id="scrollContainer"></div>
<script>
const totalItems = 1000;
const visibleHeight = 300;
const itemHeight = 50;
const scrollContainer = document.getElementById('scrollContainer');
function render() {
const firstItemIndex = Math.floor(scrollContainer.scrollTop / itemHeight);
const visibleItemCount = Math.ceil(visibleHeight / itemHeight);
scrollContainer.innerHTML = '';
for (let i = firstItemIndex; i < firstItemIndex + visibleItemCount; i++) {
if (i < totalItems) {
const item = document.createElement('div');
item.className = 'item';
item.textContent = `Item ${i + 1}`;
scrollContainer.appendChild(item);
}
}
}
scrollContainer.addEventListener('scroll', render);
render(); // Initial render
</script>
<style>
#scrollContainer {
height: 300px;
overflow-y: auto;
position: relative;
}
.item {
height: 50px;
line-height: 50px;
text-align: center;
}
</style>
十一、Vue Router和原生路由的区别?
Vue Router更适合用于构建单页面应用,而原生路由则适用于传统的多页面应用。Vue Router与原生路由的主要区别在于其运行环境和实现方式:
- 运行环境:Vue Router是Vue.js的官方路由管理器,专为单页面应用(SPA)设计,运行在浏览器端。而原生路由通常指的是服务器端路由,由服务器直接处理URL请求并返回相应的页面。
- 实现方式:Vue Router通过前端路由技术实现,根据URL的变化来动态渲染前端组件,无需服务器重新加载页面。它提供了丰富的API来定义路由规则、处理路由跳转等。而原生路由则是由服务器端的路由框架或服务器本身来处理,每个URL请求都会对应一个服务器上的页面或资源。
十二、html行内元素和块级元素的区别?
HTML中的行内元素和块级元素在页面布局中扮演着不同的角色,它们之间有几个关键的区别:
- 布局方式:行内元素不会独占一行,而是在同一行内连续显示;块级元素则会独占一行,无论其内容是否占满一行的宽度。
- 尺寸设置:行内元素的宽度和高度由其内容决定,不能直接设置宽度和高度;而块级元素的宽度、高度、边距和内边距都可以通过CSS属性进行设置。
- 包含关系:块级元素可以包含其他块级元素和行内元素;而行内元素不能包含块级元素,只能包含其他行内元素或文本。
十三、css选择器的优先级?行内样式在哪一级别?
CSS选择器的优先级决定了哪些样式将被应用到HTML元素上。优先级从高到低可以分为以下几个级别:
- !important规则:这是最高优先级,可以覆盖页面内任何位置定义的元素样式。
- 行内样式:直接在HTML元素中通过style属性指定的样式,例如,其优先级仅次于!important规则。
- ID选择器:使用"#"符号指定元素的ID,例如#header,其优先级低于行内样式。
- 类选择器、属性选择器和伪类:使用"."符号指定元素的类,例如.btn,其优先级低于ID选择器。
- 标签选择器:指定HTML元素的标签名称,例如div,其优先级低于类选择器。
- 通配选择器、选择符和逻辑组合伪类:例如*、+、>、:not()等,其优先级最低。
十四、css了解哪几种定位?分别以什么进行定位?
- 静态定位(static):元素的默认定位方式,按照正常文档流进行排列,不会被特别定位。
- 相对定位(relative):元素在正常文档流中占据原先的空间,但可以通过left、top、right、bottom等属性相对于自身的初始位置进行偏移。
- 绝对定位(absolute):元素脱离正常文档流,相对于最近的已定位祖先元素进行定位。如果没有已定位的祖先元素,则相对于文档的初始坐标(x:0,y:0)进行定位。
- 固定定位(fixed):元素脱离正常文档流,相对于浏览器窗口进行定位,即使窗口滚动,元素的位置也不会改变。
- 粘性定位(sticky):元素在滚动到指定位置前是相对定位,滚动到指定位置后则固定在指定位置,直到滚动区域滚动超过指定位置。
十五、documentfragment api是什么有哪些使用场景?
DocumentFragment API是一种用于DOM操作的API,旨在提高性能。它是一个虚拟的DOM节点容器,可以在其中存储多个DOM元素,但不会直接在页面中渲染显示。主要使用场景包括:
- 动态创建HTML元素:可以在DocumentFragment中构建一组DOM元素,然后一次性将它们添加到文档中,从而减少DOM操作的重绘和重排,提高性能。
- 简化代码:通过使用DocumentFragment,可以将多个DOM操作合并为一个操作,使代码更加简洁和高效1。 改善用户体验:减少回流和重绘有助于提高页面的响应速度,改善用户体验。
- 使用示例:
// 创建一个空的DocumentFragment
var fragment = document.createDocumentFragment();
// 创建一个<ul>元素
var ul = document.createElement('ul');
// 假设我们要添加5个<li>元素到<ul>中
for (var i = 0; i < 5; i++) {
var li = document.createElement('li');
li.appendChild(document.createTextNode('Item ' + (i + 1)));
// 将<li>元素添加到DocumentFragment中
fragment.appendChild(li);
}
// 将DocumentFragment中的所有子节点添加到<ul>中
ul.appendChild(fragment);
// 最后,将<ul>添加到文档的body中
document.body.appendChild(ul);
十六、介绍一下requestIdleCallback api
requestIdleCallback API 是一个浏览器提供的Web API,它允许开发者在浏览器的空闲时段内执行一些低优先级的任务,而不会影响到页面的关键操作,如动画、输入响应等。这个API的主要目的是帮助开发者更有效地利用浏览器的空闲时间,以改善页面的性能和响应性。requestIdleCallback 的使用场景主要包括:
- 延迟非关键任务的执行:比如,分析、日志记录或其他不紧急的后台任务。
- 分解长时间运行的任务:将一个耗时的任务分解成多个小部分,并在浏览器的空闲时段内逐一执行。
- 优化页面加载性能:在页面加载或用户交互的空闲时段内,预先执行一些计算或数据处理任务。
十七、git pull和git fetch有啥区别?
git pull和git fetch是Git中用于从远程仓库获取更新的两个命令,它们在功能和使用上有所不同。
- 目的与用途:git fetch主要用于从远程仓库获取最新版本到本地,但不会自动合并;而git pull则会从远程获取最新版本并自动合并到本地。
- 对当前工作的影响:使用git fetch时,它不会对工作目录中的文件进行任何更改,只更新本地仓库中的远程跟踪分支;而git pull会自动合并远程仓库的更改到当前工作分支,可能会修改工作目录中的文件。
- 操作效果:git fetch更新代码后,本地的库中master的commitID不变;而git pull更新代码后,本地的库中master的commitID会发生改变。
十八、前端如何保证系统稳定性
- 监控与告警:
- 实施全面的业务监控,包括前端脚本错误、异步通信、性能参数等,确保能及时发现并响应问题。
- 设立实时告警机制,对异常情况及时发送告警通知,以便快速采取措施。
- 代码质量与测试:
- 强化代码审查,确保代码符合规范,逻辑清晰,减少漏洞。
- 实施单元测试、集成测试和压力测试,确保代码功能正确,系统能承受高负载。
- 工具与平台利用:
- 充分利用可视化工具、人工智能能力和低代码/无代码解决方案,提升前端开发和维护效率。
- 自研或采用现有工具,如日志平台、前端监控系统等,辅助问题排查和系统优化。
十九、如何统计长任务时间,长任务执行次数
- 统计长任务时间:
- 使用performance.now()获取高精度时间,以测量长任务的开始和结束时间。
- 通过计算结束时间与开始时间的差值,得到长任务的执行时间。
- 统计长任务执行次数:
- 利用console.count()方法统计特定代码块的执行次数。
- 为长任务添加装饰器或使用其他技术手段,在每次执行时递增计数器,从而追踪执行次数。
二十、用JS写一个cookies解析函数,输出结果为一个对象
function parseCookies() {
let cookies = document.cookie;
let cookiesObj = {};
if (cookies) {
let cookiePairs = cookies.split('; ');
for (let i = 0; i < cookiePairs.length; i++) {
let cookiePair = cookiePairs[i].split('=');
let key = cookiePair;
let value = cookiePair;
cookiesObj[key] = value;
}
}
return cookiesObj;
}
// 使用示例
let cookies = parseCookies();
console.log(cookies);
二十一、vue中scoped styles是如何实现样式隔离的,原理是什么?
- 唯一选择器生成:Vue编译单文件组件时,为使用scoped特性的样式选择器生成一个唯一的属性选择器,如[data-v-xxxxxxx]。
- 编译时转换:Vue解析组件模板并对样式进行处理,将选择器转换为带有唯一属性选择器的形式,如.class会被转换为.class[data-v-xxxxxxx]。
- 渲染时应用:组件渲染时,Vue为组件根元素添加属性值为唯一标识符的属性,如data-v-xxxxxxx。渲染完成后,样式选择器中的唯一属性选择器与组件根元素的属性匹配,实现样式隔离。
二十二、样式隔离方式有哪些?
- 作用域样式(Scoped Styles):Vue中通过为样式添加scoped属性实现。编译时,Vue为每个样式选择器生成唯一属性选择器,并在组件渲染时为根元素添加此属性,实现样式隔离。
- CSS Modules:另一种样式隔离方式,通过构建工具(如Webpack)将CSS类名局部化,确保样式只应用于特定组件,避免全局污染。
- 深度选择器、插槽选择器、全局选择器:Vue中作用域样式的扩展应用,分别用于父组件影响子组件、组件影响插槽元素、组件样式应用到全局的场景。
二十三、在js中,如何解决递归导致栈溢出问题?
- 尾递归优化:尾递归是指在函数的最后一步调用自身。在ES6中,如果你使用尾递归,一些JavaScript引擎(如Node.js的V8引擎)可以优化这种递归形式,消除传统递归带来的栈溢出风险。
// 在这个例子中,factorial函数是尾递归的,因为它在返回其值之前调用自身。
function factorial(n, acc = 1) {
if (n <= 1) return acc;
return factorial(n - 1, n * acc);
}
- 使用循环代替递归:对于许多递归函数,你都可以找到一种使用循环来替代的方法。循环不会增加调用栈的深度,因此不会造成栈溢出。
function factorial(n) {
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
- 增加堆栈大小:在某些环境中,你可以增加JavaScript引擎的堆栈大小,但这通常不是解决问题的最佳方法,因为它只是推迟了问题的发生,并没有真正解决问题。
- 使用Web Workers:如果你正在处理大量数据,并且计算密集,可以考虑使用Web Workers。Web Workers运行在浏览器后台,它们有自己的堆栈,不会阻塞UI线程。
- 分割任务:如果你面对的是一个可以分割的大任务,考虑将其分割成小块,然后逐一处理。这样,你可以避免一次递归调用处理所有数据。
二十四、站点如何防止爬虫?
- 调整请求频率与伪装请求头:
- 合理设置时间间隔,模拟人类正常浏览行为,避免频繁请求。
- 伪装请求头信息,如设置合理的User-Agent,以规避被识别为爬虫。
- 利用robots协议与限制IP地址:
- 通过robots.txt文件明确告知爬虫哪些页面可被抓取,以控制访问。
- 限制IP地址,识别并拒绝频繁发起请求的IP,特别是恶意爬虫。
- 添加验证码与处理反爬技术:
- 使用验证码阻止自动化程序,提高爬虫进入门槛。
- 应对图片伪装、CSS偏移、自定义字体等反爬技术,确保数据不被轻易获取。
二十五、ts项目中,如何使用node modules里面定义的全局类型包到自己项目src下面使用?
- 安装全局类型包:使用npm或yarn安装所需的全局类型包,例如@types/react。
- 配置TypeScript:修改项目的tsconfig.json文件,添加typeRoots或types配置来指定全局类型的位置。在compilerOptions中使用types数组列出所有全局类型的名称。设置include配置项为"src/**/*",表示编译器会考虑src目录下的所有文件。
- 使用import语句导入类型:显式地使用import语句导入全局类型,提高代码的可读性和可维护性。
二十六、ts中泛型是什么?和any的区别是什么
- 在TypeScript(TS)中,泛型(Generics)是一种强大的特性,它允许在定义函数、接口或类时不预先指定具体的类型,而是在使用时再指定类型。泛型的主要好处是可以创建可重用的组件,一个组件可以支持多种类型的数据。这意味着可以使用相同的函数、接口或类代码来处理不同类型的数据,而不必为每种类型编写新的代码。泛型的基本语法是在函数名、接口名或类名后面添加一个尖括号<>,并在其中指定一个或多个类型变量。然后,可以在函数体、接口或类中使用这些类型变量来代表任意类型。例如,以下是一个使用泛型的简单函数:
function identity<T>(arg: T): T {
return arg;
}
// 在这个例子中,T是一个类型变量,它在函数被调用时会被实际的类型替换。这意味着可以使用identity函数来处理任何类型的数据
let output = identity<string>("myString");
- 与泛型相比,any类型是TypeScript中另一种处理类型的方式。any类型允许变量接受任何类型的值,这相当于关闭了TypeScript的类型检测。使用any类型时,虽然可以传入任何类型的值,但无法确保函数返回值的类型,也失去了TypeScript类型保护的优势。
二十七、不同标签页或窗口间(主动推送消息机制)的方式有哪些?
- BroadcastChannel API:允许同源下的不同浏览器上下文(如标签页、iframe)之间进行消息传递。通过创建一个广播频道,并在不同的标签页中监听该频道,可以实现跨标签页通信。
- Service Worker:作为Web应用程序、浏览器与网络之间的代理服务器,可以拦截网络请求并根据网络状态采取适当动作。通过Service Worker,各个标签页可以通过clients.matchAll()方法找到所有其他客户端(如打开的标签页),然后使用postMessage发送消息。
- WebSocket:通过服务器作为中介,实现标签页之间的消息传递和数据同步。通信的标签页连接同一个WebSocket服务器,发送消息到服务器后,服务器推送消息给所有连接的客户端。
二十八、在vue项目开发过程中,是否可以不用vue router,使用浏览器原生history路由来组织页面路由?
浏览器原生的History API虽然可以实现无刷新修改、监听浏览器URL变化,但需要自己处理路由的映射关系、页面渲染等,以下简单代码示例:
// 假设你有两个Vue组件,Home和About
const Home = {template: '<div>Home Page</div>'};
const About = {template: '<div>About Page</div>'};
// 创建一个Vue实例,并挂载到#app元素上
new Vue({
el: '#app',
data: {
currentView: 'home' // 当前显示的视图
},
computed: {
currentComponent() {
return this.currentView === 'home' ? Home : About;
}
},
mounted() {
// 监听popstate事件,以便在浏览器历史记录变化时更新视图
window.addEventListener('popstate', (e) => {
this.currentView = e.state ? e.state.view : 'home';
});
// 初始化时,检查当前URL并设置相应的视图
this.checkUrlAndSetView();
},
methods: {
navigateTo(view) {
// 使用History API的pushState方法更新浏览器的历史记录
history.pushState({ view }, '', `/${view}`);
// 更新当前视图
this.currentView = view;
},
checkUrlAndSetView() {
// 这里可以根据URL来设置currentView的值
// 例如,如果URL是"/about",则设置currentView为"about"
const path = window.location.pathname;
this.currentView = path === '/about' ? 'about' : 'home';
}
},
components: { Home, About}
});
// 在模板中使用currentComponent来动态渲染组件
// 注意:这里使用了Vue的<component>元素和is属性来实现动态组件
<div id="app">
<button @click="navigateTo('home')">Home</button>
<button @click="navigateTo('about')">About</button>
<component :is="currentComponent"></component>
</div>
二十九、在表单校验场景中,如何实现页面滚动到报错位置?
在表单校验场景中,当检测到错误并希望页面滚动到报错位置时,可以通过JavaScript实现。下面是一个简单的实现步骤:
- 获取报错元素的引用:确保每个表单元素都有一个唯一的标识符(如ID),这样你就可以轻松地通过JavaScript获取到这些元素的引用。
- 检测错误并存储报错元素:在用户提交表单时,进行校验。如果某个字段校验失败,存储该字段对应元素的引用。
- 滚动到报错位置:使用JavaScript的scrollIntoView()方法,将报错元素滚动到浏览器窗口的可视区域内。
<form id="myForm">
<input type="text" id="username" placeholder="用户名" required>
<input type="email" id="email" placeholder="邮箱" required>
<button type="submit">提交</button>
</form>
<script>
document.getElementById('myForm').addEventListener('submit', function(event) {
event.preventDefault(); // 阻止表单默认提交行为
var username = document.getElementById('username');
var email = document.getElementById('email');
var isError = false;
var errorElement;
// 假设校验逻辑如下
if (!username.value.trim()) {
errorElement = username;
isError = true;
} else if (!email.value.includes('@')) {
errorElement = email;
isError = true;
}
// 如果有错误,滚动到报错位置
if (isError) {
errorElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
});
</script>
三十、如何一次性渲染十万条数据还能保证页面不卡顿?
可以通过分批次渲染和使用虚拟文档碎片(document.createDocumentFragment())来实现。具体方法如下:
- 分批次渲染:
- 使用setTimeout或requestAnimationFrame将渲染任务分批进行,避免一次性渲染过多DOM导致页面卡顿。
- requestAnimationFrame的优势在于其执行频率与浏览器刷新率同步,可以更有效地控制渲染节奏。
- 使用虚拟文档碎片:
- 创建一个虚拟文档碎片,将生成的DOM元素先添加到碎片中,最后再一次性将碎片添加到真实DOM中。
- 这样可以减少DOM操作的次数,从而降低页面回流的频率,提高渲染效率。