2022最新前端面试题(vue方向)
前言:又到了跳槽旺季,经过几天的收集,整理出了2022年后最新的面试题及答案,坐标武汉,期望薪资15k+的。持续更新,也欢迎各位大佬的评论区补充
1.vue的运行机制
1.初始化
调用vue原型上的_init()初始化,会初始化vue的生命周期,data,methods,props,watch,computed,利用object.definepropty对data里面的属性设置getter和setter函数,来实现响应式和依赖收集。
2.挂载组件
调用$mount挂载组件
3.编译
parse(解析):利用正则将模板转换成抽象语法树;
optimize:标记静态节点,以后update的时候,diff算法会跳过静态节点
generate:将抽象语法树转换成字符串,供render去渲染dom
经过以上步骤可以得到render function
4.响应式
利用object.definepropty设置data所返回的对象后,在进行render function渲染的时候,会对data对象进行数据读取,触发getter函数,从而把data里面的属性进行依赖收集,依赖收集的目的就是将这些属性放到观察者watcher的观察队列中,一旦我们对data里面的数据进行修改时,就会触发setter函数,setter告诉观察者数据变化,需要重新渲染视图,观察者调用update更新视图
5.虚拟dom
render function会被转换成虚拟dom,虚拟dom实际上就是一个js对象,从顶层dom层层描述dom。
6.更新视图
当数据发生变化时,会经历 setter–watcher–update,在update的时候会执行patch,将旧的vnode传进去,通过diff算法算出差异,局部更新视图
2.vue是如何实现数据的双向绑定的,v-model的原理
vue的数据双向绑定主要是指数据变化更新视图,视图变化更新数据。
1.对数据对象进行遍历,利用object.defineproperty()对属性都加上getter和setter,这样的话给某个对象赋值都会触发setter,就能检测到数据的变化。
2.结合订阅者发布者模式,订阅者监视数据,一旦数据发生变动,收到通知,调用更新函数进行数据更新。
v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件,例如
text,textarea使用value属性和input事件
check和radio使用checked属性和change事件
select将value作为prop并将change作为事件
3.怎样理解 Vue 的单向数据流?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收到后,由父组件修改。
4.vue中的data为什么必须是一个函数?为什么需要return返回?
vue中data必须是函数是为了保证组件的独立性和可复用性,data是一个函数,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,你实例化几次,就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。
不使用return包裹的数据会在项目的全局可见,会造成变量污染;使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件
5.computed 和 watch 的区别和运用的场景?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
- 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
- 当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
6.由于js的限制,直接给一个数组项赋值,Vue 能不能检测到变化?
不能,用索引直接设置一个数组项时 或者 当你修改数组的长度时,Vue 不能检测到数组的变动。
为了解决他们,Vue 也提供了操作方法:
Vue.set
vm.$set(Vue.set的一个别名)
Array.prototype.splice
vm.items.splice(修改数组的长度)
7.vue组件传值(父子,隔代,兄弟)
1.父–子传值
子组件通过props接收
2.子传父
1).子组件绑定一个事件,通过$emit()来触发
2).父组件通过callback函数,并把callback函数传给子组件,子组件通过props接收
3).通过 p a r e n t 和 parent和 parent和children或者$refs访问组件的实例
4). 父组件通过provide提供变量,子孙组件通过inject注入变量。
5).vuex
8.vuex的理解
vuex是vue的状态管理模式,每个vuex的核心就是store(仓库),包含着应用中大部分的状态(state)。
vuex的状态存储是响应式的,当组件从store里获取状态时,当state里面的状态发生改变,相应的组件也会更新。
改变store里的状态唯一的途径就是显式的提交mutation。
主要包括几大模块:
state:单一数据源,定义应用状态的数据结构,设置初始状态。
getters:允许组件从store中获取数据,是store的计算属性。
mutations:是store的methods,保存着更改数据的回调函数,是同步操作,第一个参数state,第二个是自定义参数。
actions:是异步的操作方法,第一个参数是context,是一个与store具有相同的属性和对象,使用commit异步提交mutations里的方法。
modules:模块化,将store分割成模块,是每个模块都有自己的state,getters,mutations,actions
9.Vue 的父组件和子组件生命周期钩子函数执行顺序?
页面加载时:父beforeCreate–父Created-父beforeMount-子beforeCreate-子Create-子beforeMount-子Mounted-父Mounted
子组件更新:父beforeUpdate-子beforeUpdate-子Updated-父Updated
父组件更新:父beforeUpdate-父Updated
组件销毁:父beforedestroy-子beforedestroy-子destroyed-父destroyed
10.在哪个生命周期内调用异步请求?
可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值,推荐created
11.在什么阶段才能访问操作DOM?
mounted
12.Vue 框架怎么实现对象和数组的监听?
通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i]) // observe 功能为监测数据的变化
}
}
13.Proxy 与 Object.defineProperty 优劣对比。vue2和3的区别
Proxy 可以直接监听对象而非属性;
Proxy 可以直接监听数组的变化;
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;
Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
14.vue路由
常用的有hash和history模式
hash:是用的location.hash()获取#后面的为路由地址,在地址栏会带有难看的#。js对location.hash进行赋值,改变url的hash值
history:依赖html5的historyApi和服务器配置,pushState新增历史记录和RepalceState替换当前的历史记录
abstract:支持所有的js运行环境,如果发现没有浏览器api,会自动强制进入这个模式
动态路由:在path后面跟上对应的值,
导航钩子:
全局守卫:
全局前置钩子:router.beforeEach(to,from,next)
全局后置钩子:router.afterEach(to,from),不接受next,也不会改变导航本身
路由独享守卫:beforeEnter(to,from,next)
组件内的守卫:beforeRouteEnter
离开守卫:beforeRouteLeave(to,from,next),路由离开时调用,防止用户未提交页面突然离开
router:vueRouter的对象,是一个全局的实例对象,包含了所有的路由的对象和属性;例如替换路由: r o u t e r . p u s h ( ) , 切 换 路 由 : router.push(),切换路由: router.push(),切换路由:router.replace()
route:跳转的路由对象,每个路由都会有一个route对象,用来获取路由的path,name,params,query
meta:路由元信息,可以在里面配置title和roles(路由的权限访问)
keep-alive:路由缓存,当组件切换时,会保存所有的组件状态,而不是切换页面后又要重新操作,默认情况下会缓存打开的所有组件,如果需要指定缓存哪些组件,需要用include["…","…"]指定。特有的生命周期:activated(组件**状态),deactivated(组件失活状态)
当路由组件采用缓存后,created和mounted这两个生命周期函数,只会在第一次执行;并且destroyed这个生命周期函数不会执行。
这时候,通常都会配合activated(路由组件**状态生命周期函数)和deactivated(路由组件失活状态生命周期函数)这两个生命周期钩子。
**注意:**只有当组件在 内被切换,才会有activated 和 deactivated 这两个钩子函数。
15.keep-alive的理解
keep-alive是vue的内置的一个组件,可以使被包含的组件保留状态,避免重新渲染
一般结合路由和动态组件一起使用,用于缓存组件
提供include和exclude属性,都支持字符串和正则表达式,include表示只有名称相匹配的组件会被缓存,exclude表示任何组件都不会被缓存,exclude优先级高于include
16.v-if和v-show
v-if:当初始条件为真的时候才会渲染,否则一直不会渲染
v-show:不管初始条件是什么,元素总会被渲染,基于css的display:none;进行切换
v-if优先级高于v-show
v-if适用于不需要频繁切换条件的场景
v-show适用于需要频繁切换条件的场景
17.webpack原理,构建流程,如何按需加载
根据文件之间的依赖关系对其进行静态分析,将这些模块按照指定规则生成静态资源,webpack处理程序时,会递归的构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或者多个包(bundle)
主要功能是将多个文件打包成一个文件,减少服务器压力,将预编译语言转换成浏览器识别的语言,性能优化。
特点:
代码拆分:
webpack有同步和异步两种组织模块的依赖方式
只能解析:
有一个智能解析库,几乎可以解析任何一个第三方库
快速运行:
使用异步I/O,多级缓存提高运行效率,使得它有非常快的速度快速增量编译
核心概念:
entry:一个可执行模块和库的入口文件
chunk:多个文件组成的一个代码块,例如一个可执行模块和他所有依赖的模块组成了一个chunk,体现了webpack的打包机制。
loader:文件转换器,例如es6转化成es5,scss转换成css
plugin:插件,扩展webpack的功能,在webpack构建生命周期的节点上加入扩展hook为webpack加入功能
构建流程:
1.解析webpack配置参数,合并从shell传入的和webpack.config.js文件里配置的参数,生产最后的配置结果。
2.注册所有配置的插件,让插件监听webpack构建生命周期的事件节点,并作出对应的反应
3.从配置的entry入口文件开始解析文件构建AST语法树,找出每个文件所依赖的文件,递归下去
4.在解析文件递归的过程中,根据文件类型和loader配置,找出合适的loader对文件进行转换
5.递归完后,得到每个文件最终的结果,根据entry配置生成代码块chunk。
6.输出所有chunk到文件系统
按需加载:
使用code splitting实现按需加载
使用场景:
在最开始使用webpack时,都是将所有的js文件全部打包到build.js里,但是在大型项目中,build.js可能过大,导致页面加载时间过长,这个时候就需要code splitting将文件分割成块(chunk),我们可以定义一些分割点,然后根据这些分割点分割成块,并进行按需加载
18.position 属性
- static(元素默认的静态定位)
- relative(相对定位,相对于正常未知进行定位)
- fixed(固定定位,相对于窗口进行定位)
- absolute(绝对定位,相对于最近的定位祖先元素进行定位)
- sticky(粘性定位,根据用户滚动位置进行定位)
19.flex弹性布局
容器属性
- flex-direction
- flex-wrap
- flex-flow
- justify-content
- align-items
- align-content
项目属性
- order
- flex-grow
- flex-shrink
- flex-basis
- flex
- align-self
20.css预处理器
sass:允许定义变量,允许css代码嵌套,函数功能,继承等
定义变量:再有使用相同属性时,可以使用定义变量($)将属性存储到变量中,当需要统一修改相同属性时,直接修改变量即可。
代码嵌套:box>box1{}------> box{box1{}}
函数:复用的代码可以写成一个函数,在有需要的时候调用这个表达式即可,但是这个表达式不产生一个值
$mixin box{}
继承:如果一个样式与另一个样式几乎相同,只有少量的区别,就可以用继承,当使用继承时,可以使用@extend,用法同$mixin,在extend后面就是需要继承的代码
21.css性能问题如何优化
1.减少css嵌套
2.建立公共样式
3.巧用css的继承
4.雪碧图
22,promise
为了代码更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来
三种状态:
pending: 等待中,或者进行中,表示还没有得到结果
resolved(Fulfilled): 已经完成,表示得到了我们想要的结果,可以继续往下执行
rejected: 也表示得到结果,但是由于结果并非我们所愿,因此拒绝执行
new Promise(function(resolve, reject) {
if(true) { resolve() };
if(false) { reject() };
}).then(function(){
}).catch(function(){
})
参数:resolve,reject,都是函数,将状态修改为resolved,rejected
Promise.all([promisea,promiseb,…]).then().catch()
当所有都是执行完成,并且结果都是成功的时候才会执行回调
Promise.race([promisea,promiseb,…]).then().catch()
谁先执行完谁先进入回调,无论结果是成功还是失败,后面的都不再执行
reject:是抛出异常,是promise的方法,当pending为reject的时候,会进入catch
catch:是处理异常,是promis实例的方法
23.js的事件循环机制,单线程,和宏任务微任务
宏任务:每次执行栈执行的代码就是一个宏任务,包括每次从消息队列里获取的一个事件回调放到执行栈执行的,包括script,setTimeout,setInterval等
微任务:在当前宏任务执行结束后立即执行的,在当前宏任务执行结束后,下一个宏任务执行之前,渲染之前的,包括promise.then(),object.observe等
事件循环执行机制: 执行一个宏任务(栈没有就从消息队列中获取)---->执行过程中遇到微任务,就将它添加到微任务的任务队列中---->宏任务执行完毕后,立即依次执行所有的微任务队列中的微任务---->当前宏任务执行完毕开始检查渲染,然后浏览器进行渲染----->渲染完毕后开始下一个宏任务
24.对es6的了解
闭包:在一个函数内部创建另一个函数,函数嵌套函数,函数内部可以引用函数外部的变量和参数,参数和变量不会被垃圾回收机制回收
let,const:let声明变量,const声明常量,两个都有块级作用域,var是功能范围的
解构赋值:允许按照一定模式,从对象和数组中提取值,对变量进行赋值
箭头函数:this不是指向window而是父级,不能用作构造函数,也就是不能使用new,不能够使用arguments对象
模板字符串:(``)加强版的字符串,使用${}可以将表达式与字符串进行拼接,也可以当普通字符串使用
forEach:一般用来遍历数组
for in:一般用来遍历对象或者json,遍历出来的是key
for of:数组对象都可以遍历,遍历出来的是value
import:导入
export:导出
set:用于数据重组,数据不能重复,只有键值,没有键名,可以遍历,方法有add,delete
map:用于数据存储,是键值对的集合,可以遍历,与各种数据格式转换
25.js的数据类型
基本类型(值类型):undefined,number,string,boolear
基本数据类型是按值访问的,我们可以操作保存在变量中的实际的值,基本类型的值是不可改变的,任何方法都不会改变一个基本数据类型的值。改变只是指针和指向的改变,基本数据类型是不能添加属性和方法的。
基本数据类型的值占用固定大小的内存空间,保存在栈中
引用类型:object,array,function,data
引用数据类型实际上是保存在堆内存中,栈内存中保存的是对象在堆内存中的引用地址,可以通过引用地址,可以快速查找到保存在堆内存中的对象
基本数据类型和引用数据类型的区别
声明变量时,不同的内存分配
基本数据类型存储在栈里,就是变量访问的位置。
引用数据类型存储在堆中,存储在变量处的值是一个指针。指向存储对象在堆内存中的地址
不同的访问机制
基本数据类型可以直接访问的到
访问引用数据类型时,先得到的是这个对象在堆内存中的地址,然后按照这个地址去获取这个对象的值,也就是按引用访问
复制变量时不同
基本数据类型是将原始值的副本赋值给新变量,此后两个变量是完全独立的,只是拥有两个相同的value
引用数据类型会将这个对象的内存地址赋值给新变量,两个内存地址指向的是堆内存同一个对象,他们之中任何一个作出改变都会反映到另一个身上
参数传递的不同
基本数据类型是把变量的值传给参数,之后这个参数和变量互不影响
引用数据类型是变量把他里面的值传递给了参数,这个参数也指向原对象,因此在函数内部给这个参数赋值另外一个对象时,这个参数就会更改他的值为新对象的内存地址指向新的对象,但原来的变量还是指向原来的对象
26.深拷贝,浅拷贝
浅拷贝:创建新的数据,这个数据有着原始数据属性值的一份精确拷贝。如果属性是基本类型,拷贝的是基本类型的值,如果属性是引用类型,拷贝的是内存地址。常见的浅拷贝:object.assign,slice(),concat()
深拷贝:开辟一个新的栈,两个对象属性完全相同,但是对应的是不同的地址,修改一个对象的属性,不会改变另一个对象的属性。常见的深拷贝:.cloneDeep(),jquery.extend(),JSON.stringify()
区别:复制对象属性的时候,行为不一样,浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共用一块内存,修改对象属性会影响原对象,深拷贝是创造一个一模一样的对象,新对象和原对象不共享内存,修改新对象不会改到原对象
27.前端怎么解决跨域?
jsonP,cors跨域资源共享,nginx反向代理接口跨域,postMessage(data,origin)跨域
28.什么是幽灵节点?怎么解决?
图片下方出现的未知空白,将图片设置为 display:block;
29.说一下对async/await的理解
async await 是用来解决异步的,async函数是Generator函数的语法糖
使用关键字async来表示,在函数内部使用 await 来表示异步
async函数返回一个 Promise 对象,可以使用then方法添加回调函数
当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句
30.setTimeout、Promise、Async/Await 的区别
事件循环中分为宏任务队列和微任务队列
setTimeout的回调函数会被放到宏任务队列里,等到执行队列清空之后执行
promise.then()的回调函数会被放到对应的宏任务的微任务队列里,等宏任务里面的同步代码执行完之后执行
Async函数表示函数里可能会存在异步方法,Await后面跟一个表达式,Async执行时,遇到Await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让执行队列的同步代码先执行
31.原型,原型链,继承
原型:每一个函数都有一个prototype对象属性,指向另一个对象,prototype所有的属性和方法都会被构造函数的实例继承,我们就可以把公用的属性和方法定义在prototype上,这个prototype就是调用构造函数所创建的那个实例的原型。
原型链:实例对象与原型之间的连接。每个对象都有一个proto属性,原型链上的对象正式依靠这个连接在一起。
原型链继承:
将构造函数的原型设置为另一个构造函数的实例对象,这样就可以继承另一个原型对象的属性和方法。
32.等于和恒等于的区别
等于(双等号):先检查两个操作数数据类型,如果相等,再进行===比较,如果不相同,则会进行一次类型转换,转成相同类型再比较。
恒等于(===):数据类型不同,直接false
如果两个数一个是null一个是undefined,相同
33.this指向
1.如果一个函数中有this,但他没有被调用,this指向的是window,严格模式中,this默认指向的是undefined
2.如果一个函数中有this,这个函数有被上一级对象所调用,那么this指向的就是上一级对象
3.如果一个函数中有this,这个函数包含多个对象,尽管这个函数被最外层函数调用,那他也指向的是它的上一级对象
构造函数中的this指向:
当用变量创建了一个函数实例的时候,谁调用了这个函数实例,this指向的就是谁
34.call、apply、bind三者的用法和区别
call ,apply ,bind三者都是改变this指向的方法
call:当前实例通过原型链的查找机制,找到原型的call方法
当call方法执行的时候,首先要把操作的函数中的this关键字变成了call的第一个传递的参数,把call方法第二个及以后的实参获取到,然后把要操作的函数执行,把call方法的第二个及以后的实参传递给函数。
非严格模式下,call会吧第一个参数包装成一个对象,再指向包装后的对象,如果不传参数或者第一个参数传的是null或者undefined,this指向的都是window
严格模式下,第一个参数传的是谁,this就指向谁,包括null和undefined,如果没传,this指向的就是undefined。
apply:跟call基本一致,区别是传递参数的不同
apply需要把给函数传递的参数放到一个数组中传递过去,虽然写的是一个数组,但是也是给函数一个个的传过去
bind:跟call的语法一样,区别在于立即执行还是等待执行,bind不兼容ie6-8
bind会把函数中的this预处理为obj,但此时函数没有执行,当触发事件的时候函数才会执行
35.js防抖和节流
防抖:事件触发n秒后再执行回调,如果n秒内再次被触发,则重新计算时间。(在触发某个事件后,在下一次触发之前,中间的间隔时间如果超过设置的时间才会发送请求,一直触发就不会发送请求)
使用场景:滚动条滚动触发,搜索框输入验证,表单验证,按钮提交事件等
节流:如果连续触发某个事件,则每隔一段时间执行一次
使用场景:dom元素的拖拽功能,射击类游戏,计算鼠标移动距离,监听scroll事件
36.ajax
ajax是异步js和xml,是一种用于创建快速动态网页的技术。
工作原理:
客户端发送请求,请求交给xhr,xhr把请求交给服务,服务器进行业务处理,服务器响应数据交给xhr对象,xhr对象接收数据,由js把数据渲染在页面上。
基本步骤:
1.创建一个异步调用对象(XMLHttpRequest)。
2.创建一个新的http请求,并指定这个请求的方法,url及验证信息。
3.设置响应http请求状态变化的函数,
4.发送http请求。
5.获取异步调用返回的数据。
6.使用js对dom进行局部更新
37.事件冒泡,事件捕获,事件委托
事件冒泡:目标元素事件先触发,然后父元素事件再触发。
事件捕获:父元素事件先触发,然后目标元素事件触发。
事件执行顺序是先对事件进行捕获(window---->document依次往下),然后对事件进行处理,然后是事件冒泡。
阻止事件冒泡的方式:event.stopPropagation(),event.target
事件委托:利用事件冒泡原理,把事件绑定在元素的父级上,当元素被点击时,父级上绑定的事件就会被触发。
事件委托的优点:
减少事件注册,节省内存。
简化dom更新时,相应事件的更新。
缺点:事件委托基于冒泡,对不冒泡的事件不支持,层级过多,冒泡过程中可能被阻止掉
38.js阻塞页面
所有浏览器在下载js文件的时候,会阻塞页面上的其他活动,只有当js下载,执行,解析完才会执行后面的操作。
1.内嵌脚本阻塞
直接写在html中的js代码就是内嵌js,内嵌脚本无需加载,可以直接执行,当页面有内嵌脚本时,可以直接执行,导致阻塞其他资源的加载和页面的呈现
2.外联脚本阻塞
外联脚本只有当页面加载到
为了不阻塞页面,脚本的放置位置
1.尽量合并脚本,减少
2.尽量使用外联脚本,并将脚本放置在body底部
3.使用延迟脚本和异步脚本
4.内嵌脚本放置在window.onload中执行
39.http和https
http:是互联网上应用最广泛的网络协议,是客户端和服务端请求应答的标准,用于从www服务器传输超文本到本地浏览器的传输协议。
https:是以安全为目标的http通道,就是http的安全版,在http里加入的sll层。
https协议的作用主要分两种:一种是建立信息安全通道,保障数据传输的安全,一种是确认网站的真实性
区别:
https需要申请ca证书,
http是超文本传输协议,信息都是明文传输,https则具有安全的ssl加密传输协议。
http和https是完全不同的连接方式,端口号也不同,http是80,https是443
http的连接很简单,是无状态的,https的连接是由http和ssl构建的可进行身份验证,加密传输的协议,比http安全。