[toc]
1.页面中定义一个定时器,在哪个阶段清除?
答案:在 beforeDestroy 中销毁定时器。
① 为什么销毁它:
在页面 a 中写了一个定时器,比如每隔一秒钟打印一次 1,当我点击按钮进入页面 b 的时候,会发现定时器依然在执行,这是非常消耗性能的。
② 解决方案
A:1
2
3
4
5
6
7
8mounted(){
this.timer = setInterval(()=>{
console.log(1)
},1000)
},
beforeDestroy(){
clearInterval(this.timer)
}
方案A有两点不好的地方,引用尤大的话来说就是:
它需要在这个组件实例中保存这个 timer,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。
方案B(推荐):该方法是通过$once 这个事件侦听器在定义完定时器之后的位置来清除定时器1
2
3
4
5
6
7
8mounted(){
const timer = setInterval(()=>{
console.log(1)
},1000)
this.$once('hook:beforeDestroy',()=>{
clearInterval(timer)
})
}
官网参考链接:https://cn.vuejs.org/v2/guide/components-edge-cases.html
2.父组件如何获取子组件的数据,子组件如何获取父组件的数据,父子组件如何传值?
① 先说,父组件如何主动获取子组件的数据?
方案 1:$children
$children 用来访问子组件实例,要知道一个组件的子组件可能是不唯一的,所以它的返回值是数组。
现在,我们定义 Header,HelloWorld 两个组件1
2
3
4
5
6
7
8
9
10<template>
<div class="index">
<Header></Header>
<HelloWorld :message="message"></HelloWorld>
<button @click="goPro">跳转</button>
</div>
</template>
mounted(){
console.log(this.$children)
}
打印的是一个数组,可以用 foreach 分别得到所需要的的数据
缺点:
无法确定子组件的顺序,也不是响应式的。如果你确切的知道要访问子组件建议使用$refs。
方案 2 :$refs1
<HelloWorld ref="hello" :message="message"></HelloWorld>
调用 helloworld 子组件的时候直接定义一个 ref,这样就可以通过 this.$refs 获取所需要的的数据。1
2this.$refs.hello.属性
this.$refs.hello.方法
② 子组件如何主动获取父组件中的数据?
通过 :$parent
用来访问父组件实例,通常父组件都是唯一确定的,跟children类似1
2this.$parent.属性
this.$parent.方法
父子组件通信除了以上三种,还有 props 和 attrs
③ inheritAttrs
这是@2.4 新增的属性和接口。inheritAttrs 属性控制子组件 html 属性上是否显示父组件的提供的属性。
如果我们将父组件 Index 中的属性 desc、keysword、message 三个数据传递到子组件 HelloWorld 中的话,如下
父组件 Index 部分1
<HelloWorld ref="hello" :desc="desc" :keysword="keysword" :message="message"></HelloWorld>
子组件:HelloWorld,props 中只接受了 message1
2
3props: {
message: String
},
实际情况,我们只需要 message,那其他两个属性则会被当做普通的 html 元素插在子组件的根元素上。
如图
这样做会使组件预期功能变得模糊不清,这个时候,在子组件中写入,inheritAttrs:false ,这些没用到的属性便会被去掉,true 的话,就会显示。
如果,父组件中没被需要的属性,跟子组件本来的属性冲突的时候,则依据父组件1
<HelloWorld ref="hello" type="text" :message="message"></HelloWorld>
子组件:HelloWorld1
2
3<template>
<input type="number">
</template>
这个时候父组件中 type=“text”,而子组件中 type=”number”,而实际中最后显示的是 type=”text”,这并不是我们想要的,所以只要设置:inheritAttrs:false,type 便会成为 number
上述这些没被用到的属性,如何被获取呢?这就用到了$attrs
③ $attrs
作用:可以获取到没有使用的注册属性,如果需要,我们在这也可以往下继续传递。
就上上述没有被用到的 desc 和 keysword 就能通过$attrs 获取到。
通过$attrs 的这个特性可以父组件传递到孙组件,免除父组件传递到子组件,再从子组件传递到孙组件的麻烦
代码如下 父组件 Index 部分1
2
3
4
5
6
7
8
9
10<div class="index">
<HelloWorld ref="hello" :desc="desc" :keysword="keysword" :message="message"></HelloWorld>
</div>
data(){
return{
message:'首页',
desc:'首页描述',
keysword:'我是关键词key'
}
},
子组件 HelloWorld 部分1
2
3
4<div class="hello">
<sunzi v-bind="$attrs"></sunzi>
<button @click="aa">获取父组件的数据</button>
</div>
孙子组件 sunzi 部分1
2
3
4
5
6<template>
<div class="header">
{{$attrs}}
<br>
</div>
</template>
可以看出通过 v-bind=”$attrs”将数据传到孙组件中
除了以上,provide / inject 也适用于 隔代组件通信,尤其是获取祖先组件的数据,非常方便。
简单的说,当组件的引入层次过多,我们的子孙组件想要获取祖先组件的资源,那么怎么办呢,总不能一直取父级往上吧,而且这样代码结构容易混乱。这个就是 provide / inject 要干的事情。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<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 这个变量1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<template>
<div>
{{demo}}
</div>
</template>
<script>
export default {
name: "",
inject: ['for'],
data() {
return {
demo: this.for
}
}
}
</script>
3.自定义指令如何定义,它的生命周期是什么?
通过 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 都应该被视为只读.
现在,自定义一个指令,添加一些样式,表示定位的距离1
2
3
4
5
6
7Vue.directive('tack',{
bind(el,binding){
el.style.position='fixed';
el.style.top=binding.value + 'px'
}
})
<div class="header" v-tack="10" >我是header</div>
假设我们想要区分从顶部或者左侧偏移 70px, 我们可以通过传递一个参数来做到这一点1
2
3
4
5
6
7Vue.directive('tack', {
bind(el, binding, vnode) {
el.style.position = 'fixed';
const s = (binding.arg === 'left' ? 'left' : 'top');
el.style[s] = binding.value + 'px';
}
})
也可以同时传入不止一个值1
2
3
4
5
6
7
8Vue.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>
4、vue 生命周期,各个阶段简单讲一下?
- 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。
5、watch 和 computed 的区别?
computed:
① 有缓存机制;② 不能接受参数;③ 可以依赖其他 computed,甚至是其他组件的 data;④ 不能与 data 中的属性重复
watch:
① 可接受两个参数;② 监听时可触发一个回调,并做一些事情;③ 监听的属性必须是存在的;④ 允许异步
watch 配置:handler、deep(是否深度)、immeditate (是否立即执行)
总结:
当有一些数据需要随着另外一些数据变化时,建议使用 computed
当有一个通用的响应数据变化的时候,要执行一些业务逻辑或异步操作的时候建议使用 watch
6、请说一下 computed 中的 getter 和 setter
① computed 中可以分成 getter(读取) 和 setter(设值)
② 一般情况下是没有 setter 的,computed 预设只有 getter ,也就是只能读取,不能改变设值。
一、默认只有 getter 的写法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<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 里的值必须要在模板里使用才行。如果将去掉,get()方法是不会触发的。
二、setter 的写法,可以设值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34<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 依赖于这两个值,所以便会自动改变。
7、导航钩子有哪几种,分别如何用,如何将数据传入下一个点击的路由页面?
① 全局导航守卫
前置守卫1
2
3router.beforeEach((to, from, next) => {
// do someting
});
后置钩子(没有 next 参数)1
2
3router.afterEach((to, from) => {
// do someting
});
② 路由独享守卫1
2
3
4
5
6
7
8
9
10
11cont router = new VueRouter({
routes: [
{
path: '/file',
component: File,
beforeEnter: (to, from ,next) => {
// do someting
}
}
]
});
顺便看一下路由里面的参数配置:
③ 组件内的导航钩子
组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。他们是直接在路由组件内部直接进行定义的
beforeRouteEnter1
2
3
4
5
6
7
8
9
10
11data(){
return{
pro:'产品'
}
},
beforeRouteEnter:(to,from,next)=>{
console.log(to)
next(vm => {
console.log(vm.pro)
})
}
注:beforeRouteEnter 不能获取组件实例 this,因为当守卫执行前,组件实例被没有被创建出来,我们可以通过给 next 传入一个回调来访问组件实例。在导航被确认时,会执行这个回调,这时就可以访问组件实例了
仅仅是 beforRouteEnter 支持给 next 传递回调,其他两个并不支持,因为剩下两个钩子可以正常获取组件实例 this
如何通过路由将数据传入下一个跳转的页面呢?
答:params 和 query
params1
2
3
4
5
6
7
8
9// 传参
this.$router.push({
name:"detail",
params:{
name:'xiaoming',
}
});
// 接受
this.$route.params.name
query1
2
3
4
5
6
7
8
9
10// 传参
this.$router.push({
path:'/detail',
query:{
name:"xiaoming"
}
})
// 接受
// 接收参数是this.$route
this.$route.query.id
那 query 和 params 什么区别呢?
① params 只能用 name 来引入路由,query 既可以用 name 又可以用 path(通常用 path)
② params 类似于 post 方法,参数不会再地址栏中显示
query 类似于 get 请求,页面跳转的时候,可以在地址栏看到请求参数
那刚才提到的 this.router和this.route有何区别?
先打印出来看一下
router.push 方法
$route 为当前 router 跳转对象,里面可以获取 name、path、query、params 等
8、es6 的特有的类型, 常用的操作数组的方法都有哪些?
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
9、vue 双向绑定原理?
通过 Object.defineProperty()来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
10、vue-router 的实现原理,history 和 hash 模式有什么区别?
vue-router 有两种模式,hash 模式和 history 模式
hash 模式
url 中带有#的便是 hash 模式,#后面是 hash 值,它的变化会触发 hashchange 这个事件。
通过这个事件我们就可以知道 hash 值发生了哪些变化。然后我们便可以监听 hashchange 来实现更新页面部分内容的操作:1
2
3
4
5window.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 三个方法,对应浏览器的前进,后退,跳转操作1
2
3
4history.go(-2);//后退两次
history.go(2);//前进两次
history.back(); //后退
hsitory.forward(); //前进
② 修改历史状态
包括了 pushState,replaceState 两个方法,这两个方法接收三个参数:stateObj,title,url1
2
3
4
5
6
7
8
9history.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 错误
11、怎么在vue中点击别的区域输入框不会失去焦点?
答:阻止事件的默认行为
具体操作:监听你想点击后不会丢失 input 焦点的那个元素的 mousedown 事件,回调里面调用 event.preventDefault(),会阻止使当前焦点丢失这一默认行为。
12、vue中data的属性可以和methods中的方法同名吗?为什么?
答:不可以
因为,Vue会把methods和data的东西,全部代理到Vue生成的对象中,会产生覆盖所以最好不要同名
13、怎么给vue定义全局的方法?
Vue.prototype.方法名称
14、Vue 2.0 不再支持在 v-html 中使用过滤器怎么办?
解决方法:
①全局方法(推荐)1
2
3
4Vue.prototype.msg = function(msg){
return msg.replace("\n","<br>")
}
<div v-html="msg(content)"></div>
②computed方法1
2
3
4
5
6computed:{
content:function(msg){
return msg.replace("\n","<br>")
}
}
<div>{{content}}</div>
③$options.filters(推荐)1
2
3
4
5
6
7
8
9filters:{
msg:function(msg){
return msg.replace(/\n/g,"<br>")
}
},
data:{
content:"XXXX"
}
<div v-html="$options.filters.msg(content)"></div>
14、怎么解决vue打包后静态资源图片失效的问题?
答:将静态资源的存放位置放在src目录下
16、怎么解决vue动态设置img的src不生效的问题?
1 | <img class="logo" :src="logo" alt="公司logo"> |
因为动态添加src被当做静态资源处理了,没有进行编译,所以要加上require
17、跟keep-alive有关的生命周期是哪些?描述下这些生命周期
activated和deactivated两个生命周期函数
1.activated:当组件激活时,钩子触发的顺序是created->mounted->activated
2.deactivated: 组件停用时会触发deactivated,当再次前进或者后退的时候只触发activated
18、你知道vue中key的原理吗?说说你对它的理解
暂时没弄明白,等会儿写
19、vue中怎么重置data?
答:Object.assign()
Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。1
2
3
4
5
6var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1); // { a: 1, b: 2, c: 3 },注意目标对象自身也会改变。
注意,具有相同属性的对象,同名属性,后边的会覆盖前边的。
由于Object.assign()有上述特性,所以我们在Vue中可以这样使用:
Vue组件可能会有这样的需求:在某种情况下,需要重置Vue组件的data数据。此时,我们可以通过this.$data获取当前状态下的data,通过this.$options.data()获取该组件初始状态下的data。
然后只要使用Object.assign(this.options.data())就可以将当前状态的data重置为初始状态。
20、vue怎么实现强制刷新组件?
答:① v-if ② this.$forceUpdate
v-if1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//当v-if的值发生变化时,组件都会被重新渲染一遍。因此,利用v-if指令的特性,可以达到强制
<comp v-if="update"></comp>
<button @click="reload()">刷新comp组件</button>
data() {
return {
update: true
}
},
methods: {
reload() {
// 移除组件
this.update = false
// 在组件移除后,重新渲染组件
// this.$nextTick可实现在DOM 状态更新后,执行传入的方法。
this.$nextTick(() => {
this.update = true
})
}
}
this.$forceUpdate1
2
3
4
5
6<button @click="reload()">刷新当前组件</button>
methods: {
reload() {
this.$forceUpdate()
}
}
21、vue如何优化首页的加载速度?
① 第三方js库按CDN引入(一、cdn引入 二、去掉第三方库引入的import 三、把第三方库的js文件从打包文件里去掉)
② vue-router路由懒加载
③ 压缩图片资源
④ 静态文件本地缓存
http缓存:推荐网站:https://www.cnblogs.com/chinajava/p/5705169.html
service worker离线缓存:,缺点:需要在HTTPS站点下,推荐:http://lzw.me/a/pwa-service-worker.html
⑤ 服务器端SSR渲染
除了上面的方案以外,另一种方案也不容小视
我们先说说通常项目中是如何加载页面数据:Vue组件生命周期中请求异步接口,在mounted之前应该都可以,据我了解绝大部分同学是在mounted的时候执行异步请求。但是我们可以把页面需要的请求放到Vue-Router的守卫中执行,意思是在路由beforeEnter之前就可以请求待加载页面中所有组件需要的数据,此时待加载页面的Vue组件还没开始渲染,而Vue组件开始渲染的时候我们就可以用Vuex里面的数据了。
以上方法的实现思路:
图意:每个页面(Page)中都会有很多个Vue组件,可以在Vue组件中添加自定义属性fetchData,fetchData里面可以执行异步请求(图中执行Vuex的Action),但是我们怎么获取到所有组件的fetchData方法并执行呢?如图所示,在router.beforeResolve守卫中,我们看看router.beforeResolve的定义,所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用,意思是即使页面中有异步组件,它会等待异步组件解析之后执行,并且解析守卫在beforeEnter之前执行。那我们怎么在解析守卫中获取到待加载页面的所有组件呢?通过router.getMatchedComponents方法。
这样我们就可以在解析守卫中获取到所有待加载组件的fetchData方法并执行,这样无疑会在组件开始渲染之后获取到所有数据,提高页面加载速度。以上方法的实现思路:
很多人可能有个疑问,如果异步请求放在beforeCreate和created不是一样吗?答案是否定的,因为这种方式可以将异步请求放到beforeCreate之前!
22、你了解vue的diff算法吗?
推荐网站:https://www.cnblogs.com/wind-lanyan/p/9061684.html
23、vue能监听到数组变化的方法有哪些?为什么这些方法能监听到呢?
Vue.js观察数组变化主要通过以下7个方法(push、pop、shift、unshift、splice、sort、reverse)
大家知道,通过Object.defineProperty()劫持数组为其设置getter和setter后,调用的数组的push、splice、pop等方法改变数组元素时并不会触发数组的setter,继而数组的数据变化并不是响应式的,但是vue实际开发中却是实时响应的,是因为vue重写了数组的push、splice、pop等方法
从源码中可以看出,ob.dep.notify()将当前数组的变更通知给其订阅者,这样当使用重写后方法改变数组后,数组订阅者会将这边变化更新到页面中
24、说说你对proxy的理解?
Proxy用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var target = {
name: 'zhangsan',
age:20,
sex:'男'
}
var logHandler = {
get(target, key) {
console.log(`${key}被读取`)
return target[key]
},
set(target, key, value) {
console.log(`${key}被设置为${value}`)
target[key] = value
}
}
var demo = new Proxy(target, logHandler)
demo.name //name被读取
var proxy = new Proxy(target, handler);
Proxy对象的所有用法,都是上面的这种形式。不同的只是handle参数的写法。其中new Proxy用来生成Proxy实例,target是表示所要拦截的对象,handle是用来定制拦截行为的对象。
我们可以将Proxy对象,设置到object.proxy属性,从而可以在object对象上调用。1
var object = { proxy: new Proxy(target, handler) };
Proxy对象也可以作为其它对象的原型对象。1
2
3
4
5
6
7var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
上面代码中,proxy对象是obj的原型对象,obj本身并没有time属性,所以根据原型链,会在proxy对象上读取属性,从而被拦截。
同一个拦截函数,可以设置多个操作。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var handler = {
get: function (target, name) {
if (name === 'prototype') {
return Object.prototype;
}
return 'Hello, ' + name;
},
apply: function (target, thisBinding, args) {
return args[0];
},
construct: function (target, args) {
return { value: args[1] };
}
};
var fproxy = new Proxy(function (x, y) {
return x + y;
}, handler);
fproxy(1, 2) // 1
new fproxy(1, 2) // {value: 2}
fproxy.prototype === Object.prototype // true
fproxy.foo === "Hello, foo" // true
25、怎么缓存当前的组件?缓存后怎么更新?
1 | <keep-alive> |
如果缓存的组件想要清空数据或者执行初始化方法,在加载组件的时候调用activated钩子函数,如下:1
2
3activated: function () {
this.data = '';
}
26、axios怎么解决跨域的问题?
使用axios直接进行跨域访问不可行,我们需要配置代理
代理可以解决的原因:
因为客户端请求服务端的数据是存在跨域问题的,而==服务器和服务器之间可以相互请求数据,是没有跨域的概念==(如果服务器没有设置禁止跨域的权限问题),也就是说,我们可以配置一个代理的服务器可以请求另一个服务器中的数据,然后把请求出来的数据返回到我们的代理服务器中,代理服务器再返回数据给我们的客户端,这样我们就可以实现跨域访问数据
1.配置BaseUrl1
2
3import axios from 'axios'
Vue.prototype.$axios = axios
axios.defaults.baseURL = '/api' //关键代码
2.配置代理
在config文件夹下的index.js文件中的proxyTable字段中,作如下处理:1
2
3
4
5
6
7
8
9
10
11
12
13proxyTable: {
'/api': {
target:'http://api.douban.com/v2', // 你请求的第三方接口
changeOrigin:true,
// 在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据,
//这样服务端和服务端进行数据的交互就不会有跨域问题
pathRewrite:{ // 路径重写,
'^/api': ''
// 替换target中的请求地址,也就是说以后你在请求http://api.douban.com/v2/XXXXX
//这个地址的时候直接写成/api即可。
}
}
}
- 在具体使用axios的地方,修改url如下即可
1
2
3
4
5
6
7
8axios.get("/movie/top250").then((res) => {
res = res.data
if (res.errno === ERR_OK) {
this.themeList=res.data;
}
}).catch((error) => {
console.warn(error)
})
原理:
因为我们给url加上了前缀/api,我们访问/movie/top250就当于访问了:localhost:8080/api/movie/top250(其中localhost:8080是默认的IP和端口)。
在index.js中的proxyTable中拦截了/api,并把/api及其前面的所有替换成了target中的内容,因此实际访问Url是http://api.douban.com/v2/movie/top250。
至此,纯前端配置代理解决axios跨域得到解决
27、怎么实现路由懒加载呢?
第一种(最常用):1
2
3
4
5
6const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
第二种:1
2
3
4
5
6
7
8
9
10const router = new Router({
routes: [
{
path: '/index',
component: (resolve) => {
require(['../components/index'], resolve) // 这里是你的模块 不用import去引入了
}
}
]
})
第三种(官方推荐):1
2
3
4
5
6
7
8
9
10
11
12// r就是resolve
const list = r => require.ensure([], () => r(require('../components/list/list')), 'list');
// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载
const router = new Router({
routes: [
{
path: '/list/blog',
component: list,
name: 'blog'
}
]
})
28、怎样动态加载路由?
一、思路
① 在vue-router对象中首先初始化公共路由,比如(首页,404,login)等
② 用户登陆成功后,根据用户的角色信息,获取对应权限菜单信息menuList,并将后台返回的menuList转换成我们需要的router数据结构
③ 通过router.addRouter(routes)方法,同时我们可以将转后的路由信息保存于vuex,这样我们可以在我们的SideBar组件中获取我们的全部路由信息,并且渲染我们的左侧菜单栏,让动态路由实现。
二、实现
① 初始化公共路由1
2
3
4
5
6
7
8
9//只显示主要代码
export const routes= [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true }
]
export default new Router({
scrollBehavior: () => ({ y: 0 }),
routes: routes
})
② 登陆成功后,获取菜单信息menuList,并转换成router数组的结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38router.beforeEach((to, from, next) => {
NProgress.start()//进度条包 npm安装
if (getToken()) {
/*有 token,已经登录成功*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
store.dispatch('GetInfo').then(res => { // 拉取user_info
const roles = res.roles
store.dispatch("GetMenu").then(data => {
initMenu(router, data);
});
next()
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
/* 无 token*/
if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
next()
} else {
next('/login') // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
③ 动态加载路由1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45import store from '../store'
export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound) //404组件最后添加
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
export const formatRoutes = (aMenu) => {
const aRouter = []
aMenu.forEach(oMenu => {
const {
path,
component,
name,
icon,
childrens
} = oMenu
if (!validatenull(component)) {
let filePath;
const oRouter = {
path: path,
component(resolve) {
let componentPath = ''
if (component === 'Layout') {
require(['../views/layout/Layout'], resolve)
return
} else {
componentPath = component
}
require([`../${componentPath}.vue`], resolve)
},
name: name,
icon: icon,
children: validatenull(childrens) ? [] : formatRoutes(childrens)
}
aRouter.push(oRouter)
}
})
return aRouter
}
④ 渲染菜单1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38<template>
<el-scrollbar wrapClass="scrollbar-wrapper">
<el-menu
mode="vertical"
:show-timeout="200"
:default-active="$route.path"
:collapse="isCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409EFF"
>
<sidebar-item v-for="route in permission_routers" :key="route.name" :item="route" :base-path="route.path"></sidebar-item>
</el-menu>
</el-scrollbar>
</template>
<script>
import { mapGetters } from 'vuex'
import SidebarItem from './SidebarItem'
import { validatenull } from "@/utils/validate";
import { initMenu } from "@/utils/util";
export default {
components: { SidebarItem },
created() {
},
computed: {
...mapGetters([
'permission_routers',
'sidebar',
'addRouters'
]),
isCollapse() {
return !this.sidebar.opened
}
}
}
</script>
就这样我们动态加载路由就是实现了,关键点就是router.addRoute方法
⑤ 防坑
点击刷新的时候页面空白 控制台也不报错?
点击刷新,vue-router会重新初始化,那么我们之前的动态addRoute就不存在了,此时访问一个不存在的页面,所以我们的sidebar组件也就不会被访问,那么也无法获取菜单信息,就导致页面空白。所以我们需要把加载菜单信息这一步放在router的全局守卫beforeEach中就可以了。1
2
3
4
5
6
7
8
9
10
11
12export const initMenu = (router, menu) => {
if (menu.length === 0) {
return
}
let menus = formatRoutes(menu);
// 最后添加
let unfound = { path: '*', redirect: '/404', hidden: true }
menus.push(unfound)
router.addRoutes(menus)
store.commit('ADD_ROUTERS',menus)
}
//404组件一定要放在动态路由组件的最后,不然你刷新动态加载的页面,会跳转到404页面的
29、切换到新路由时,页面要滚动到顶部或保持原先的滚动位置怎么做呢?
当创建一个 Router 实例,可以提供一个 scrollBehavior 方法:1
2
3
4
5
6
7注意: 这个功能只在 HTML5 history 模式下可用。
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
}
})
scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。1
2
3scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
} // 对于所有路由导航,简单地让页面滚动到顶部。
返回 savedPosition,在按下 后退/前进 按钮时,在滚动条位置,就会像浏览器的原生表现那样:1
2
3
4
5
6
7scrollBehavior (to, from, savedPosition) {
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
模拟『滚动到锚点』的行为1
2
3
4
5
6
7scrollBehavior (to, from, savedPosition) {
if (to.hash) {
return {
selector: to.hash
}
}
}
还可以利用路由元信息更细颗粒度地控制滚动。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20routes: [
{ path: '/', component: Home, meta: { scrollToTop: true }},
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar, meta: { scrollToTop: true }}
]
const scrollBehavior = (to, from, savedPosition) => {
if (savedPosition) {
return savedPosition
} else {
const position = {}
if (to.hash) {
position.selector = to.hash
}
if (to.matched.some(m => m.meta.scrollToTop)) {
position.x = 0
position.y = 0
}
return position
}
}
还可以在main.js入口文件配合vue-router写这个1
2
3router.afterEach((to,from,next) => {
window.scrollTo(0,0);
});
30、vue-router如何响应路由参数的变化?
当使用路由参数时,比如:1
{path:’/list/:id’component:Foo}
从 /list/aside导航到 /list/foo,原来的组件实例会被复用。
因为两个路由都渲染同个组件Foo,比起销毁再创建,复用则更加高效。
不过,这也意味着组件的生命周期钩子不会再被调用。
如果跳转到相同的路由还会报以下错误
这个时候我们需要重写push方法,在src/router/index.js 里面import VueRouter from ‘vue-router’下面写入下面方法即可1
2
3
4const routerPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return routerPush.call(this, location).catch(error=> error)
}
如何响应不同的数据呢?
① 复用组件时,想对路由参数的变化作出响应的话,你可以简单地 ==watch (监测变化) $route 对象==:1
2
3
4
5
6
7
8const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...
}
}
}
② 使用beforeRouteUpdate1
2
3
4
5
6
7const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
注意:
(1)从同一个组件跳转到同一个组件。
(2)生命周期钩子created和mounted都不会调用。
31、vue模板中为什么以_、$开始的变量无法渲染?
名字以_或$开始的属性不会被vue实例代理,因为它们可能与vue的内置属性与API方法冲突。用vm.data._property 访问它们。
32、vue中,如何监听一个对象内部的变化?
方法①:对整个obj深层监听1
2
3
4
5
6
7
8
9
10watch:{
obj:{
handler(newValue,oldValue){
console.log('obj changed')
},
deep: true,//深度遍历
immediate: true
//默认第一次绑定的时候不会触发watch监听,值为true时可以在最初绑定的时候执行
}
}
方法② :指定key1
2
3
4
5
6
7watch: {
"dataobj.name": {
handler(newValue, oldValue) {
console.log("obj changed");
}
}
}
方法③:computed1
2
3
4
5computed(){
ar(){
return this.obj.name
}
}
33、v-for循环时为什么要加key?
key的作用主要是为了高效的更新虚拟DOM,是因为Virtual DOM 使用Diff算法实现的原因。
当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。
比如一下这个情况
我们希望可以在B和C之间加一个F,Diff算法默认执行起来是这样的:
即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。
34、$nextTick用过吗,有什么作用?
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
解决的问题:==有些时候在改变数据后立即要对dom进行操作==,此时获取到的dom仍是获取到的是数据刷新前的dom,无法满足需要,这个时候就用到了$nextTick。
35、vue和react的区别是什么?
① React严格上只针对MVC的view层,Vue则是MVVM模式
② virtual DOM不一样,vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树.而对于React而言,每当应用的状态被改变时,全部组件都会重新渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
③ 组件写法不一样, React推荐的做法是 JSX + inline style, 也就是把HTML和CSS全都写进JavaScript了,即’all in js’; Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,jd写在同一个文件;
④ 数据绑定: vue实现了数据的双向绑定,react数据流动是单向的
⑤ state对象在react应用中不可变的,需要使用setState方法更新状态;在vue中,state对象不是必须的,数据由data属性在vue对象中管理