前端面试套题系列(第七篇)
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 应用的性能和用户体验。
15、数组、对象、字符串的api
数组(Array)API:
- push():向数组末尾添加一个或多个元素。
- pop():从数组末尾移除并返回最后一个元素。
- concat():连接两个或多个数组,并返回新数组。
- slice():返回指定位置的子数组。
- forEach():对数组中的每个元素执行提供的函数,没有返回值
- reduce(callback,initialValue): 累计求和、计算平均值,最终返回一个结果
- filter():根据指定条件,筛选出符合条件的元素,返回一个新数组
- find():找到符合条件的第一个元素并返回
- some():判断数组中是否存在满足指定条件,只要有一个满足返回true,否则返回false
- every():数组中全部元素都满足指定条件才返回true,否则返回false
对象(Object)API:
- Object.keys():返回对象中可枚举属性的名称数组。
- Object.values():返回对象中可枚举属性的值数组。
- Object.entries():返回对象中可枚举属性的键值对数组。
字符串(String)API:
- split():将字符串分割成字符串数组。
- join():将数组元素连接成一个字符串。
- indexOf():返回指定字符串首次出现的索引位置,如果没有找到则返回 -1。
- substr():返回从指定位置开始的指定长度的子字符串。
- startWith(): 判断字符串是否以指定字符开头,返回true/false
- endsWith(): 判断字符串是否以指定字符结尾,返回true/false
- includes(): 判断字符串是否包含指定字符串,返回true/false
- trimStart()、trimEnd(),去除字符串开头空格或者结尾空格
16、vuex实现原理
Vuex 实现原理的简要说明:
-
State(状态):Vuex 的核心概念之一是 State,它表示应用程序中需要共享的数据状态。State 被存储在一个单一的 JavaScript 对象中,并可以通过
this.$store.state
在应用的任何组件中访问。 -
Getter(获取器):Getter 可以认为是 Vuex 中的计算属性。它们允许从 Store 中派生出一些状态,这样可以在组件中对派生状态进行复用,而无需重复执行计算。
-
Mutation(变更):Mutation 用于修改 State 中的数据。它们必须是同步函数,并且每个 Mutation 都有一个字符串类型的名称和一个调用时提供的载荷(payload)。通过提交 Mutation 来触发状态的变更。
-
Action(动作):Action 提供了一种处理异步操作的方式。Action 类似于 Mutation,但是可以包含异步操作和逻辑。它们被触发时会分发到 Mutations 中,从而实际改变 State。
-
Module(模块):Vuex 允许将 Store 分割成模块,每个模块拥有自己的 State、Getter、Mutation 和 Action。模块化使得大型应用程序的状态管理更加简单和可维护。
Vuex 的实现原理可以概括如下:
-
创建一个 Vuex Store 对象,通过将包含 State、Getter、Mutation 和 Action 的模块注册到 Store 中来设置应用程序的状态管理。
-
当组件需要访问状态时,可以通过
this.$store.state
访问状态对象。 -
组件可以通过提交 Mutation 来改变状态,使用
this.$store.commit('mutationName', payload)
提交 Mutation。 -
如果需要处理异步操作或复杂逻辑,组件可以分发 Action,使用
this.$store.dispatch('actionName', payload)
分发 Action。 -
Getter 可以用于派生一些状态,并且可以在组件中进行复用。
-
最终,Store 协调所有组件之间的状态管理,保证状态的一致性。
总的来说,Vuex 实现了一个单一的状态树(State),并提供了一套机制来修改状态的方式(Mutation 和 Action)。这使得多个组件之间的状态共享和管理变得更加简单和可预测。
在Vue.js中使用Vuex时,使用Vue.use()方法将Vuex插件安装到Vue实例中。这个过程会在Vue应用初始化阶段被调用,并且会自动执行一些操作来让Vuex在应用中起作用。
具体而言,当我们使用Vue.use(Vuex)时,Vue会执行以下操作:
调用Vuex.install()方法:这是Vuex插件的安装方法。它会被调用并传入Vue构造函数作为参数。在这个方法中,Vuex会进行一些初始化工作。
创建一个全局的Vue实例属性$store:$store是一个指向Vuex存储实例的引用,在整个应用程序中可以通过this.$store访问。Vuex.install()方法会为Vue.prototype添加$store属性,使每个组件都能够访问到这个全局的存储实例。
注册全局混入(Global Mixin):通过Vue.mixin()方法,将一个全局混入对象注册到Vue实例中。这个混入对象会将Vuex的状态和计算属性注入到每个组件中,从而实现每个组件都能够访问到Vuex的状态。
设置Vue的beforeCreate钩子:Vue的beforeCreate钩子函数会在组件实例创建之前被调用。在这个钩子函数中,会检查每个组件实例的options对象,如果有store选项,则将其赋值给组件实例的$store属性。这样,每个组件就可以通过this.$store访问到Vuex的存储实例。
通过以上步骤,使用Vue.use(Vuex)会在Vue应用中启用并集成Vuex,并且让每个组件都能够方便地访问和操作Vuex的状态管理功能。
17、一维数组转换成树
const data = [ { id: 1, name: 'Node 1', parentId: null }, { id: 2, name: 'Node 1.1', parentId: 1 }, { id: 3, name: 'Node 1.2', parentId: 1 }, { id: 4, name: 'Node 1.1.1', parentId: 2 }, { id: 5, name: 'Node 2', parentId: null }, { id: 6, name: 'Node 2.1', parentId: 5 }, { id: 7, name: 'Node 2.2', parentId: 5 }, ];
function arrayToTree(data) { const map = {}; // 哈希表,用于存储每个节点的引用 // 遍历数组,创建节点并将节点存储到哈希表中 data.forEach(node => { const { id, name, parentId } = node; map[id] = { id, name, children: [] }; }); // 遍历数组,构建树状结构 const tree = []; data.forEach(node => { const { id, parentId } = node; const treeNode = map[id]; if (parentId === null) { // 根节点 tree.push(treeNode); } else { // 子节点 const parentNode = map[parentId]; if (parentNode) { parentNode.children.push(treeNode); } } }); return tree; } const treeData = arrayToTree(data); console.log(treeData);