面试造火箭,入职拧螺丝,不管是前端实习生还是前端老鸟,面试时候会遇到各种前端八股文。本人因此收集广大求职者被面试官问到的前端八股文,从JS基础,CSS,Vue,React,甚至浏览器的底层原理等叼钻问题,去繁从简,言简意赅,适合前端面试用,以下问题答案随机排列,仅供参考。
1. attribute 和 property 的区别是什么?
Attribute自定义属性,prop是内置属性。
2. ES6 中数组结构赋值都有哪几种操作方式?
正常赋值,解构赋值,扩展运算符
3. Vue 组件通讯有哪些方法?
有6种方式
- Props 和 Events。通过 props,父组件向子组件传递数据,子组件通过 events 构造函数向父组件发送数据
- $parent 和 $children。$parent 属性指向父组件,$children 属性指向子组件。通过 $parent 和 $children 属性,组件可以直接访问父组件和子组件的数据和方法。
- $refs。$refs 属性是一个对象,包含了在组件中使用 ref 属性命名的子组件或 DOM 元素的引用。通过 $refs 属性,组件之间可以相互引用和调用。
- Event Bus。Event Bus 是一个简单的 Vue 实例,可以通过 $emit 方法向其他 Vue 组件发送事件,也可以通过 $on 方法接收其他 Vue 组件发送的事件。
- Provide 和 Inject。Provide 和 Inject 允许父组件将数据传递给所有后代组件,而不是只传递给直接子组件。Provide 和 Inject 是一种不同于 props、events 和 $parent/$children 的组件通信形式,是一种更加灵活和封装性更强的通信方式。
4. ES6 Map 和 WeakMap 有什么区别?
weakMap 只接受 对象作为key,该对象的引用为弱引用,只要该对象的其他引用被删除,该对象就会被垃圾回收,因此weakmap成员数量不稳定,没有size属性,没有clear方法,也不能遍历
5. 简述浏览器渲染过程?
1、解析html文档构建dom树 2、构建渲染树 3、布局和绘制渲染树
6. promise 中常用的方法有哪些?
then回调,resolve成功回调,reject失败回调,all并发请求任意一失败即返回,all selected并发请求全部结束返回,race任意一个任意一个成功或者失败返回
7. 为什么 typeof null 是 Object?
因为typeof 是根据前四位的bit来判断类型,null和object都是0000
8. 谈谈你对深拷贝和浅拷贝的理解?
就是内存空间的问题吧,浅拷贝就是复制内存地址,拷贝了n份,有一份改了,其他都会变,深拷贝就是重新去分配一块内存进行复制,每个拷贝的值都是不同的内存地址,现有一些前端工具库有实现深拷贝方案,平常层级不深,没有特殊格式(例如函数,时间等等)可以直接序列化处理,层级较深可以递归处理
9. Vue 常用的修饰符有哪些应用场景?
.stop:阻止事件冒泡 .native:绑定原生事件 .once:事件只执行一次 .self :将事件绑定在自身身上,相当于阻止事件冒泡 .prevent:阻止默认事件 .caption:用于事件捕获 .once:只触发一次 .keyCode:监听特定键盘按下 .right:右键
10. ES6 数据结构中 Set 和 Map 有什么区别?
Set可以用于数组去重,因为Set值唯一,而Map不唯一 Map存储的是键值对,可以通过get方法取值而Set不行,Set存储的是值
11. 浏览器是怎样解析 CSS 选择器的?
从右到左解析,根据选择器遍历dom树,将样式加到对应的dom元素上。
12. Vue 父子组件生命周期执行顺序是什么?
1.当父组件执行完beforeMount挂载开始后,会依次执行子组件中的钩子,直到全部子组件mounted挂载到实例上,父组件才会进入mounted钩子 2.子级触发事件,会先触发父级beforeUpdate钩子,再去触发子级beforeUpdate钩子,下面又是先执行子级updated钩子,后执行父级updated钩子 总结:父组件先于子组件created,而子组件先于父组件mounted
13. JS 中 BOM 和 DOM 有什么区别?
JS Bom 和Dom的区别 1、1.BOM是浏览器对象模型 2、DOM是文档对象模型 2、BOM没有相关标准。 DOM是W3C的标准。 3、BOM的最根本对象是window。DOM最根本对象是document
14. Vue3.0 性能提升主要是通过哪几个方面体现的?
1、响应式实现优化,vue2使用数据劫持需要递归去做响应式处理,vue3使用代理则一步到位; 2、diff算法优化,vue2同层全量对比,vue3使用了静态标记、静态提升、函数缓存等去优化。
15. Vue 中组件的 data 为什么是一个函数?而 new Vue 实例里,data 可以直接是一个对象?
因为组件可能在应用中多次被使用而被多次实例化,组件的是个函数可以确保每个实例化后的实例有独立的作用域,从而确保组件实例化后的data中的数据不会相互污染。而new Vue生成的是根应用实例,只有一个。
16. 箭头函数和普通函数之间有什么区别?
- 箭头函数不绑定this 使用上层作用域的this
- 箭头函数没有arguments,使用上层的arguments
- 箭头函数不可new 因为没有this,new要把函数的prototype赋值给this的__proto__
- 箭头函数不会函数提升
- 不能用call和apply绑定
17. 页面导入样式时,使用 link 和 @import 有什么区别?
- link是由html提供的,@import是由css提供的。
- link会与页面加载时同步加载,@import在页面加载完成才会加载。
- link没有兼容性问题,@import存在兼容性问题,只有i5以上才能识别。
- link的权重会更高一些
18. JS 实现继承的方式有哪些
原型链,组合继承,寄生组合继承,类继承
19. ES6 中的 Module 和 CommonJS 模块有什么区别?
Module使用导出是export,导入是import是静态的,不立即执行 Commonjs使用的导出是module.exports方式,导入是require是动态的,立即执行的
20. JS 引用数据类型有哪些?
function,object,array
21. Vue3.0 里为什么要用 Proxy API 替代 defineProperty API?
proxy监听对象,defineproperty监听属性,当defineproperty监听对象时需要递归绑定监听,而proxy就不需要。而且defineproperty监听的对象添加属性时会监听不到。
22. JS 中 bind、call、apply 之间有什么区别?
都是用来改变this指向的。 bind返回的是一个函数,需要主动调用。 call,apply为立执行,apply传递数组,call散列传参(可分多次传入)
23. for 循环和 map 循环有什么区别?
for循环性能比map循环性能高,map循环更便于维护;map返回一个新数组,map是基于数组的循环的语法糖,而for是原生js语法;在计算上说,map是基于回调的,for是直接执行的。
24. JS 中的 Array.splice() 和 Array.slice() 方法有什么区别?
splice()返回符合条件的新数组,且会改变原数组;slice()返回符合条件的数组,并不会改变原数组
25. Vue 中组件和插件有什么区别?
组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue。 插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身。
26. computed有哪些特性
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当 computed 内有异步操作时无效,无法监听数据的变化
27. http缓存有哪些?
- 强缓存:发送过得请求强行缓存,有效期内直接使用,不用重发请求
- 协商缓存:如果缓存过期,缓存的数据没有发生改变,服务器返回304,不返回内容。数据就能继续用了。
28. 什么是防抖与节流?应用场景举例
- 防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 应用场景: 提交按钮、用户注册时候的手机号验证、邮箱验证。比如函数设置一个传入参数,内部声明一个timeout变量,然后返回一个函数,在回调函数内部设置一个setTimeout方法作为定时器,通过apply改变函数的this,再赋值给变量timeout,最后通过clearTimout方法清除timeout变量,也就是清除了定时器
- 节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。 应用场景: window对象的resize、scroll事件 拖拽时候的mousemove 射击游戏中的mousedown、keydown事件 文字输入、自动完成的keyup事件,节流是通过闭包和setTimeout定时器来处理,在函数内声明一个变量time值为true和设置一个传入参数,然后在回调函数内部判断变量是否true,如果不是那就是return,然后再设置一个setTimeout定时器函数,定时器函数内部用apply改变传入参数的this的指向,并且定时器函数重新设置time变量为true
29. CSS 中实现元素水平垂直居中的方式有哪些?
- 设置绝对定位,然后上下左右值设为0,margin为auto。
- 使用flex布局,justify-content:center,align-items:center。
- 绝对定位,设置left,top为50%,transform:translate(-50%,-50%)
- 子元素display:inline-block,再在父元素上text-aligen:center
- 设置文本的话,行高+text-align
30. Vue 中 computed 和 watch 有什么区别?
computed是计算属性,依赖其他属性缓存的,不支持异步操作。 watch是监听器,不支持缓存,可以异步操作
31. ES6的新特性有哪些
- let定义变量,const定义常量
- Object.keys() 对键名的遍历,Object.values() 对键值的遍历,Object.entries()对键值对的遍历
- promise 优雅处理异步请求
- async/await 更好解决回调地狱
- 箭头函数
- class类
- symbol新类型。在某些情况下,我们需要确保属性名是唯一的,这时候就可以使用
Symbol
类型来创建一个全局唯一的字符,避免属性名或者方法名发生冲突。 - set集合
- 导入inport,导出export default
- for of 遍历键值对的值,for in 遍历键值对的键
- 模块字符串反引号 ``
- 数组对象的结构赋值 . ..
- 结构 {} 比如通过`${变量} `对变量进行拼接处理
- 函数传参可以有默认值
- spread操作符,被用于迭代器中
- rest操作符,用于获取函数的多余参数,这样就不需要使用arguments对象了
32. TCP 和 UDP 的区别是什么?并分别举例他们的应用场景有哪些?
个人认为最主要的区别是安全性,tcp通过三次握手不会出现丢包现象,udp只管发送。而且udp可以用来进行udp打洞,不需要端口映射就能与外网ip进行通信,p2p就是利用这种原理。音视频的的实时通讯也是用的udp因为传输量大,但是对数据的安全性要求不高,丢失一些像素并不影响整体,所以多采用udp。对于一些需要准确度较高的信息,比如QQ消息需要采用tcp通讯,QQ视频等可以使用udp。
如果面试官继续追问三次握手有哪些,或者问四次挥手是什么,可以简单回答。三次握手和四次挥手是TCP协议中用来建立和终止TCP连接的过程。三次握手是建立一个TCP连接的过程,需要三个步骤:客户端发送连接请求报文段,服务器收到请求并发送确认报文段,客户端收到确认并发送确认报文段。四次挥手是终止一个TCP连接的过程,需要四个步骤:客户端发送终止请求报文段,服务器收到请求并发送确认报文段,客户端收到确认并发送确认报文段,服务器收到确认并终止连接。
33. vue的双向绑定原理是什么?里面的关键点在哪里?
Vue数据双向绑定原理是通过数据劫持结合发布者-订阅者模式的方式来实现的,首先是对数据进行监听,然后当监听的属性发生变化时则告诉订阅者是否要更新,若更新就会执行对应的更新函数从而更新视图。通过Object.defineProperty()
来劫持各个属性的setter, getter
,在数据发生变动时通知Vue实例,触发相应的getter和setter回调函数。
关于VUE双向数据绑定,其核心是 Object.defineProperty() 方法
Object.defineProperty(obj, prop, descriptor)
这个方法内有三个参数,分别为 obj(要定义其上属性的对象)、prop(要定义或修改的属性)、descriptor(具体的改变方法)
简单来说,就是用这个方法定义一个值,当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,同时又调用了里面的set方法
let obj = {}
Object.defineProperty(obj,'youjia',{
get:function(){
console.log('调用了get方法')
},
set:function(){
console.log('调用了set方法')
}
})
obj.youjia;
obj.youjia = 'jiayou'
34. 单向绑定与双向绑定的区别,适合的场景?
- 单向绑定:单向绑定的优点是相应的可以带来单向数据流,这样做的好处是所有状态变化都可以被记录、跟踪,同时组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性。缺点则是代码量会相应的上升,数据的流转过程变长,从而出现很多类似的样板代码。同时由于对应用状态独立管理的严格要求(单一的全局store),在处理局部状态较多的场景时(如用户输入交互较多的“富表单型”应用),会显得繁琐。
- 双向绑定:优点是在表单交互较多的场景下,会简化大量业务无关的代码。缺点就追踪局部状态的变化比较麻烦,潜在的行为太多也增加了出错时 debug 的难度。同时由于组件数据变化来源入口变得可能不止一个,造成数据追溯比较麻烦,不过一些浏览器可以通过安装插件来查看数据流动
35. 常用伪元素有哪一些?
- a标签::hover :visited ::before ::after
- p标签::first-child ::first-letter ::first-line
- li标签 :first-child :last-child ::before ::after
- input :focus ::placeholder
36. 移动端如何适配不同屏幕尺寸?
- 媒体查询:使用CSS3的媒体查询功能,根据不同的屏幕尺寸和方向,应用不同的样式规则。通过设置不同的样式,可以适配不同的设备。个人认为是一种相对比较麻烦的方案,需要在每个页面上写媒体查询的代码。
- 百分比布局:使用百分比单位设置元素的宽度、高度和边距等。这样可以根据父元素的尺寸自动调整子元素的大小,实现一定程度的自适应效果。
- 因为不同属性的百分比值,相对的可能是不同的参照物,所以百分比往往很难统一,在移动端适配中使用是非常少的。
- 弹性盒子布局:使用CSS3弹性盒子布局模型,通过设置弹性容器和弹性项目的属性,实现灵活的布局。弹性盒子布局可以根据容器的尺寸和内容的大小,自动调整项目的 布局和排列。
- rem单位 + 动态的font-size:使用rem(根元素字体大小的倍数)作为单位来设置元素的尺寸。通过设置根元素的字体大小,可以控制整个页面的缩放比例,从而实现不同设备的适配。
- Viewport单位:使用viewport单位(例如vw、vh、vmin、vmax)来设置元素的尺寸。vw是窗口宽度的1%,vh窗口高度的1%,viewport单位是相对于浏览器窗口的尺寸来计算的,可以根据窗口的大小进行自适应布局。
- CSS预处理器的mixin和函数:使用CSS预处理器(如Sass或Less)的mixin和函数功能,可以根据不同的设备尺寸生成对应的样式代码。通过定义不同的mixin或函数,可以根据需要生成不同的样式规则。
37. less与sass的区别
共同点:less与sass都是css的预处理器,增加了变量、嵌套、函数、语句、继承等概念,基本思想都是用编程的思路编写css代码。
区别:
- sass通过ruby编译,需要Ruby环境; less是通过js编译
- Sass 变量符号是 $, Less 变量符号是 @
- sass支持条件语句,可以使用if{}else{}、for{}循环等,less不支持
- sass引用的外部文件命名必须以_开头, less引用外部文件和css中的@import相同
- sass的混入是使用@mixin(){}–@include(), less的混入是使用.属性名(){}–.属性名()
- sass的继承使用@extend, less的继承使用&:extend()
38. 本地存储有哪些,都有什么区别?
-
sessionStorage。sessionStorage并不持久化,在窗口关闭时会被清除
-
localStorage。localStorage是持久化存储,窗口关闭时不会被清除,只能手动清除,数据保存更多,可以达到5Mb
-
cookie。在客户端请求服务器端和服务器响应时,cookie始终被携带在http请求,可以设置过期时间,若未设置过期时间,在浏览器窗口关闭时,cookie就失效了,httpOnly设置为true,则js不能通过document.cookie操作cookie。
-
session。session是基于cookie的,不过session保存在服务器端,客户端通过sessionId,读取到相对应的数据
39. Let、const、var三者有什么区别?
使用 var 时,变量会自动提升到函数作用域的顶部
let声明的范围是块作用域,不会在作用域变量提升,不能在同一个作用域内重复声明同一个变量,比如let声明的变量作用域仅限于or循环块内部
const与let基本相同,不过const要初始化变量,const变量的引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制
40. 数组去重有哪些办法?
- Set()方法。Set本身是一个构造函数,用来生成Set数据结构,它类似于数组,但里面的成员是唯一的,不重复的。
- indexOf()。先for循环数组,然后在循环内部通过indexOf方法返回一个整数值,指出 string对象内子字符串的开始位置。如果没有找到该字符串则返回-1
- includes方法。 先for循环数组,然后在循环内部通过includes方法查询字符串中是否包含某一个元素 返回的是一个布尔值。
- slice方法。 Array.slice(num1,num2) 截取数组,将截取到的数组返回,不改变原数组。有两个参数,第一个是开始截取数组的位置,第二个是停止截取数组的位置
- 双层for循环结合splice方法。外层循环元素,内层循环时比较值如果有相同的值则跳过,内层循环时用的是splice方法去重,不相同的push进数组
- 数组递归去重。函数内部调用自身函数,先排序,然后从最后开始比较,遇到相同,则删除
- sort() 方法。sort() 方法可以对数组进行排序,然后从最后通过for循环开始比较,不相同则push进数组
41. Vuex有几个属性及作用?
- state:vuex的基本数据,用来存储变量
- geeter:从基本数据(state)派生的数据,相当于state的计算属性
- mutation:提交更新数据的方法,必须是同步的(如果需要异步使用action)。每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。
- action:Action 提交的是 mutation,而不是直接变更状态,Action 可以包含任意异步操作。
- modules::模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
42. 判断一个变量是否是数组,有哪些办法?
-
使用 Array.isArray() 方法,如果是则返回true,否则返回false
-
instanceof 运算符。用于检测某个对象是否是指定类(构造函数)的实例,因此可以使用该运算符判断变量是否为数组。如果是则返回true,否则返回false
-
通过原型链判断是否具有和数组同一原型链,如果是则返回true,否则返回false。变量.proto === Array.prototype。变量.constructor === Array。
43. 哪些遍历方式会改变原数组?
for,for of,for in,forEach
44. 什么是BFC
BFC,即“块级格式化上下文”(Block Formatting Context),是 CSS 中一个重要的概念,它指的是一个独立的渲染区域,让块级盒子在布局时遵循一些特定的规则。
-
根元素或包含它的元素
-
浮动元素(元素的 float 不是 none)
-
绝对定位元素(元素的 position 为 absolute 或 fixed)
-
行内块元素(元素的 display 为 inline-block)
-
表格单元格(元素的 display 为 table-cell)
-
表格标题(元素的 display 为 table-caption)
-
overflow 值不为 visible 的块元素
应用场景:清除浮动,避免 margin 重叠,实现多栏布局,防止浮动元素遮盖
45. Flex:1 包含哪三种属性
flex:1 是三个属性的连写:flex-grow(属性定义的是项目的方法比例,默认为 0)、flex-shrink(属性定义了项目的缩小比例)、flex-basis(属性定义了在分配多余空间之前,项目占据的主轴空间)
46. nextTick 的作用是什么?他的实现原理是什么?
作用:vue 更新 DOM 是异步更新的,数据变化,DOM 的更新不会马上完成,nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。
实现原理:nextTick 主要使用了宏任务和微任务。
47. 宏任务和微任务
宏任务:整体代码script、setTimeout、setInterval、setImmediate、i/o操作(输入输出,比如读取文件操作、网络请求)、ui render(dom渲染,即更改代码重新渲染dom的过程)、异步ajax等
微任务:Promise(then、catch、finally)、async/await、process.nextTick、Object.observe(⽤来实时监测js中对象的变化)、 MutationObserver(监听DOM树的变化)
执行顺序:在 JavaScript 中,微任务会优先于宏任务执行。这意味着在当前任务执行结束后,所有微任务都会被立即执行,而宏任务只有在所有微任务执行完毕后才会执行。这意味着可以将一些耗时的操作放入宏任务队列中,从而避免阻塞当前任务的执行。
48.React hooks原理
hooks 的实现就是基于 fiber 的,会在 fiber 节点上放一个链表,每个节点的 memorizedState 属性上存放了对应的数据,然后不同的 hooks api 使用对应的数据来完成不同的功能。
49. Vue的渲染原理
vue是一个MVVM渐进式框架,vue框架中数据会自动驱动视图。
在MVVM架构下,View
和Model
之间并没有直接的联系,而是通过ViewMode
进行交互,Model和ViewModel之间的交互是双向的,因此View数据的变化会同步到Model中,而Model数据的变化也会立即反应到View上。
Vue 的响应式原理是核心是通过 ES5 的保护对象的 Object.defindeProperty
中的访问器属性中的 get
和 set
方法,data 中声明的属性都被添加了访问器属性,当读取 data 中的数据时自动调用 get 方法,当修改 data 中的数据时,自动调用 set 方法,检测到数据的变化,会通知观察者 Wacher,观察者 Wacher自动触发重新render 当前组件(子组件不会重新渲染),生成新的虚拟 DOM 树,Vue 框架会遍历并对比新虚拟 DOM 树和旧虚拟 DOM 树中每个节点的差别,并记录下来,最后,加载操作,将所有记录的不同点,局部修改到真实 DOM 树上。
50. 什么是虚拟dom?vue如何把虚拟dom转化成dom?
虚拟dom就是一段js形式的html
代码,也就是jsx,jsx有tag标签,props属性(包括id,className,style,事件等),children子元素,数组或者字符串,vue通过diff算法,也就是通过新旧的虚拟dom对比,计算出更小的更新范围 核心算法就是:同层级比较,不做跨级比较;tag不相同则直接删除重建,不做深度比较;tag和key相同,则认为是相同节点,不做深度比较
vue源码中做diff算法的函数是patch函数
51. vue2 如何监听数组的变化
在Vue 2中,底层是通过重写数组的原型方法来实现对数组变化的监听。具体来说,Vue 2使用了一个名为Observer的类来劫持数组的原型方法,使其在调用这些方法时能够触发相应的变化通知。
当Vue 2初始化一个响应式对象时,如果对象是一个数组,Vue会将数组的原型指向一个经过改造的Array原型对象。这个改造后的原型对象中重写了数组的一些常用方法,如push、pop、shift、unshift、splice、sort和reverse等。当调用这些方法时,Vue会在内部进行一系列的操作,包括触发变化通知、更新视图等。
52. react18 新特性
1. 默认情况下,setState
方法会以异步方式进行更新。
2. React 18 提供了 flushSync
方法,可以强制执行同步更新。
import { flushSync } from 'react-dom';
// 同步更新
flushSync(() => {
this.setState({ count: this.state.count + 1 });
});
3. 使用 useTransition。
useTransition
是 startTransition
的 hook 版本。提供的优先级控制来平衡不同任务之间的更新,延迟操作时控制异步更新的优先级。
4. startTransition
延迟更新组件状态,异步请求可以在后台执行,不会影响应用程序的交互性能。
5. createRoot createRoot
是一个新的入口函数,用于创建根 React 组件。它可以替代原先的 ReactDOM.render
方法,使得开发者可以将多个根节点渲染到一个页面上。
6. useMutableSource
用于在多个组件之间共享数据状态。
7. useDeferredValue
用于组件状态的更新推迟到未来的帧中。这对于处理与用户输入相关的操作非常有用,可以避免在频繁输入时产生连续的重渲染。
8. React.StrictMode
React 严格模式主要包含以下几个方面的检查和提示:
- 识别不安全的生命周期方法,提示开发者修改,这些方法可能会导致意外的副作用或错误。
- 检测意外的副作用,例如:多余的重新渲染、不符合预期的函数调用等。
- 检测某些过时的 API 使用,提供更好的替代方案。
- 检测警告信息,使其更加明显和易于发现。
9. 并发模式 React Concurrent Mode 在传统的 React 中,更新组件树时会阻塞用户界面的响应,可能导致卡顿和延迟。利用并发模式,React 可以将渲染过程分解为多个小任务,并根据优先级来动态调整任务执行的顺序。这样,在浏览器空闲时间或网络请求等异步操作期间,React 可以暂停当前任务,执行其他具有更高优先级的任务,以实现更爽快的用户交互体验。
53. 说说 Vue 和 React 的区别
数据渲染:
Vue
双向绑定,修改数据自动更新视图,而React
单向数据流,需要手动setState
Vue template
结构表现分离,React
用jsx
结构表现融合Vue2
利用基本都是Mixin
,React
可以用高阶函数、自定义hook
实现- 都支持服务端渲染,都有虚拟
DOM
,数据驱动,组件化开发,响应式,组件通信,生命周期,Diff
,都有状态管理Vuex/Pinia
、Redux/Mobx
React hook
是根据调用顺序来确定下一次重新渲染时的state
是来源于哪个,所以有一些限制,比如不能在循环/条件判断/嵌套函数里使用,而且必须在函数最顶层调用hook
等Vue3 hook
是基于响应式实现的,它是声明在setup
里,一次组件实例化只调用一次setup
,而React
每次重新渲染都要重新调用,而且可以在循环/条件判断/嵌套函数里使用,并且正因为是基于响应式实现的,还自动实现了依赖收集,而React
需要手动传入依赖等Vue2
响应式的特点就是依赖收集,数据可变,自动派发更新,初始化时通过Object.defineProperty
递归劫持data
所有属性添加getter
/setter
,触发getter
的时候进行依赖收集,修改时触发etter
自动派发更新找到引用组件重新渲染
技术选项:
Vue2
响应式的特点就是依赖收集,数据可变,自动派发更新,初始化时通过Object.defineProperty
递归劫持data
所有属性添加getter
/setter
,触发getter
的时候进行依赖收集,修改时触发etter
自动派发更新找到引用组件重新渲染Vue3
响应式使用原生Proxy
重构了响应式,一是proxy
不存在响应式存在的缺陷,二是性能更好,不仅支持更多的数据结构,而且不再一开始递归劫持对象属性,而是代理第一层对象本身。运行时才递归,用到才代理,用effect
副作用来代替Vue2
里的watcher
,用一个依赖管理中心trackMap
来统一管理依赖代替Vue2
中的Dep
,这样也不需要维护特别多的依赖关系,性能上取得很大进步- 相比
Vue
的自动化,react
则是基于状态,单向数据流,数据不可变,需要手动setState
来更新,而且当数据改变时会以组件根为目录,默认全部重新渲染整个组件树,只能额外用pureComponent
/shouldComponentUpdate
/useMemo
/useCallback
等方法来进行控制,更新粒度更大一些
Diff 算法:
Vue2
是同层比较新老vnode
,新的不存在老的存在就删除,新的存在老的不存在就创建,子节点采用双指针头对尾两端对比的方式,全量diff
,然后移动节点时通过splice
进行数组操作Vue3
是采用Map
数据结构以及动静结合的方式,在编译阶段提前标记静态节点,Diff
过程中直接跳过有静态标记的节点,并且子节点对比会使用一个source
数组来记录节点位置及最长递增子序列算法优化了对比流程,快速Diff
,需要处理的边际条件会更少React
是递归同层比较,标识差异点保存到Diff
队列保存,得到patch
树,再统一操作批量更新DOM
。Diff
总共就是移动、删除、增加三个操作,如果结构发生改变就直接卸载重新创建,如果没有则将节点在新集合中的位置和老集合中的lastIndex
进行比较是否需要移动,如果遍历过程中发现新集合没有,但老集合有就删除
性能优化:
React
迭代是增加了一个个避免刷新的钩子函数或者 API
还有采用 Fiber
的架构来做时间分片也是来优化渲染的性能。而 Vue1
/Vue2
/Vue3
每个版本虽然改的东西多,但核心都是围绕响应式来优化的,所以我觉得这是这两框架之间最重要的区别。
React hook
底层是基于链表实现的,每次组件被 render
的时候都会按顺序执行所有 hooks
,而且正因为底层是链表,每个 hook
的 next
是指向下一个 hook
的,所以我们写代码是不能在不同的 hooks
调用里使用条件判断/函数嵌套之类的,因为这会导致执行顺序不对,从而出错。而 Vue hook
只会被注册调用一次,因为它是声明在 setup
里,一次组件实例化只调用一次 setup
,Vue
之所以能避开这些问题,主要还是得益于数据响应式,不需要链表对 hooks
进行记录,而是直接对数据代理观察,但它也有困扰的地方,就是不得不返回一个包装对象,通过 .value
获取。面试官问为什么会有这样的问题?答:因为在 JS
里基础类型只有值,没有引用,或者说只存在栈里,使用完就回收了,无法追踪后续变化,自然做不到数据的代理和拦截,这算是这个设计的一个缺点吧。
再比如编译优化的问题,Vue
能够做到数据劫持,再到 Vue3
动静结合的 Diff
思想也得益于它的模板语法实现了静态编译。就是能做到预编译优化,可以静态分析,在解析模板时能根据解析到的不同的标签、文本等分别执行对应的回调函数来构造 AST
,而 React
虽然 JSX
语法更加灵活,可也正是因为这样导致可以优化的地方不足,重新渲染时就是一堆递归调用 React.createElement
,无法从模板层面进行静态分析,也就做不到双向绑定,即使是很厉害的 fiber
,也是因为伤害已经造成,所以通过时间分片的优化来弥补伤害吧,因为已经无法在编译阶段进行优化了,这也是这个设计所带来的问题吧。
两者的项目选型
从加载速度,运行时性能来说,我觉得这两个框架综合各种场景应该是没什么质的差别的。硬要说的话,Vue
在更新时性能优化方面需要的心智负担可能会少那么一点,特别是 Vue3
,而 React
如果不注意,容易导致一些组件无用的 Diff
,但其实实际项目中真正能遇到这种性能瓶颈的也是极少数,所以(这里有两种说法):
(如果公司主要用 Vue
技术栈的话):所以总的来说我觉得 Vue
性能上会更有优势一点,特别是 Vue3
更加灵活,有很好的可扩展性,同时有更快的渲染速度和更小的打包体积。从 mixins
到 HOC
到 render props
再到 hooks
,React
基本已经废掉了过去很多基于组件的逻辑抽象模式,抹掉了 JSX
对比模板的一个优势,Vue3
中现在也都能做到,所以我会偏向 Vue3
。
(如果公司主要用 React
技术栈的话):所以总的来说我觉得要是一些不大的系统或者 H5
就用 Vue
,因为不管是上手还是开发难度上都很简单,开发效率也高嘛,而且它有更小的打包体积,毕竟在移动端网络差异大的情况下,资源体积是非常重要的。但像是一些中后台系统,或者一些大点的项目,会越做越大的,多人协作开发的,就用 React
,因为它的函数式编程有更加灵活的结构和可扩展性,丰富的生态圈和工具链,解决方案多,后期也更方便迭代与维护,还适用原生 APP
,所以我会偏向 React
。
什么是冒泡排序呢?
就是将数组的前一项元素与后一项元素进行比较,将大的一项往后移动一位,通过循环比较相邻的两位大小,不断将大的后移,实现数组元素的大小排序。