面试和准备面试时整理的题目,有错误欢迎指出
-
VUE2和VUE3的区别
- 双向绑定原理不同:vue2通过Object.defineProperty给每一个对象进行get和set操作,数据多了会造成很大的开销。而vue3是通过proxy,当你用到这个数据时他才会拦截,有13种拦截方法。
- object.defineproperty相关
- vue2使用object.defineproperty来实现响应式系统,通过这个为对象的每个属性添加了getter和setter方法,这样数据变化的时候vue就能检测到并进行更新。
- 缺点:
- 不能检测到对象属性的添加或删除,对象是键值对的集合,对象的属性就是指这些键值对中的键和与之相关的值,也就是说我们无法添加或删除这些键值对。
- 不能检测到数组通过索引设置新值或修改数组长度的变化。例如
:arr[0] = newValue 或 arr.length = newLength都是检测不到的。
- 解决方法:
- 提供了一些额外API:Vue.set用于向对象添加响应式属性。Vue.delete方法用于删除对象的响应式属性。
- Vue重写了某些方法如push、pop、shift、split等,使得这些方法能够触发视图更新
- Proxy相关
- vue3引入,可以更加自然地处理对象和数组的响应式,不需要像vue2一样特殊处理。用于创建一个对象的代理,从而实现基本操作的自定义(如属性查找、赋值、枚举等)。
- Proxy接受两个参数,target是要包装的对象,handler是一个处理器对象,包含了一组陷阱,这些陷阱对应于各种操作,比如获取属性get,set,删除deleteProperty等
- 解决Proxy只会代理对象的第一层的问题,Vue 3 的 reactive 方法会递归地遍历对象的所有属性,并对每个嵌套的对象也应用Proxy这样,无论对象的嵌套层次有多深,都能实现响应式的观测,判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理
const handler = { get(target, prop, receiver) { if (prop in target) { return Reflect.get(target, prop, receiver); } else { throw new ReferenceError(`Property '${prop}' does not exist.`); } }, set(target, prop, value, receiver) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('Age must be an integer.'); } } return Reflect.set(target, prop, value, receiver); } }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Alice proxy.age = 26; // 正常设置 console.log(proxy.age); // 26 try { proxy.height = 170; // 将抛出错误,因为 height 不存在于目标对象中 } catch (e) { console.error(e.message); // Property 'height' does not exist. } try { proxy.age = 'thirty'; // 将抛出错误,因为 age 必须是整数 } catch (e) { console.error(e.message); // Age must be an integer. }
-
生命周期钩子不同:
vue2 Vue3 beforeCreate setup created setup beforeMount onBeforeMount mounted onMounted beforeUpdate onBeforeUpdate updated onUpdated beforeDestroy onBeforeUnmount destroyed onUnmounted
- vue2:beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed
- vue3:setup,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted
- vue3支持多个根节点,vue2只能有一个
- Vue3的响应式数据在setup中定义,使用ref或者reactive来定义响应式数据。
- ref适合处理单个简单值,如数字、字符串等
- reactive适合处理复杂数据结构如对象或数组,会深度转换为响应式对象,对象中的每个属性都是响应式的
- vue2是选项式api,vue3是组合式api
-
let const var区别
-
let和const声明的变量属于他们所在块的作用域({}包围),仅在作用域有用,而var声明的变量属于所在的函数作用域
//外层作用域无法获取内层作用域 function a(){ let a=100; if(a==100){ let a=300 console.log(a) //300 } console.log(a) //100 } a() -
var声明变量会把这个变量放到所在函数顶部,但是初始化不会提升,即变量提升
-
const声明时必须赋值并且以后不能再重新赋值
-
var可以在相同作用域内重复声明一个变量,let和const不允许
-
在循环中定义变量,如果是用var定义,他会在函数或全局作用域中共享一个绑定,最后导致只会保持最后的值,除非使用立即执行函数传递变量。如果用let定义,每次循环迭代都会创建一个新的绑定,可以正常循环输出。
-
-
在浏览器输入URL会发生什么?
- 判断这是不是正确格式的url,是的话就解析,不是的话就用搜索引擎直接搜索
- DNS域名解析,转换成对应的ip地址
- 建立TCP连接,三次握手四次挥手
- 页面渲染,其中js会阻塞HTML的解析,css会阻塞js的执行,css不会阻塞dom树,会阻塞页面渲染:
- 解析HTML,搭建DOM树 --- 外部资源的话会提前加载
- 解析css,搭建样式树
- 将HTML与CSS相结合,搭建渲染树
- 根据渲染树计算布局
- 绘制在页面上
-
cookie,localStroage,sessionStroage
- cookie存储的数据会被浏览器自动放在http请求中,除非用expire设置过期时间,否则关掉页面就会消失,存放内容限制4kb
- sessionStroage用于本地存储一个会话中的数据,关掉浏览器数据就会消失,内容限制5MB
- localStroage是本地化存储,除非主动删除否则永远存在,内容限制5MB
-
Diff算法
vue使用了diff算法来优化其虚拟DOM的更新过程。diff的过程就是调用名为patch的函数,比较新旧节点,发现有不一样的地方就修改在真实DOM上,一样的节点就会去查找子节点。
-
http缓存
浏览器第一次向一个web服务器发起http请求后,服务器会返回请求的资源,并且在响应头添加一些有关缓存的字段,之后再请求该资源就可以视情况使用强弱缓存。
浏览器在加载资源的时候会先判断是否命中强缓存,如果命中就直接从本地缓存中获取数据,其中包含字段:
- expire字段是缓存到期时间,绝对时间
- cache-control是缓存最大储存时间,相对时间
如果没有命中强缓存就会向服务端发送请求看是否命中协商缓存,如果命中就返回304代表资源没改变,可以使用本地缓存,否则就返回新数据并更新资源。
-
js绑定事件的方法和步骤
-
内联,在标签里写onclick
-
dom属性绑定:
document.getElementById().onclick -
addEventListener:
document.getElementById("myButton").addEventListener("click", function() { alert('点击了按钮!'); });其中addEventListener(事件类型,事件处理函数,是否使用捕获机制)。第三个参数是true就是监听器在事件捕获阶段触发,false就是在事件冒泡触发,默认值是false。
-
8.事件机制
事件流:一个事件触发后会在子元素和父元素之间传播,分为三个阶段。捕获,目标和冒泡。
- 事件捕获:点击子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件
- 事件冒泡:当一个事件触发,同样的时间会在该元素的所有祖先事件中依次被触发,调用所有父级元素的同名事件。
- 事件委托:利用事件冒泡来减少注册次数,给父元素注册事件,当我们触发子元素,就会冒泡到父元素身上,从而触发父元素事件,再对子元素进行操作
-
闭包
闭包是一个可以访问外部作用域的内部函数,通过闭包达到封装性,可以隐藏信息,创建私有状态的函数,使得timer定时器、事件处理、ajax请求等异步任务更加容易
-
v-if和v-show
- v-if是动态的向dom树内添加或者删除DOM元素
- v-show本质是通过设置css中的display设置成none,控制隐藏
- 前端路由模式hash/history
- hash兼容性高,但是url会带上#
- history不会影响url美观,但是需要和后端配合上线
-
为什么VUE中的data是函数型
因为每个vue实例都需要自己的数据副本,如果不是函数而是字面变量,由于js对象的机制是基于原型链的,一个对象被挂载到vue实例的原型链上,所有实例就会共享这一个data对象,会造成数据污染,所以将data定义为一个返回新对象的函数,每个实例通过调用这个函数获得一份新的数据副本。
-
js的原型
js中每一个对象都有一个特殊的属性叫原型(prototype),他指向另一个对象,这个对象被称为原型对象,原型对象是用来共享属性和方法的。
原型分为隐式(proto)和显式(prototype),找属性或方法时是通过_proto_去找prototype
-
原型链
原型链其实是一个查找规则,当尝试访问一个对象的属性或方法时,如果这个对象本身没有这个属性方法,就回去查找它的原型,也就是它_proto_指向的prototype原型对象,直到找到object为止。这种路线被称为原型链。
-
生命周期钩子:activated和deactivated
这两个钩子是专门为keep-alive功能设计的,keep-alive是一个内置组件,被keep-alive包含的组件在第一次被创建之后被缓存起来,对于保持组件状态或避免重复渲染昂贵的组件很有用。组件并不是被销毁而是被冻结,状态保留。
- actived:当组件被缓存后,再次进入(比如v-if切换或者路由导航)将会调用这个钩子,可以来执行一些初始化操作。
- deactived:当组件被缓存后,离开组件,就会用这个钩子,可以执行一些清理操作比如暂停定时器、取消定时器。
-
前端缓存方式
- Service Workers :可以缓存资源的请求
- web Storage
- IndexDB :相当于浏览器中的数据库
- Cache API
- Http缓存头
- 怎么解决跨域问题
- CROS:服务器在返回响应报文的时候在响应头设置一个允许的header
- JSONP :利用script标签没有跨域限制的特点。只能处理get请求
- 通过web服务器:Nginx,node等
- node + vue + webpack + webpack-dev-server搭建的项目,跨域请求接口,直接修改webpack.config.js配置
- 设置相同的document.domain,两个页面就可以共享Cookie(此方案仅限主域相同,子域不同)
- 跨文档通信api:html5引入的api--postMessage方法
- webpack代理: WebpackDevServer 配置本地代理
-
虚拟DOM
虚拟DOM是对DOM的抽象,本质上是js对象,这个对象就是更加轻量的对DOM的描述,是一种用于优化网页渲染性能的编程概念。将真实DOM的数据抽取出来,以对象的形式模拟树形结构。
-
为什么需要虚拟DOM
直接操作DOM的成本较高,浏览器需要通过大量的计算来重新渲染页面,而虚拟DOM允许开发者先在内存中进行修改,然后只更新实际发生变化的元素,减少不必要的重绘和回流。
-
虚拟DOM优势
- 减少了直接操作DOM的成本
- 简化diff算法,高效
- 跨平台能力,可以用来构建原生移动应用
- 批量更新
- 判断数组的方法
- .isArray
- instanceof
- Object.prototype.toString.call(数组) 强制执行Object原型的toString方法
- Array.constructor === Array
- 检查数组特有的方法和属性:
typeof myVariable.push !== 'undefined' - typeof无法用来判断是不是数组,因为他返回object结果
-
深浅拷贝
深浅拷贝都是对于引用数据而言的,浅拷贝就是复制了原对象的引用地址,改变浅拷贝过来的变量值原变量也会改变。深拷贝是完全拷贝,改变拷贝后的变量值不影响原来的。
-
怎么实现深拷贝
-
递归调用:
const deepCopy = (obj) => { // 判断传入的值是否为一个对象,如果不是对象或者为null,则直接返回 if (typeof obj !== "object" || obj === null) { return obj; } // 判断对象的类型 注意这里不考虑包装类对象 if (Object.prototype.toString.call(obj) === "[object Date]") { return new Date(obj); } if (Object.prototype.toString.call(obj) === "[object RegExp]") { return new RegExp(obj.source, obj.flags); } if (Object.prototype.toString.call(obj) === "[object Error]") { return new Error(obj.message); } // 判断对象是类数组还是对象 let newObj = Array.isArray(obj) ? [] : {}; for (let item in obj) { if (obj.hasOwnProperty(item)) { // 确保只拷贝对象自身的属性 newObj[item] = deepCopy(obj[item]); } } return newObj; }; -
使用 JSON.stringify 加 JSON.parse :将对象转换为JSON字符串,再将字符串解析为一个新的对象(此方法只用于简单的数据结构)
-
lodash库:的 _.cloneDeep方法
-
Object.asgin({},):这个只有对象中没有嵌套对象时才可以实现深拷贝
-
structuredClone :原生API,可以深拷贝大多数数据类型,包括循环引用和特殊的对象类型。与json方法不同,他可以正确处理对象之间的循环引用。缺点是浏览器兼容性
-
watch和computed区别
watch:
-
当监听的数据发生变化时,对应的回调函数会被执行
-
适用于需要在数据变化时进行异步操作或开销较大的操作,比如网络请求、复杂逻辑计算等 computed:
-
监听数据变化时,相应的计算属性会重新计算结果
-
不支持异步,主要用于对同步数据的处理
-
适用于进行实时计算的场景
如果需要一个属性的值随着其他属性的变化而动态变化,就用computed,如果想监视某个属性去完成一段逻辑那么就用watch
-
为什么要使用axios
axios是一个基于promise的网络请求库
- 基于promise的异步处理,更加简洁,易于维护,可以通过then和catch来捕获处理错误
- 支持取消请求
- 可以批量发送请求
- 提供了丰富的配置项,比如超过时间、最大相应数据大小等,可以更加精细地控制请求行为
- 自动转化json数据,会将js对象序列化为json
- 设置了XSRF(跨站请求伪造)的防御机制
-
promise
promise是表示异步操作最终完成失败的一个对象,有三种状态,待定(pending),已兑现(fulfilled),已拒绝(rejected)
let promise = new Promise(function(resolve, reject) { //这俩是函数 resolve(value); // 调用resolve方法·,用结果 value 兑现 Promise reject(reason); // 调用reject方法,用错误原因 reason 拒绝 Promise });提供了.then和catch方法链式地处理成功的结果和错误结果,.then可以返回一个新的promise从而允许进一步的链式操作。 如果promise被拒绝,可以通过catch方法来捕获错误,或者通过then方法中的第二个回调函数来处理,但是第二个回调无法捕获第一个处理成功的函数抛出的错误
-
promise.all
处理多个promise实例的方法,接受一个实例数组作为参数,等所有实例都解决了Promise.all 返回的 Promise 也会解决,并且解决值是一个数组,包含所有传入promise的解决值。 如果一个失败了那么全部都失败,拒绝理由只有第一个失败的,其他的会被忽略
-
this指向
this代表了函数执行时的上下文环境,this的值取决于函数是如何被调用的
- 由new调用就绑定到新创建的对象身上
- 默认:全局对象
- 由上下文对象调用就是指向上下文对象
- 自 (ES5)起,当函数不是作为方法调用,也不是通过 new 关键字调用,并且在严格模式下时,this 不会默认指向全局对象,而是保持为 undefined。
- 如何改变this指向
- call
- apply:绑定数组
- bind:和call一样,但是会创建一个新的函数,一旦用bind绑定了一个函数的this,就无法再通过call或apply来改变this指向
- 箭头函数的this
- 箭头函数会捕获自己所在的函数上下文的this值作为自己的this值,自己本身并没有this值
- 箭头函数的this指向不能通过call,bind,apply进行更改
-
事件机制
js本身是一个单线程的语言,多线程是模拟出来的,机制是这样:如果是同步任务的话,会放入主执行栈中,异步任务的话会注册事件放入事件表中,等到指定的事情完成后,事件表会把这个函数移入事件队列,等到主线程的事件都完成后移入到主线程栈中执行
-
事件循环
事件循环是单线程下管理任务的执行顺序的一种运行机制,在这种机制下代码被分为两种类型,同步任务和异步任务。
-
同步任务:立即执行的代码块,按照编写顺序依次执行
-
异步任务:在某个条件满足或经过一段时间后才执行的代码块
事件循环机制可以让js高效处理多个并发任务
异步任务分为宏任务和微任务
宏任务有:
script(整体代码)
setTimeout
setInterval
setImmediate(Node.js 环境)
I/O 操作(例如文件读写、网络请求等)
UI 渲染(浏览器环境)
微任务有:
Promise 的回调
process.nextTick
queueMicrotask
MutationObserver(浏览器环境,用于监听DOM变化)
-
vue2的数据劫持不能监测长度,要是数组增加了怎么办
VUE为此提供了几个变异方法,这些方法是专门用来改变数组的方法,并且在改变数组的同时也会触发视图的更新,这些变异方法包括:push,pop,shift,unshift,splice,sort,reverse,当你想要改变数组,就应该使用这些变异方法而不是直接赋值
-
为什么v-for需要有key?
在vue中改变数据的话会生成一个新的虚拟DOM,然后vue会将虚拟DOM与旧的虚拟DOM进行比较更新,如果你不用key的话,vue就会优先复用已有的元素,可能会错误的导致dom元素与数据系那个不匹配的问题。而key能让vue精准识别每个元素,确保DOM和数据项的对应关系,快速比较,高效。
-
如果用索引当key会发生什么问题?
- 如果列表的顺序改变,vue会认为大部分的DOM都需要更新,因为比较新旧虚拟节点的算法是基于key的,所以key改变他就会觉得要更新,会进行不必要的DOM元素销毁和创建
- 数据错位:可能会将之前的状态应用到错误的元素上,就是因为索引不提供唯一性
-
es6的新特性
- let const
- 箭头函数
- 模版字符串
- 结构赋值
- 扩展运算符
- promise
- 类
- 新增数据类型:set,map,weakmap,weakset,symbol
-
为什么vue2是选项式api,到vue3就是组合式,有什么好处?
- 开发者可以根据业务需求自由地组织和拆分逻辑块
- 更容易抽取和复用
-
VUE3新组件
-
Fragment组件
Vue 3 支持渲染一个虚拟 DOM 片段,即在不需包裹元素的情况下渲染多个根节点。Fragment在这里是指Vue组件模板能够像HTML片段一样返回多个顶级元素的能力。就是支持多个根元素
-
Teleport组件
teleport 组件可以将模板的一部分挂载至指定的DOM元素上,不受父级样式影响。改变位置
-
Suspense组件
用于等待嵌套Promise解析,并渲染后备内容,解析完成后显示默认内容。有两个插槽:v-slot:default 插槽和 fallback 插槽。default 插槽用于定义异步组件成功加载后要显示的内容,而 #fallback 插槽则用于定义在异步组件加载过程中要显示的内容。
-
keep-alive组件:之前介绍过,缓存组件以避免不必要的重新渲染
-
Transition和TransitionGroup组件增强:新增更多样式
-
-
构建工具怎么理解
构建工具通常指的是软件开发过程中用来自动化完成项目构建过程的一系列软件工具,这些工具可以包括编译器、打包工具、测试框架等。
-
WeakMap和Map的区别
- weakmap结构与map对象相似,但只接受对象作为键名,且键名所引用的对象不会被计入引用次数,有利于垃圾回收。
- map中有size属性而且告诉你存了多少个键值对,weakmap中没有且不能遍历
- WeakMap通常用于需要关联额外数据到对象,但又不想影响这些对象的生命周期的场景,例如,缓存、元数据管理或实现私有属性。
-
选择ref还是reactive?
- ref: 如果处理的是基本数据类型或者想保持对某个对象的响应式引用时
- reactive:处理的是一个对象并且希望它的所有属性都是响应式。
-
居中布局
- 行盒/行块盒居中:text-align:center
- 块盒:定宽,设置左右margin为auto
- absolute元素:左右为0,margin左右为auto
- 单行文本垂直居中:line-height为整个区域的高度
- flex布局
- 定位布局
- static:默认值,表示没有定位
- relative:相对定位,相对于元素的正常位置进行定位
- absolute:绝对定位,相对于第一个非静态定位的父级元素进行定位,如果没有满足条件的父级就相对于浏览器窗口
- fixed:固定定位,相对于浏览器,必须定义top、right、bottom、left
- 常见的行内元素/块元素
- 块盒:div h1-h6 p ul ol li header footer section article aside form blockquote
- 行盒:span a img em strong small label kbd code
- 行快盒:input button
-
flex:1指的是什么
flex-shrink: 1; flex-basis: 0%; -
回流和重绘
-
回流 : 是指浏览器重新计算元素的位置和几何结构的过程
-
重绘:是指浏览器更新元素的外观的过程,不涉及元素的几何结构的改变
回流必将引起重绘,重绘比一定会引起回流
-
如何避免回流
-
css方面
-
避免使用table布局
-
尽可能在DOM树最末端改变class
-
避免设置多层内联样式
-
避免使用css表达式
-
对使用复杂动画的元素使用绝对定位,使他脱离文档流,否则会引起父元素及后续元素频繁回流
-
js方面
- 避免频繁操作样式
- 避免频繁操作DOM
-
-
介绍一下MVVM
model:数据的处理和业务逻辑
view:展示数据给用户,处理用户输入
viewModel:将模型中的数据转换为视图可以直接使用的形式,并监听视图的变化来更新模型的数据
通过数据绑定机制将viewModel中的数据直接绑定到视图上,实现了视图和数据的自动同步,VUE中就使用了这种模式进行数据的双向绑定
-
MVVM与MVC区别
- MVC通常用于Web应用程序、桌面应用程序等,相对简单,适用于小型应用,控制器可能变得臃肿
- MVVM通常用于前端开发、移动应用程序等,简化了视图和模型之间的交互,可能会导致性能问题
MVC中的controller处理用户输入并更新model和view,而在MVVM中,VM不仅处理输入,还直接将数据绑定给view,view和model就不用再交互
- viewModel有什么好处
- 分离关注点:减少数据、模型、视图之间的耦合,使代码更清晰、模块化、易于维护和扩展
- 增加代码可测试性
- 增强数据绑定,提高开发效率
-
nextTick是怎么实现的
实现原理是基于浏览器的异步任务队列,采用微任务优先的方式,Vue.nextTick() 可以将回调函数推入到异步队列中,在 DOM 更新完成后执行。这样就可以确保我们操作的是更新后的 DOM,而不是更新前的 DOM。比如在某些情况下需要获取某个元素的尺寸、位置等属性时,如果不使用 Vue.nextTick(),可能会获取到错误的结果。
-
状态管理VUEX
在vue框架中,状态管理通常指的是如何有效地在组件之间共享和管理状态。因为单个组件中修改状态很方便,但在实际开发中,往往是多个组件公用一个状态,或者是多层组件嵌套,这时候传参会变得非常繁琐,就需要进行状态管理,方便维护,减少耦合
VUEX是专门在 Vue中实现集中式状态(数据)管理的一个Vue插件,对 vue应用中多个组件的共亨进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
如果把vuex比作餐厅
state:仓库存储数据的地方,相当于是菜
mutations:修改state的唯一手段,相当于是后厨
action:处理action,可以书写自己的业务逻辑也可以处理异步,相当于服务员
getters:理解为计算属性,当state中的数据需要经过加工后再使用时,可以使用getters加工,用于简化仓库数据,让组件获取仓库的数据更加方便
vue就相当于客人
- 组件通信
- 父子组件通信:props; children; provide / inject ; ref ; listeners
- 兄弟组件通信:eventBus ; vuex
- 爷孙组件通信:eventBus;Vuex;provide / inject 、listeners
- 状态码
- 1xx代表请求已经接受
- 2xx代表请求成功
- 3xx代表重定向
- 4xx代表客户端错误
- 5xx代表服务器错误
- 303:由于请求对应的资源存在着另一个 URI,应使用 GET方法定向获取请求的资源
- 304:从上次请求后,网页从未修改过,资源没变化(主要用于缓存机制中)
- 400:请求语法错误
- 401:需要身份认证
- 403:禁止访问
- 404:资源未找到
- 405:请求方法不被允许
- 500:服务器内部错误
- 502:错误网关无效响应
- 503:服务器暂时不可用
- 504:网关超时没及时收到请求
- 505:http版本不支持
- 防抖和节流
| 性能优化 | 说明 | 使用场景 |
|---|---|---|
| 防抖 | 单位时间内,频繁触发事件,只执行最后一次 | 搜索框搜索输入、手机号、邮箱验证输入检测 |
| 节流 | 单位时间内,频繁触发事件,只执行一次 | 高频事件:鼠标移动mousemove、页面尺寸缩放resize、滚动条滚动scroll等等 |
- 防抖函数
function debounce(fn , t){
let timer
return function (){
if(timer) clearTimeout(timer)
timer = setTimeout(function(){
fn()
},t)
}
}
// 模拟事件触发
window.addEventListener('resize', debounce(log, 1000));
57. 节流函数
function throttle(fn,t){
let timer = null
return function (){
if(!timer){//是否有等待执行的任务
timer = setTimeout(function (){
fn()
timer = null //清除定时器,以便下一次触发
},t)
}
}
}
}
box.addEventListener('mousemove',throttle(mouseMove,3000)
58. 会改变原数组的方法
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
- 常用不改变原数组的方法
- slice
- concat
- join
- toString
- indexOf
- forEach
- map
- includes
- reduce
- filter