由于疫情原因,原本每年的“金三银四”仿佛消失,随之而来的是找工作的压力,这里给要面试的小伙伴们总结了到目前为止我遇到的前端面试题,仅供参考哦,第一次写博客,如有错误之处,还请指出。
(1)通过在router.js文件中配置path的地方动态传递参数 eg: path: '/detail/:id' 然后在组件内通过this.$route.params.id即可获取
(2).在router-link标签中传递参数
也通过this.$route.params获取
注意:这里通过router-link传递参数的方式是隐式传参
由于v-for的优先级比v-if高,所以导致每循环一次就会去v-if一次,而v-if是通过创建和销毁dom元素来控制元素的显示与隐藏,所以就会不停的去创建和销毁元素,造成页面卡顿,性能下降。
解决办法:在v-for的外层或内层包裹一个元素来使用v-if
beforedestoryed是组件销毁之前执行的一个生命周期,在这个生命周期里,我们可以进行回调函数或定时器的清除,不用的dom元素的清除等
1.如果是兄弟组件,可通过父元素作为中间组件进行传值 2.通过创建一个bus,进行传值
// 创建一个文件,定义bus中间件,并导出
const bus = new Vue()
// 在一个组件中发送事件
bus.$emit('事件名称', 传递的参数)
// 在另一个组件中监听事件
bus.$on('事件名称', 得到传过来的参数)
vue中通过在子组件上定义ref属性来获取子组件的属性和方法,代码如下:
// 这里是父组件
1.渲染过程顺序:
父组件beforeCreate() -> 父组件created() -> 父组件beforeMount() -> 子组件beforeCreate() ->子组件created() -> 子组件beforeMount() -> 子组件mounted() -> 父组件mounted()
2.更新过程顺序:
父组件更新过程:
父组件beforeUpdate() -> 父组件updated()
子组件更新过程:
父组件beforeUpdate() -> 子组件beforeUpdate() -> 子组件updated() -> 父组件updated()
3.销毁过程
父组件beforeDestroy() -> 子组件beforeDestroy() -> 子组件destroyed() -> 父组件destroyed()
父组件能够监听到子组件的生命周期,通过@hook:进行监听代码如下:
// 这里是父组件
keep-alive是用于做组件缓存的,只会执行一次,不会被销毁。被keep-alive包裹的组件,没有create和beforeDestroyed等方法,但是有activated和deactivated方法。
对于有返回值的数据,watch就能监听到,比如数组的pop,push, unshift,map等行为。
watch: {
obj: {
handler: function(val) {
},
deep: true // 深度监听
}
}
(1).修改对象属性后页面未重新渲染可以使用 this.$set(对象名称, '属性名', '属性值')
(2).使用this.$forceUpdate()方法可重新渲染页面
1;每个构造函数都有一个原型对象
2;每个原型对象都有一个指向构造函数的指针
3;每个实例函数都有一个指向原型对象的指针。
4;查找方式是一层一层查找,直至顶层。Object.prototype
定义:程序不需要的内存,由于某些原因其不会返回到操作系统或者可用内存池中。内存泄露会导致(运行缓慢 ,高延迟,崩溃)的问题,常见的导致内存泄露的原因有:
由于浏览器的同源策略会导致跨域,同源策略又分为
解决方式:
1、state
保存vuex中的数据源,通过this.$store.state获取
2、getters
用于监听state中的值的变化,返回计算后的结果。getter的返回值会根据它的依赖被缓存起来
3、mutations
是修改store中的值得唯一方式
4、action
官方建议提交一个actions,在actions中提交mutations再去修改状态值。 this.$store.dispatch('add')
//this.$store.commit('add')
5、modules 模块化
Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。通俗说就是 Vue 实例从创建到销毁的过程,就是生命周期。
vue生命周期的作用:
他的生命周期中有多个事件钩子,让我们控制整个vue实例的过程时更容易形成良好的逻辑。
生命周期钩子的一些使用方法:
computed
计算结果并返回,只有当被计算的属性发生改变时才会触发(即:计算属性的结果会被缓存,除非依赖的响应属性变化才会重新及孙)
watch
监听某一个值,当被监听的值发生变化时,执行相关操作。(与computed的区别是,watch更加适用于监听某一个值得变化,并做对应操作,比如请求后太接口等。而computed适用于计算已有的值并返回结果。)
监听简单数据类型:
data(){
return{
'first':2
}
},
watch:{
first(){
console.log(this.first)
}
},
...
}
1、数据体积方面:xml是重量级的,json是轻量级的,传递的速度更快些。。
2、数据传输方面:xml在传输过程中比较占带宽,json占带宽少,易于压缩。
3、数据交互方面:json与javascript的交互更加方便,更容易解析处理,更好的进行数据交互
4、数据描述方面:json对数据的描述性比xml较差
5、xml和json都用在项目交互下,xml多用于做配置文件,json用于数据交互。
Http:超文本传输协议(Http,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。设计Http最初的目的是为了提供一种发布和接收HTML页面的方法。它可以使浏览器更加高效。Http协议是以明文方式发送信息的,如果黑客截取了Web浏览器和服务器之间的传输报文,就可以直接获得其中的信息。
Https:是以安全为目标的Http通道,是Http的安全版。Https的安全基础是SSL。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:SSL记录协议(SSL Record Protocol),它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。SSL握手协议(SSL Handshake Protocol),它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
HTTP与HTTPS的区别
https的优点
尽管HTTPS并非绝对安全,掌握根证书的机构、掌握加密算法的组织同样可以进行中间人形式的攻击,但HTTPS仍是现行架构下最安全的解决方案,主要有以下几个好处:
Https的缺点
1. this.$router.push
//跳转到不同的url,但这个方法会向history栈添加一个记录,点击后退会返回到上一个页面。
this.$router.push({path: '/home/sort/detail',query:{id: 'abc'}})
//获取参数 {{this.$route.query.userId}}
//this.$router.push({name: 'detail',params:{id: 'abc'}})
获取参数:{{this.$route.params.userId}}
2. query和params 的区别
a) 用法上,query要用path来引入,params要用name来引入:eg
this.$router.push({
name:"detail",
params:{
name:'nameValue',
code:10011
}
});
b) 展示上,query更加类似于我们ajax中get传参,params则类似于post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示
3. this.$router.replace
同样是跳转到指定的url,但是这个方法不会向history里面添加新的记录,点击返回,会跳转到上上一个页面。上一个记录是不存在的。
4. this.$router.go
相对于当前页面向前或向后跳转多少个页面,类似 window.history.go(n)。n可为正数可为负数。正数返回上一个页面
5. router-link标签使用
闭包是指有权访问其他函数所定义的变量的函数。
使用闭包的话只需要将一个函数定义在另一个函数的内部,并将它给暴露出来,比如可以直接返回或者给他传递给其他函数,由于内部函数对外部函数变量还存在着应用,所以在外部函数执行完毕后不会主动将该内存销毁。
闭包最重要的用途是实现对象的私有数据。 对象的内部数据无法被轻易的修改,保证了程序的安全性。
但同时大量的使用闭包会导致内存泄漏。
JS高级程序上的定义:利用事件冒泡,只绑定一个函数就可以处理一类事件。
在JS中,添加到页面上的事件数量直接关乎着页面的性能,多次与dom发生交互访问dom会导致页面重绘和重排,导致拖慢整个页面,这就是为什么优化里中的减少dom操作的原因。而且,每个函数都是一个对象,对象就要占据内存,内存大了性能自然就慢了。
事件委托的实现原理就是利用事件冒泡。
冒泡的顺序 :
事件代理的好处:
在JS中,每创建出来一个函数js就会默认创建一个prototype的属性,这个属性指向函数的原型对象。
在原型对象里面有一个属性constructor属性指向了构造函数。
每个对象都有一个隐式原型 __proto__ ,指向的是构造该对象的构造函数的原型对象。
原型对象也是对象,所以如果我们让原型对象指向另外的一个实例,这个实例也有自己的原型对象,如果这个实例的原型对象又等于了另外的实例,一层层下去就形成了一个链条。这就是所谓的原型链。
函数也是对象,所以函数也有隐式原型,函数的隐式原型为Function.prototype
Function.prototype的隐式原型为Object.prototype
Object.prototype 的隐式原型为null 这里也就是原型链的顶端
父子组件通信:
props
和 event
、v-model
、 .sync
、 ref
、 $parent
和 $children
非父子组件通信:
$attr
和 $listeners
、 provide
和 inject
、eventbus
、通过根实例$root
访问、vuex
、dispatch
和 brodcast
通信方式属于较基础的面试题,具体的可参考我的文章—— vue 组件通信看这篇就够了
Vue提倡单向数据流,即父级 props
的更新会流向子组件,但是反过来则不行。这是为了防止意外的改变父组件状态,使得应用的数据流变得难以理解。如果破坏了单向数据流,当应用复杂时,debug 的成本会非常高。
v-model
是用来在表单控件或者组件上创建双向绑定的,他的本质是 v-bind
和 v-on
的语法糖,在一个组件上使用 v-model
,默认会为组件绑定名为 value
的 prop
和名为 input
的事件。
文章—— vue 组件通信看这篇就够了 中也有其详细介绍
Vuex和全局对象主要有两大区别:
Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。
每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。 参考尤大大回答: www.zhihu.com/question/48…
beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed。
有自己独立的钩子函数 activated 和 deactivated。
渲染过程:
父组件挂载完成一定是等子组件都挂载完成后,才算是父组件挂载完,所以父组件的mounted在子组件mouted之后
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
子组件更新过程:
父组件更新过程:
销毁过程:
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
看起来很多好像很难记忆,其实只要理解了,不管是哪种情况,都一定是父组件等待子组件完成后,才会执行自己对应完成的钩子,就可以很容易记住。
v-if
会在切换过程中对条件块的事件监听器和子组件进行销毁和重建,如果初始条件是false,则什么都不做,直到条件第一次为true时才开始渲染模块。v-show
只是基于css进行切换,不管初始条件是什么,都会渲染。
所以,v-if
切换的开销更大,而 v-show
初始化渲染开销更大,在需要频繁切换,或者切换的部分dom很复杂时,使用 v-show
更合适。渲染后很少切换的则使用 v-if
更合适。
computed
计算属性,是依赖其他属性的计算值,并且有缓存,只有当依赖的值变化时才会更新。watch
是在监听的属性发生变化时,在回调中执行一些逻辑。
所以,computed
适合在模板渲染中,某个值是依赖了其他的响应式对象甚至是计算属性计算而来,而 watch
适合监听某个值的变化去完成一段复杂的业务逻辑。
计算属性是基于他们的响应式依赖进行缓存的,只有在依赖发生变化时,才会计算求值,而使用 methods
,每次都会执行相应的方法。
keep-alive
可以在组件切换时,保存其包裹的组件的状态,使其不被销毁,防止多次渲染。
其拥有两个独立的生命周期钩子函数 actived 和 deactived,使用 keep-alive
包裹的组件在切换时不会被销毁,而是缓存到内存中并执行 deactived 钩子函数,命中缓存渲染后会执行 actived 钩子函数。
在网站上动态渲染任意 HTML,很容易导致 XSS 攻击。所以只能在可信内容上使用 v-html,且永远不能用于用户提交的内容上。
这部分的面试题,只看答案部分是不够的,最好结合源码来分析,可以有更深的理解。我在之前的文章对某些源码做过分析的,会给出链接。
如果面试被问到这个问题,又描述不清楚,可以直接画出 Vue 官方文档的这个图,对着图来解释效果会更好。
Vue 的响应式是通过 Object.defineProperty
对数据进行劫持,并结合观察者模式实现。 Vue 利用 Object.defineProperty
创建一个 observe
来劫持监听所有的属性,把这些属性全部转为 getter
和 setter
。Vue 中每个组件实例都会对应一个 watcher
实例,它会在组件渲染的过程中把使用过的数据属性通过 getter
收集为依赖。之后当依赖项的 setter
触发时,会通知 watcher
,从而使它关联的组件重新渲染。
这道题目也可以问成 “为什么vue3.0使用proxy实现响应式?” 其实都是对Object.defineProperty 和 proxy实现响应式的对比。
Object.defineProperty
只能劫持对象的属性,而 Proxy
是直接代理对象Object.defineProperty
只能对属性进行劫持,需要遍历对象的每个属性。而 Proxy
可以直接代理对象。Object.defineProperty
对新增属性需要手动进行 Observe
, 由于 Object.defineProperty
劫持的是对象的属性,所以新增属性时,需要重新遍历对象,对其新增属性再使用 Object.defineProperty
进行劫持。 也正是因为这个原因,使用 Vue 给 data
中的数组或对象新增属性时,需要使用 vm.$set
才能保证新增的属性也是响应式的。Proxy
支持13种拦截操作,这是 defineProperty
所不具有的。Proxy
作为新标准,长远来看,JS引擎会继续优化 Proxy
,但 getter
和 setter
基本不会再有针对性优化。Proxy
兼容性差 目前并没有一个完整支持 Proxy
所有拦截方法的Polyfill方案更详细的对比,可以查看我的文章 为什么Vue3.0不再使用defineProperty实现数据监听?
Vue 的 Observer
对数组做了单独的处理,对数组的方法进行编译,并赋值给数组属性的 __proto__
属性上,因为原型链的机制,找到对应的方法就不会继续往上找了。编译方法中会对一些会增加索引的方法(push
,unshift
,splice
)进行手动 observe。 具体同样可以参考我的这篇文章 为什么Vue3.0不再使用defineProperty实现数据监听?,里面有详细的源码分析。
Vue 的组件都是可复用的,一个组件创建好后,可以在多个地方复用,而不管复用多少次,组件内的 data
都应该是相互隔离,互不影响的,所以组件每复用一次,data
就应该复用一次,每一处复用组件的 data
改变应该对其他复用组件的数据不影响。
为了实现这样的效果,data
就不能是单纯的对象,而是以一个函数返回值的形式,所以每个组件实例可以维护独立的数据拷贝,不会相互影响。
能回答清楚这道问题的前提,是清楚 EventLoop 过程。
在下次 DOM 更新循环结束后执行延迟回调,在修改数据之后立即使用 nextTick
来获取更新后的 DOM。nextTick
对于 micro task 的实现,会先检测是否支持 Promise
,不支持的话,直接指向 macro task,而 macro task 的实现,优先检测是否支持 setImmediate
(高版本IE和Etage支持),不支持的再去检测是否支持 MessageChannel,如果仍不支持,最终降级为 setTimeout
0;
默认的情况,会先以 micro task
方式执行,因为 micro task 可以在一次 tick 中全部执行完毕,在一些有重绘和动画的场景有更好的性能。
但是由于 micro task 优先级较高,在某些情况下,可能会在事件冒泡过程中触发,导致一些问题(可以参考 Vue 这个 issue:github.com/vuejs/vue/i…),所以有些地方会强制使用 macro task (如 v-on
)。
注意:之所以将 nextTick
的回调函数放入到数组中一次性执行,而不是直接在 nextTick
中执行回调函数,是为了保证在同一个tick内多次执行了 nextTcik
,不会开启多个异步任务,而是把这些异步任务都压成一个同步任务,在下一个tick内执行完毕。
vue模板的编译过程分为3个阶段:
此阶段会深度遍历生成的 AST 树,检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用。
const code = generate(ast, options)
复制代码
通过 generate
方法,将ast生成 render
函数。 更多关于 AST,Vue 模板编译原理,以及和 AST 相关的 Babel 工作原理等,我在 掌握了AST,再也不怕被问babel,vue编译,Prettier等原理 中做了详细介绍。
清晰回答这道问题,需要先清楚 Vue 的 diff 过程,关于 diff 原理,推荐一篇文章 my.oschina.net/u/3060934/b…
key
是给每个 vnode
指定的唯一 id
,在同级的 vnode
diff 过程中,可以根据 key
快速的对比,来判断是否为相同节点,并且利用 key
的唯一性可以生成 map
来更快的获取相应的节点。
另外指定 key
后,就不再采用“就地复用”策略了,可以保证渲染的准确性。
当 v-for
和 v-if
处于同一个节点时,v-for
的优先级比 v-if
更高,这意味着 v-if
将分别重复运行于每个 v-for
循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费。
这种场景建议使用 computed
,先对数据进行过滤。
全局前置/钩子:beforeEach、beforeResolve、afterEach
路由独享的守卫:beforeEnter
组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
完整的导航解析流程如下:
区别:
hash 模式:#
后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面。同时通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。
history 模式:
history 模式的实现,主要是 HTML5 标准发布的两个 API,pushState
和 replaceState
,这两个 API 可以在改变 url,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。
关于 vue-router
部分,推荐文章 juejin.im/post/5b10b4…
本文是系列文章《2020年大厂面试指南》的第一篇文章,后续系列文章更新,请关注我的公众号【前端小苑】。有任何问题也欢迎加我个人微信yu_shihu_进行交流
原文链接:https://blog.csdn.net/weixin_42981560/article/details/104646699
原文链接:https://blog.csdn.net/weixin_45325238/article/details/104968195