原文有 36 道 vue 经常使用面试题,考虑到太多一次也看不完,因此分为 上、中、下三篇,若是你能读完这三篇文章,相信你在面试中 vue 的问题你不会怕了。html
答案:在 beforeDestroy 中销毁定时器。前端
① 为何销毁它:vue
在页面 a 中写了一个定时器,好比每隔一秒钟打印一次 1,当我点击按钮进入页面 b 的时候,会发现定时器依然在执行,这是很是消耗性能的。node
② 解决方案 1:es6
mounted(){ this.timer = setInterval(()=>{ console.log(1) },1000)},beforeDestroy(){ clearInterval(this.timer)}
方案 1 有两点很差的地方,引用尤大的话来讲就是:面试
它须要在这个组件实例中保存这个 timer,若是能够的话最好只有生命周期钩子能够访问到它。这并不算严重的问题,可是它能够被视为杂物。vue-router
咱们的创建代码独立于咱们的清理代码,这使得咱们比较难于程序化的清理咱们创建的全部东西。express
方案 2(推荐):该方法是经过$once 这个事件侦听器在定义完定时器以后的位置来清除定时器后端
mounted(){ const timer = setInterval(()=>{ console.log(1) },1000) this.$once('hook:beforeDestroy',()=>{ clearInterval(timer) })}
官网参考连接:https://cn.vuejs.org/v2/guide...api
① 先说,父组件如何主动获取子组件的数据?
方案 1:$children
$children 用来访问子组件实例,要知道一个组件的子组件多是不惟一的,因此它的返回值是数组。
如今,咱们定义 Header,HelloWorld 两个组件
<template> <div class="index"> <Header></Header> <HelloWorld :message="message"></HelloWorld> <button @click="goPro">跳转</button> </div></template>mounted(){ console.log(this.$children)}
打印的是一个数组,能够用 foreach 分别获得所须要的的数据
缺点:
没法肯定子组件的顺序,也不是响应式的。若是你确切的知道要访问子组件建议使用$refs。
方案 2 :$refs
<HelloWorld ref="hello" :message="message"></HelloWorld>
调用 helloworld 子组件的时候直接定义一个 ref,这样就能够经过 this.$refs 获取所须要的的数据。
this.$refs.hello.属性this.$refs.hello.方法
② 子组件如何主动获取父组件中的数据?
经过 :$parent
用来访问父组件实例,一般父组件都是惟一肯定的,跟children 相似
this.$parent.属性this.$parent.方法
父子组件通讯除了以上三种,还有 props 和 attrs
③inheritAttrs
这是@2.4 新增的属性和接口。inheritAttrs 属性控制子组件 html 属性上是否显示父组件的提供的属性。
若是咱们将父组件 Index 中的属性 desc、keysword、message 三个数据传递到子组件 HelloWorld 中的话,以下
父组件 Index 部分
<HelloWorld ref="hello" :desc="desc" :keysword="keysword" :message="message"></HelloWorld>
子组件:HelloWorld,props 中只接受了 message
props: { message: String},
实际状况,咱们只须要 message,那其余两个属性则会被当作普通的 html 元素插在子组件的根元素上。
如图这样作会使组件预期功能变得模糊不清,这个时候,在子组件中写入,inheritAttrs:false ,这些没用到的属性便会被去掉,true 的话,就会显示。
若是,父组件中没被须要的属性,跟子组件原本的属性冲突的时候,则依据父组件
<HelloWorld ref="hello" type="text" :message="message"></HelloWorld>
子组件:HelloWorld
<template> <input type="number"></template>
这个时候父组件中 type=“text”,而子组件中 type=”number”,而实际中最后显示的是 type=”text”,这并非咱们想要的,因此只要设置:inheritAttrs:false,type 便会成为 number上述这些没被用到的属性,如何被获取呢?这就用到了$attrs
③$attrs
做用:能够获取到没有使用的注册属性,若是须要,咱们在这也能够往下继续传递。
就上上述没有被用到的 desc 和 keysword 就能经过$attrs 获取到。
经过$attrs 的这个特性能够父组件传递到孙组件,免除父组件传递到子组件,再从子组件传递到孙组件的麻烦
代码以下 父组件 Index 部分
<div class="index"> <HelloWorld ref="hello" :desc="desc" :keysword="keysword" :message="message"></HelloWorld></div> data(){ return{ message:'首页', desc:'首页描述', keysword:'我是关键词key' }},
子组件 HelloWorld 部分
<div class="hello"> <sunzi v-bind="$attrs"></sunzi> <button @click="aa">获取父组件的数据</button></div>
孙子组件 sunzi 部分
<template> <div class="header"> {{$attrs}} <br> </div></template>
能够看出经过 v-bind=”$attrs”将数据传到孙组件中
除了以上,provide / inject 也适用于 隔代组件通讯,尤为是获取祖先组件的数据,很是方便。简单的说,当组件的引入层次过多,咱们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,并且这样代码结构容易混乱。这个就是 provide / inject 要干的事情。
<template> <div><childOne></childOne> </div></template><script> import childOne from '../components/test/ChildOne' export default { name: "Parent", provide: { for: "demo" }, components:{ childOne } }
在这里咱们在父组件中 provide for 这个变量,而后直接设置三个组件(childOne、childTwo 、childThird)而且一层层不断内嵌其中, 而在最深层的 childThird 组件中咱们能够经过 inject 获取 for 这个变量
<template> <div> {{demo}} </div></template><script> export default { name: "", inject: ['for'], data() { return { demo: this.for } } }</script>
经过 Vue.directive() 来定义全局指令
有几个可用的钩子(生命周期), 每一个钩子能够选择一些参数. 钩子以下:
bind: 一旦指令附加到元素时触发
inserted: 一旦元素被添加到父元素时触发
update: 每当元素自己更新(可是子元素还未更新)时触发
componentUpdate: 每当组件和子组件被更新时触发
unbind: 一旦指令被移除时触发。
bind 和 update 也许是这五个里面最有用的两个钩子了
每一个钩子都有 el, binding, 和 vnode 参数可用.
update 和 componentUpdated 钩子还暴露了 oldVnode, 以区分传递的旧值和较新的值.
el 就是所绑定的元素.
binding 是一个保护传入钩子的参数的对象. 有不少可用的参数, 包括 name, value, oldValue, expression, arguments, arg 及修饰语.
vnode 有一个更不寻常的用例, 它可用于你须要直接引用到虚拟 DOM 中的节点.
binding 和 vnode 都应该被视为只读.
如今,自定义一个指令,添加一些样式,表示定位的距离
Vue.directive('tack',{ bind(el,binding){ el.style.position='fixed'; el.style.top=binding.value + 'px' }})<div class="header" v-tack="10" >我是header</div>
假设咱们想要区分从顶部或者左侧偏移 70px, 咱们能够经过传递一个参数来作到这一点
Vue.directive('tack', { bind(el, binding, vnode) { el.style.position = 'fixed'; const s = (binding.arg === 'left' ? 'left' : 'top'); el.style[s] = binding.value + 'px'; }})
也能够同时传入不止一个值
Vue.directive('tack', { bind(el, binding, vnode) { el.style.position = 'fixed'; el.style.top = binding.value.top + 'px'; el.style.left = binding.value.left + 'px'; }})<div class="header" v-tack="{left:’20’,top:’20’}" >我是header</div>
breforeCreate():实例建立前,这个阶段实例的 data 和 methods 是读不到的。
created():实例建立后,这个阶段已经完成数据观测,属性和方法的运算,watch/event 事件回调,mount 挂载阶段尚未开始。$el 属性目前不可见,数据并无在 DOM 元素上进行渲染。
created 完成以后,进行 template 编译等操做,将 template 编译为 render 函数,有了 render 函数后才会执行 beforeMount()
beforeMount():在挂载开始以前被调用:相关的 render 函数首次被调用
mounted():挂载以后调用,el 选项的 DOM 节点被新建立的 vm.$el 替换,并挂载到实例上去以后调用今生命周期函数,此时实例的数据在 DOM 节点上进行渲染
后续的钩子函数执行的过程都是须要外部的触发才会执行
有数据的变化,会调用 beforeUpdate,而后通过 Virtual Dom,最后 updated 更新完毕,当组件被销毁的时候,会调用 beforeDestory,以及 destoryed。
computed:
① 有缓存机制;② 不能接受参数;③ 能够依赖其余 computed,甚至是其余组件的 data;④ 不能与 data 中的属性重复
watch:
① 可接受两个参数;② 监听时可触发一个回调,并作一些事情;③ 监听的属性必须是存在的;④ 容许异步
watch 配置:handler、deep(是否深度)、immeditate (是否当即执行)
总结:
当有一些数据须要随着另一些数据变化时,建议使用 computed
当有一个通用的响应数据变化的时候,要执行一些业务逻辑或异步操做的时候建议使用 watch
① computed 中能够分红 getter(读取) 和 setter(设值)
② 通常状况下是没有 setter 的,computed 预设只有 getter ,也就是只能读取,不能改变设值。
1、默认只有 getter 的写法
<div id="demo">{{ fullName }}</div>var vm = new Vue({ el: '#demo', data: { firstName: 'Foo', lastName: 'Bar' }, computed: { fullName: function () { return this.firstName + ' ' + this.lastName } }})//其实fullName的完整写法应该是以下:fullName: { get(){ return this.firstName + ' ' + this.lastName }}
注意:不是说咱们更改了 getter 里使用的变量,就会触发 computed 的更新,前提是 computed 里的值必需要在模板里使用才行。若是将{{fullName}}去掉,get()方法是不会触发的。
2、setter 的写法,能够设值
<template> <div id="demo"> <p> {{ fullName }} </p> <input type="text" v-model="fullName"> <input type="text" v-model="firstName"> <input type="text" v-model="lastName"> </div></template>var vm = new Vue({ el: '#demo', data: { firstName: 'zhang', lastName: 'san' }, computed: { fullName: { //getter 方法 get(){ console.log('computed getter...') return this.firstName + ' ' + this.lastName }, //setter 方法 set(newValue){ console.log('computed setter...') var names = newValue.split(' ') this.firstName = names[0] this.lastName = names[names.length - 1] return this.firstName + ' ' + this.lastName } } }})
在这里,咱们修改 fullName 的值,就会触发 setter,同时也会触发 getter。
注意:并非触发了 setter 也就会触发 getter,他们两个是相互独立的。咱们这里修改了 fullName 会触发 getter 是由于 setter 函数里有改变 firstName 和 lastName 值的代码,这两个值改变了,fullName 依赖于这两个值,因此便会自动改变。
① 全局导航守卫
前置守卫
router.beforeEach((to, from, next) => { // do someting});
后置钩子(没有 next 参数)
router.afterEach((to, from) => { // do someting});
② 路由独享守卫
cont router = new VueRouter({ routes: [ { path: '/file', component: File, beforeEnter: (to, from ,next) => { // do someting } } ]});
顺便看一下路由里面的参数配置:
③ 组件内的导航钩子
组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他们是直接在路由组件内部直接进行定义的
beforeRouteEnter
data(){ return{ pro:'产品' }},beforeRouteEnter:(to,from,next)=>{ console.log(to) next(vm => { console.log(vm.pro) })}
注:beforeRouteEnter 不能获取组件实例 this,由于当守卫执行前,组件实例被没有被建立出来,咱们能够经过给 next 传入一个回调来访问组件实例。在导航被确认时,会执行这个回调,这时就能够访问组件实例了
仅仅是 beforRouteEnter 支持给 next 传递回调,其余两个并不支持,由于剩下两个钩子能够正常获取组件实例 this
如何经过路由将数据传入下一个跳转的页面呢?
答:params 和 query
params
传参this.$router.push({ name:"detail", params:{ name:'xiaoming', }});接受this.$route.params.name
query
传参this.$router.push({ path:'/detail', query:{ name:"xiaoming" } })接受 //接收参数是this.$routethis.$route.query.id
那 query 和 params 什么区别呢?
① params 只能用 name 来引入路由,query 既能够用 name 又能够用 path(一般用 path)
② params 相似于 post 方法,参数不会再地址栏中显示query 相似于 get 请求,页面跳转的时候,能够在地址栏看到请求参数
那刚才提到的 this.和route 有何区别?
先打印出来看一下 router.push 方法
$route 为当前 router 跳转对象,里面能够获取 name、path、query、params 等
es6 新增的主要的特性:
① let const 二者都有块级做用域
② 箭头函数
③ 模板字符串
④ 解构赋值
⑤ for of 循环
⑥ import 、export 导入导出
⑦ set 数据结构
⑧ ...展开运算符
⑨ 修饰器 @
⑩ class 类继承
⑪ async、await
⑫ promise
⑬ Symbol
⑭ Proxy 代理
操做数组经常使用的方法:
es5:concat 、join 、push、pop、shift、unshift、slice、splice、substring 和 substr 、sort、 reverse、indexOf 和 lastIndexOf 、every、some、filter、map、forEach、reduce
es6:find、findIndex、fill、copyWithin、Array.from、Array.of、entries、values、key、includes
经过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变更时发布消息给订阅者,触发相应的监听回调
vue-router 有两种模式,hash 模式和 history 模式
hash 模式
url 中带有#的即是 hash 模式,#后面是 hash 值,它的变化会触发 hashchange 这个事件。
经过这个事件咱们就能够知道 hash 值发生了哪些变化。而后咱们即可以监听 hashchange 来实现更新页面部份内容的操做:
window.onhashchange = function(event){ console.log(event.oldURL, event.newURL); let hash = location.hash.slice(1); document.body.style.color = hash;}
另外,hash 值的变化,并不会致使浏览器向服务器发出请求,浏览器不发出请求,也就不会刷新页面。
history 模式
history api 能够分为两大部分,切换和修改
① 切换历史状态
包括 back,forward,go 三个方法,对应浏览器的前进,后退,跳转操做
history.go(-2);//后退两次history.go(2);//前进两次history.back(); //后退hsitory.forward(); //前进
② 修改历史状态
包括了 pushState,replaceState 两个方法,这两个方法接收三个参数:stateObj,title,url
history.pushState({color:'red'}, 'red', 'red'})window.onpopstate = function(event){ console.log(event.state) if(event.state && event.state.color === 'red'){ document.body.style.color = 'red'; }}history.back();history.forward();
经过 pushstate 把页面的状态保存在 state 对象中,当页面的 url 再变回这个 url 时,能够经过 event.state 取到这个 state 对象,从而能够对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态均可以存储到 state 的里面。
history 缺点:
1:hash 模式下,仅 hash 符号以前的内容会被包含在请求中,如http://www.a12c.com,所以对于...,即便没有作到对路由的全覆盖,也不会返回 404 错误。
2:history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致。如http://www.a12c.com/book/a。若是后端缺乏对/book/a 的路由处理,将返回 404 错误
往期
127个经常使用的JS代码片断,每段代码花30秒就能看懂(三)
127个经常使用的JS代码片断,每段代码花30秒就能看懂(四)
[
](http://mp.weixin.qq.com/s?__b...