前端面试套题系列(第七篇)
1、{}、new Object和Object.create的区别
主要区别
1、{} 和 new Object() 除了本身创建的对象,都继承了 Object 原型链上(Object.prototype)的属性或者方法,eg:toString();当创建的对象相同时,可以说 {} 等价于 new Object() 。
2、Object.create() 是将创建的对象继承到原型链上,而本身没有继承 Object.prototype 的属性和方法。
Object.cerate() 必须接收一个对象参数,创建的新对象的原型指向接收的参数对象,
new Object() 创建的新对象的原型指向的是 Object.prototype. (表述有点啰嗦,简洁点说就是前者继承指定对象, 后者继承内置对象Object)
可以通过Object.create(null) 创建一个干净的对象,也就是没有原型,
而 new Object() 创建的对象是 Object的实例,原型永远指向Object.prototype.
2、为什么vite在开发过程中会更快
ES modules:Vite 利用了浏览器本身对 ES modules 的原生支持,因此不需要打包构建就可以直接使用模块化的开发方式。这意味着在开发过程中只需要编译当前修改的文件,而不需要重新构建整个项目,从而提高了开发效率。
开发服务器:Vite 使用了基于浏览器原生 ES modules 支持的开发服务器,将每个单独的模块作为独立的文件服务,并且实现了按需编译。在开发时,每次修改文件保存后,只会重新编译这个文件,而不会重新编译整个项目,因此可以快速地进行开发测试。同时,Vite 使用了缓存机制,可以避免重复编译相同的文件。
esbuild 预构建:Vite 的 Dev 环境会进行预构建优化,Vite 是基于浏览器原生**支持 **ESM 的能力实现的,但要求用户的代码模块必须是ESM模块,因此必须将 commonJS
和 UMD
规范的文件提前处理,转化成 ESM 模块并缓存入 node_modules/.vite
。
3、点击内部div,执行onClick会发生什么
import React from 'react'; export function App() { const ref = React.useRef(); React.useEffect(() => { ref.current.addEventListener('click', () => { console.log(123) }) }, []) return ( <div ref={ref} style={{ width: 200, height: 200, background: 'orange'}}> <div style={{ width: 100, height: 100, background: 'red'}} onClick={e => e.stopPropagation()}>123</div> </div> ) }
<div>
元素上添加了一个点击事件监听器,并且内层 <div>
元素的点击不会触发外层元素的点击事件。当点击内层 <div>
元素时,会输出 123 到控制台。
4、pnpm的依赖管理机制
Store 目录:store 目录用于存储依赖的 hard links,一般 store 目录默认是设置在 ${os.homedir}/.pnpm-store
这个目录下。
.npmrc 设置这个 store 目录位置,不过一般而言 store 目录对于用户来说感知程度是比较小的。
5、react中 useMemo、useCallback区别
useMemo
:useMemo
用于在依赖项变化时,缓存一个计算结果。它接收两个参数:计算函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,useMemo
会重新计算计算函数,并返回计算结果。useCallback
:useCallback
用于缓存一个回调函数,以便在依赖项不变时,避免创建新的回调函数实例。它接收两个参数:回调函数和依赖项数组。当依赖项数组中的任何一个值发生变化时,useCallback
会返回一个新的回调函数。useCallback内部也是使用了useMemo来实现。
总结:
useMemo
用于缓存一个计算结果,在依赖项变化时重新计算。useCallback
用于缓存一个回调函数,在依赖项变化时返回新的回调函数。
6、react实现一个关键字高亮组件
import React from 'react'; const KeywordHighlight = ({ text, keyword }) => { const highlightText = (text, keyword) => { const regex = new RegExp(`(${keyword})`, 'gi'); return text.replace(regex, '<span class="highlight">$1</span>'); }; const highlightedText = {__html: highlightText(text, keyword)}; return <div dangerouslySetInnerHTML={highlightedText} />; }; export default KeywordHighlight;
7、js 实现一个深拷贝
function deepClone(obj) { // 判断是否为引用类型 if (typeof obj !== 'object' || obj === null) { return obj; // 若是非引用类型,则直接返回 } // 判断是否为数组 if (Array.isArray(obj)) { const newArr = []; // 创建一个新数组 for (let i = 0; i < obj.length; i++) { newArr.push(deepClone(obj[i])); // 递归拷贝数组元素 } return newArr; } // 否则为普通对象 const newObj = {}; // 创建一个新对象 for (let key in obj) { newObj[key] = deepClone(obj[key]); // 递归拷贝对象属性 } return newObj; }
8、js 实现浏览器url参数获取,使用正则
function getUrlParams(url) { const regex = /[?&]([^=#]+)=([^&#]*)/g; // 匹配 URL 中的查询参数部分 const params = {}; let match; while (match = regex.exec(url)) { const key = decodeURIComponent(match[1]); // 解码参数名 const value = decodeURIComponent(match[2]); // 解码参数值 params[key] = value; // 存储参数到对象 } return params; }
9、下面这段代码输出什么
for (var i = 0; i < 3; i++) { setTimeout(function() { console.log("a:"+i); }, 0); console.log("b:"+i); }
输出:
b:0
b:1
b:2
3次 a:3
10、实现一个函数,返回最长二叉树路径的长度
function getLongestPathLength(root) { let maxLength = 0; // 最长路径长度 // 定义深度优先搜索函数 function dfs(node) { if (node === null) { return 0; // 空节点的路径长度为0 } // 递归计算左子树和右子树的最长路径长度 const leftLength = dfs(node.left); const rightLength = dfs(node.right); // 计算以当前节点为根的最长路径长度 const currentLength = leftLength + rightLength; // 更新最长路径长度 maxLength = Math.max(maxLength, currentLength); // 返回当前节点为根的最长路径长度 return Math.max(leftLength, rightLength) + 1; } dfs(root); // 调用深度优先搜索函数 return maxLength; }
11、讲一讲性能优化
一些常见的性能优化技巧:
-
减少重绘和重排:避免频繁改变页面布局、样式和结构,因为这会导致浏览器进行重绘和重排操作,影响性能。尽量使用 CSS 类名的方式进行样式修改,使用
requestAnimationFrame
来优化动画效果。 -
避免过多的 DOM 操作:DOM 操作比较昂贵,可以将多个 DOM 操作进行批量处理,或者使用文档片段(DocumentFragment)进行离线操作后再一次性插入到文档中。
-
使用事件委托:将事件监听器绑定到父元素上,通过事件冒泡机制来处理子元素的事件,减少监听器的数量,提高性能。
-
避免频繁的重复计算:在需要多次计算的地方,可以将结果缓存起来,避免重复计算。
-
使用节流和防抖:对于频繁触发的事件(如窗口大小改变、滚动等),可以使用节流(throttling)和防抖(debouncing)来限制事件的触发频率,减少函数的调用次数。
-
缓存数据:对于频繁使用的数据,可以将其缓存起来,避免重复获取或计算。
-
合并和压缩文件:将多个 JavaScript 和 CSS 文件合并为一个文件,并进行压缩,减少网络请求和文件大小,提高加载速度。
-
使用 Web Workers:对于耗时的操作,可以使用 Web Workers 进行多线程处理,避免阻塞主线程。
-
避免不必要的网络请求:减少不必要的 AJAX 请求、图片加载等网络请求,尽量使用缓存。
-
使用性能分析工具:使用浏览器开发者工具的性能分析功能来识别瓶颈,并进行针对性的优化。
12、浏览器的进程、五大线程
-
浏览器的进程(Process):
- 浏览器是一个多进程的应用程序,主要包括浏览器进程、渲染进程、插件进程等。
- 浏览器进程负责处理用户界面、用户输入、存储、网络请求等任务,是浏览器的主进程。
- 渲染进程负责解析 HTML、CSS,构建 DOM 树和渲染页面,每个标签页通常对应一个独立的渲染进程。
- 插件进程负责管理插件,以提供额外的功能和服务。
-
浏览器的五大线程(Thread):
- UI 线程:负责处理用户界面操作,包括渲染页面、响应用户输入等,也称为主线程。
- JavaScript 线程:负责执行 JavaScript 代码,解析和运行 JavaScript 脚本,也称为 JS 引擎线程。
- 事件线程:负责处理事件,例如鼠标点击、键盘输入等,将事件添加到事件队列中等待执行。
- 定时器线程:负责定时器相关任务,如 setTimeout 和 setInterval 设置的定时任务。
- 异步 HTTP 请求线程:负责处理异步 HTTP 请求,例如 AJAX 请求等。
需要注意的是,JavaScript 线程和 UI 线程(主线程)是互斥的,即在同一时间只能执行其中之一的任务。因此,在编写 JavaScript 代码时,应尽量避免长时间的同步操作,以免阻塞 UI 线程导致页面卡顿。
13、乾坤样式隔离
乾坤框架提供了几种方式来实现样式隔离:
-
Shadow DOM:乾坤框架中的子应用可以使用 Shadow DOM 技术来实现样式隔离。Shadow DOM 可以将子应用的 DOM 结构和样式封装到一个独立的作用域中,不会影响其他子应用或宿主应用。
-
CSS Modules:乾坤框架还支持使用 CSS Modules 来实现样式隔离。CSS Modules 是一种将 CSS 文件作为模块进行引入的机制,每个子应用的样式文件都可以使用独立的作用域,避免样式冲突。
-
命名空间或前缀:在乾坤框架中,可以为每个子应用的样式添加命名空间或前缀,data-子应用名称,以确保样式的唯一性和隔离性。通过为样式类名添加独特的前缀,可以避免不同子应用之间的样式冲突。
使用乾坤框架进行样式隔离可以有效地解决多个子应用共存时的样式冲突问题,保证各个子应用的独立性和可维护性。
14、react的fiber解决了什么问题
-
长时间的渲染阻塞:在 React 之前,渲染过程是同步的,即在一个渲染周期内,如果有大量计算密集型任务或者组件层级较深,就会导致渲染阻塞,造成页面卡顿。Fiber 引入了一套基于优先级的调度算法,能够将渲染工作分割为小的任务单元,并且可以中断和恢复渲染过程,从而实现了更加细粒度的时间分片,避免了长时间的渲染阻塞。
-
用户交互的响应性:由于 React 在更新 UI 时是同步进行的,当有大量的计算任务或者复杂的布局计算时,可能会导致用户交互不流畅,无法及时响应用户的操作。Fiber 通过将渲染工作切分为优先级不同的任务,使得浏览器有机会在每个任务之间进行渲染和用户输入响应,从而提升了用户交互的响应性能。
-
更好的实现异步渲染:在 React 之前,实现异步渲染需要使用额外的库或技术,如 React.lazy 和 Suspense。而 Fiber 的引入使得 React 内部可以更好地支持异步渲染,实现了更高效、更灵活的代码分割和懒加载。
总的来说,React Fiber 解决了渲染阻塞、用户交互响应性不足以及异步渲染等问题,通过引入任务优先级调度和时间分片机制,提升了 React 应用的性能和用户体验。