vue2.0面试篇

$nextTick的使用

答:当你修改了data的值然后马上获取这个dom元素的值,是不能获取到更新后的值,
你需要使用$nextTick这个回调,让修改后的data值渲染更新到dom元素之后在获取,才能成功。

data为什么必须是一个函数

  1. 组件中的data写成一个函数,这样每次复用组件的时候,都会返回一份新的data,相当于每个组件实例都有自己私有的数据空间,它们只负责各自维护的数据,不会造成混乱。
  2. 而单纯的写成对象形式,就是所有的组件实例共用了一个data,这样改一个全都改了。

delete和Vue.delete删除数组的区别

答:delete只是被删除的元素变成了 empty/undefined 其他的元素的键值还是不变。Vue.delete 直接删除了数组 改变了数组的键值。

父子组件传值

  1. props父传子,$emit监听子组件事件

  2. eventBus

  3. p a r e n t 、 parent、 parentchildren、

  4. p a r e n t 、 parent、 parentrefs

  5. provide、inject 非响应式

    • provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。
  6. vuex

  7. 插槽

  8. v-model:这样子组件就可以改变父组件传递的值

  • 第一步:父组件中给子组件v-model一个自己的foo属性

    <zizujian v-model="foo" :rows="rows">zizujian>
    
  • 第二步:子组件用一个model属性来接收这个foo属性的值;

  • 第三步:子组件需要model中定义一个自己属性pfoo来存放父组件传递过来的foo值,并指定反馈父组件数值变化的自定义函数(这里使用event1)

    		model: {
                prop: "pfoo",//指定自己的pfoo属性接受数值
                event: "event1"//指定自己的event1事件回送数据给父组件
            },
            props: {  
                rows: {
                    type: Array,
                    default: []
                },
    			//根据自己model的要求定义pfoo属性
                pfoo: { 
                    type: String, 
                    default: ""           			
                }
            },
            methods:{
                doChange(val){
                    console.log(val)
                    console.log(this.pfoo);
                    this.$emit('event1',this.pfoo);
                },
            }
    
  • 第四步:子组件以某一个用户的操作触发改变,调用某一个函数来触发自定义事件(event1),使用$emit传递数值给父组件v-model属性。完成同步。

v-for中key的作用

当数据发生变化时,vue是怎样更新节点的?
渲染真实DOM的开销是很大的,比如有时我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排。diff算法先根据真实DOM生成一颗virtual DOM,当virtual DOM某个节点的数据改变后生成一个新的vnode,然后Vnode和oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode

Diff算法两个假设
1.两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构
2.同一个层级的一组节点,他们可以通过惟一的id进行区分

diff的比较方式

  • 当页面的数据发生变化时,diff算法比较新旧节点的时候,比较只会在同层进行,不会跨层级比较。
  • 如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。
  • 如果节点类型相同:则会重新设置该节点的属性,从而实现节点的更新。

key来给每个节点做一个唯一的标识,Diff算法就可以正确地识别此节点,找到正确的位置区域插入新的节点

所以:比如列表随便删除一项,若没有key,他会删除最后一项

key的作用主要是为了高效地更新虚拟DOM,另外vue在使用相同标签名元素的过渡切换时,也会使用到key属性。其目的也是为了让vue可以区分他们,否则vue只会替换其内部属性而不会触发过渡效果。

不建议使用index作为key,当向数组中指定位置插入一个新元素后,对应着后面的虚拟DOM的key值全部更新了,这个时候还是会做不必要的更新,就像没有加KEY一样。此外对于单选框删除,选中项错乱问题依然存在

image-20210329212818179 image-20210329212856632

ref

  1. 在普通DOM元素中,添加ref属性名字为a,this.$refs.a指的就是DOM元素
  2. 在子组件的添加fef属性名字为b,this.$refs.b指的就是子组件的实例
  3. 父组件调取子组件的方法:this.$refs.b.子组件方法名(参数)

.native

  1. 主要是给自定义的组件添加原生事件
  2. 有时候我们想要给 子组件整体添加一个事件,而不是子组件内部的某个dom元素上添加事件,从父组件给子组件添加事件

,这样添加点击事件是不会触发的,

因为在自定义组件上注册的事件触发的是组件自定义的事件,根本不是原生的dom事件,所以要添加.native

即可触发

如果使用router-link标签,加上@click事件,绑定的事件会无效因为:router-link的作用是单纯的路由跳转,会阻止click事件,你可以试试只用click不用native,事件是不会触发的。此时加上**.native**,才会触发事件

路由两种模式

  1. hash模式:vue-router默认的是hash模式。使用URL的hash来模拟一个完整的URL,于是当URL改变的时候,页面不会重新加载,也就是单页应用了。

    对于hash模式会创建hashHistory对象,在访问不同的路由的时候,会发生两件事:HashHistory.push()将新的路由添加到浏览器访问的历史的栈顶,和HasHistory.replace()替换到当前栈顶的路由

  2. history模式:window.history对象打印出来可以看到里边提供的方法和记录长度。利用了HTML5中新增的pushState() 和replaceState() 方法。用于浏览器的历史记录站,在当前已有的back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改是,虽然改变了当前的URL,但你浏览器不会立即向后端发送请求。 history模式,会出现404 的情况,需要后台配置。

  3. abstract模式
    适用于所有JavaScript环境,例如服务器端使用Node.js。如果没有浏览器API,路由器将自动被强制进入此模式。

    const router = new VueRouter({routes, mode:‘hash|history|abstract’,})

动态路由

  • 在路由文件配置动态路由

    const routes = [
        {
            path:'/',
            redirect: "/home"
        },
        {
            path:'/user/:name', //动态路由
            component: User
        },
    ];
    
  • 页面使用

    
    <div id="app">
        
         <router-link :to="'/user/' + name">用户router-link>
        <router-view>router-view>
    div>
    this.$route.params.name  //获取动态路由的参数‘zhangsan’
    
  • 路由监听

    动态路由在来回切换时,vue不会销毁再创建这个组件,而是复用这个组件,会显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。可以使用watch (监测变化) $route 对象或者使用路由守卫;

嵌套路由

const user = () => import('./user.vue')
const router = new VueRouter({
  routes: [
    { 
     	path: '/user', 
     	component: user,
     	children: [
            {
                path: '',
                name: 'user', //默认子路由
                component: Profile
            },
            {
                 path: 'cart',
                 name: 'user-cart',
                 component: Cart
             }
 		 ]
    },
  ]
  
  //使用
  <template>
    <div>
        <h3>用户中心</h3>
        <ul class="left">
            <router-link exact tag="li" :to="{name: 'user'}">基本信息</router-link>
            <router-link tag="li" :to="{name: 'user-cart'}">我的购物车</router-link>
        </ul>
        <div class="right">
            <router-view></router-view> //放置子页面
        </div>
    </div>
</template>
    
  • 子路由的 path 不需要加 /
  • 如果一个子路由的 path 为空,表示为默认子路由。父级 name 属性需要设置给这个默认子路由

路由守卫

1、全局守卫:

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
   //如果进入到的路由是登录页或者注册页面,则正常展示
   if (to.name !== 'Login' && !isAuthenticated) next('/Login')
  // 如果用户未能验证身份,则 `next` 会被调用两次
   else next()
})
router.afterEach((to, from) => {
  // 全局后置钩子:不接受 next 函数也不会改变导航本身:
})

2、路由独享守卫:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

3、组件内守卫:

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    //不能访问this。因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
    //可以通过传一个回调给 next来访问组件实例。beforeRouteEnter 是支持给 next 传递回调的唯一守卫。
    next(vm => {
		alert("Hello"+vm.name);
	})
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
    //通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false) 来取消
    if(confirm("确定离开吗?") == true ){  //询问是否离开==true
		next();  //确认离开
	}else{
		next(false);  //false不离开
	}
  }
}

vuex

  • export可以使用多次,引入需要加{}
  • export default 一个页面只能使用一次,可以直接引入

npm i vuex

//store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)

export const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  },
  actions: {
	  increment ({ commit },data) {
	  	//异步方法,获取到data
	    commit('increment',data) //异步执行完,触发mutations中方法的执行
	  }
  }
})

//main.js
import store from './store/index'  
//Vue.prototype.$store = store
//store可以注册在原型上,也可以注册在跟实例上
const app = new Vue({
    ...App,
	store,
})

1、state

  1. 对于当前页面只使用一个store中的变量,并且当前页面不会改变state中使用到的数据,则可以直接{{$store.state.name}}
  2. 对于当前页面需要使用多个state中的数据或者当前页面会操作vuex中数据的改变,则需要在计算属性computed中使用state的辅助函数 mapState,实时监测state的改变,实时更新页面

使用方法

//引入:
import { mapState } from  'vuex'
computed:{
	//这里使用...的意思是对象的扩展,用于和其他的计算变量合并成一个对象
	...mapState({
		name:'name', //变量名一致时
		sex:(state) => state.userinfo.sex
	})
}

2、getters

  • getters可以作为其他getters方法的参数
 getters: {
  doneTodosCount: (state, getters) => {
    return getters.doneTodos.length
  }
}
  • 同state一样,页面使用getters方法返回的包装数据也分为两种情况
this.$store.getters.doneTodosCount
computed: {
  // 使用对象展开运算符将 getter 混入 computed 对象中
    ...mapGetters([
      'doneTodosCount',
      'anotherGetter',
      // ...
    ])
	//或对象
	...mapGetters({
	  // 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
	  doneCount: 'doneTodosCount'
	})
  }

3、mutations

  • 调用 this.$store.commit(‘increment’, 参数)
  • 必须是同步函数
import { mapMutations } from 'vuex'

export default {
  // 这里仅仅是把方法注册进来,相当于只定义了方法,在合适的时机再去调用this.add(参数),并且传参
  methods: {
    ...mapMutations([
      'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`

      // `mapMutations` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
    ]),
    ...mapMutations({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
    })
  }
}

4、action

  • Action 类似于 mutation,操作数据
  • Action 提交的是 mutation,而不是直接变更状态
  • Action 可以包含任意异步操作

actions

  • 普通使用:this.$store.dispatch(‘xxx’,data)
import { mapActions } from 'vuex'

export default {
  // 这里仅仅是把方法注册进来,相当于只定义了方法,在合适的时机再去调用this.add(参数),并且传参
  methods: {
    ...mapActions([
      'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
      // `mapActions` 也支持载荷:
      'incrementBy' // 将 `this.incrementBy(amount)` 映射为`this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
      add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
    })
  }
}

modules

项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
 }
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
 }

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
   }
})

computed和watch

	<h2>计算属性和侦听器</h2>
    <input type="text" class="flex_l mt-20" v-model="name1" />
    <input type="text" class="flex_l mt-20" v-model="name2" />
    <input type="text" class="flex_l mt-20" v-model="name" />
    
  data() {
    return {
      name1: "",
      name2: "",
      // name: ""  //computed的变量不能再data中定义,但watch需要定义
    };
  },
  computed: {
    name: {
      get() {  //name1和name2变化引起name变化
        return this.name1 + "--" + this.name2;  //或者可以只有get
      },
      set(newV) { //name变化,设置name1和name2的值
        this.name1 = newV.split("--")[0];
        this.name2 = newV.split("--")[1];
        this.tip();
      }
    }
  },
   watch: {
    name1(newV) {
      if (!newV || !this.name2) {
        this.tip();
      } else {
        this.name = this.name1 + this.name2;
      }
    }
  },

	tip() {
      this.$toast("还有一个没填哦");
    },

1、watch 选项允许我们执行异步操作 (访问一个 API),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的
2、计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。

双向数据绑定的原理

通过Object.defineProperty()来劫持各个属性的setter,getter。在数据变动时发布消息给订阅者,触发响应的监听回调
而view 发生改变则是通过底层的input 事件来进行data的响应更改

<script>
  let p = document.querySelector('p');
  let inp = document.querySelector('input');
  let obj = {
    msg: 'hello'
  }
 
  p.innerText = obj.msg
  inp.value = obj.msg
 
  //实现视图变化数据跟着变化
  inp.oninput = function () {
    obj.msg = inp.value
    // console.log(obj.msg);
  }
 
  //实现数据变化视图跟着变化
  let temp = obj.msg
  Object.defineProperty(obj, 'msg', {
   // get() {//get方法会劫持msg这个属性的获取操作
   //   return temp
   // },
    set(value) {//set方法会劫持msg这个属性的设置操作
      p.innerText = value
      inp.value = value
    }
  })
 
</script>

MVC、MVP、MVVM架构

1、MVC

所有通信都是单向的

image-20210315165515025
  • 一种是View 传送指令到 Controller,另一种是直接通过controller接受指令
  • Controller(业务逻辑层) 完成业务逻辑后,要求 Model 改变状态
  • Model 将新的数据发送到 View,用户得到反馈

2、MVP

各部分之间的通信,都是双向的

image-20210315165921554

3、MVVM

采用双向绑定:View的变动,自动反映在 ViewModel,反之亦然

image-20210315170017749
  • MVVM 是 Model-View-ViewModel 的缩写。
  • Model代表数据模型,View 代表视图层,ViewModel 连接Model和View,使View 和 Model保持同步
  • 在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互。而View 和 Model 之间的同步工作完全是自动的。Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。

Vue的生命周期

Vue 实例从创建到销毁的过程,就是生命周期。

  1. beforeCreate(创建前): 实例已初始化,还没有初始化好 data 和 methods 属性,因此无法访问methods, data, computed等上的方法和数据。
  2. created ( 创建后 ):vue实例已经创建完成。data 和 methods 已经创建OK,此时还没有开始 编译模板。
  3. beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。已经完成了模板的编译,但是还没有挂载到页面中
  4. mounted(载入后) 已经将编译好的模板,挂载到了页面指定的容器中显示,DOM 渲染完成。
  5. beforeUpdate(更新前) 状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
  6. updated(更新后) 实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
  7. beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
  8. destroyed(销毁后)Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

Vue组件间的参数传递

1.父子组件传值

父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件:$emit方法传递参数

2.兄弟组件传值

eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。

Vue.prototype.$eventBus = new Vue();
this.$eventBus.$emit("handler","我是传递的值")
this.$eventBus.$on("handle",(value)=>{
    this.value = value;
})

3、父给子孙传值

provide:{  //父组件传递
   username:"Hehe"
}
inject:["username"]  //子组件接收

4、vuex

$route 和 $router 的区别

答: r o u t e r 是 V u e R o u t e r 的 实 例 , 在 s c r i p t 标 签 中 想 要 导 航 到 不 同 的 U R L , 使 用 router是VueRouter的实例,在script标签中想要导航到不同的URL,使用 routerVueRouterscriptURL,使router.push方法。返回上一个历史history用$router.go(-1)
$route为当前router跳转对象。里面可以获取当前路由的name,path,query,parmas等。

Vue与React的区别

相同点

  1. 都使用了虚拟Dom

    Virtual DOM是一个映射真实DOM的JavaScript对象,如果需要改变任何元素的状态,那么是先在Virtual DOM上进行改变,而不是直接改变真实的DOM。当有变化产生时,一个新的Virtual DOM对象会被创建并计算新旧Virtual DOM之间的差别。之后这些差别会应用在真实的DOM上。

  2. 组件化

    React与Vue都鼓励组件化应用。这本质上说,是建议你将你的应用分拆成一个个功能明确的模块,每个模块之间可以通过合适的方式互相联系。

  3. 快速构建工具

    React和Vue都有自己的构建工具,你可以使用它快速搭建开发环境。React可以使用Create React App,而Vue对应的则是vue-cli。

不同点:

  1. JSX和模板

    React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue模板编写

  2. 状态管理vuex vs redux

  3. vue语法更简单一些

九、vue-cli如何新增自定义指令?

1.创建局部指令

var app = new Vue({
el: ‘#app’,
data: {
},
// 创建指令(可以多个)
directives: {
// 指令名称
dir1: {
inserted(el) {
// 指令中第一个参数是当前使用指令的DOM
console.log(el);
console.log(arguments);
// 对DOM进行操作
el.style.width = ‘200px’;
el.style.height = ‘200px’;
el.style.background = ‘#000’;
}
}
}
})
2.全局指令

Vue.directive(‘dir2’, {
inserted(el) {
console.log(el);
}
})
3.指令的使用

十、vue如何自定义一个过滤器?

html代码:

{{msg| capitalize }}

JS代码:

var vm=new Vue({
el:"#app",
data:{
msg:’’
},
filters: {
capitalize: function (value) {
if (!value) return ‘’
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
})

全局定义过滤器

Vue.filter(‘capitalize’, function (value) {
if (!value) return ‘’
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
过滤器接收表达式的值 (msg) 作为第一个参数。capitalize 过滤器将会收到 msg的值作为第一个参数。

十一、对keep-alive 的了解?

  • keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
  • keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。
  • 使用场景:
    1、表单页面跳转其他页面返回后所填内容清空,例如登录注册页面
    2、tab切换组件实例会重新生成,无法记录切换前所选中的状态

使用方法

参数解释 include - 字符串或正则表达式,只有名称匹配的组件会被缓存 exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存 include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。

使用示例

十二、一句话就能回答的面试题

1.css只在当前组件起作用

答:在style标签中写入scoped即可 例如:

2.v-if 和 v-show 区别
答:v-if按照条件是否渲染,v-show是display的block或none;

3. r o u t e 和 route和 routerouter的区别
答: r o u t e 是 “ 路 由 信 息 对 象 ” , 包 括 p a t h , p a r a m s , h a s h , q u e r y , f u l l P a t h , m a t c h e d , n a m e 等 路 由 信 息 参 数 。 而 route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而 routepathparamshashqueryfullPathmatchednamerouter是“路由实例”对象包括了路由的跳转方法,钩子函数等。

4.vue.js的两个核心是什么?

答:数据驱动、组件系统

5.vue几种常用的指令

答:v-for 、 v-if 、v-bind、v-on、v-show、v-else

6.vue常用的修饰符?

答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用

7.v-on 可以绑定多个方法吗?
答:可以

8.vue中 key 值的作用?
答:当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。

10.vue等单页面应用及其优缺点

答:优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。
缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

11.怎么定义 vue-router 的动态路由? 怎么获取传过来的值

答:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。

1.页面中定义一个定时器,在哪个阶段清除?
答案:在 beforeDestroy 中销毁定时器。
① 为什么销毁它:
在页面 a 中写了一个定时器,比如每隔一秒钟打印一次 1,当我点击按钮进入页面 b 的时候,会发现定时器依然在执行,这是非常消耗性能的。
② 解决方案 1mounted(){
 this.timer = setInterval(()=>{
    console.log(1)
 },1000)
},
beforeDestroy(){
 clearInterval(this.timer)
}
方案 1 有两点不好的地方,引用尤大的话来说就是:
它需要在这个组件实例中保存这个 timer,如果可以的话最好只有生命周期钩子可以访问到它。这并不算严重的问题,但是它可以被视为杂物。
我们的建立代码独立于我们的清理代码,这使得我们比较难于程序化的清理我们建立的所有东西。
方案 2(推荐):该方法是通过$once 这个事件侦听器在定义完定时器之后的位置来清除定时器
mounted(){
 const timer = setInterval(()=>{
    console.log(1)
 },1000)
 this.$once('hook:beforeDestroy',()=>{
  clearInterval(timer)
 })
}
官网参考链接:https://cn.vuejs.org/v2/guide/components-edge-cases.html2.父组件如何获取子组件的数据,子组件如何获取父组件的数据,父子组件如何传值?
① 先说,父组件如何主动获取子组件的数据?
方案 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>
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 都应该被视为只读.
现在,自定义一个指令,添加一些样式,表示定位的距离
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>
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 的写法
<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()方法是不会触发的。
二、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 依赖于这两个值,所以便会自动改变。
7、导航钩子有哪几种,分别如何用,如何将数据传入下一个点击的路由页面?
① 全局导航守卫
前置守卫
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.$route
this.$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 等
8、es6 的特有的类型, 常用的操作数组的方法都有哪些?
es6 新增的主要的特性:
① let const 两者都有块级作用域
② 箭头函数
③ 模板字符串
④ 解构赋值
⑤ for of 循环
⑥ importexport 导入导出
⑦ set 数据结构
⑧ ...展开运算符
⑨ 修饰器 @
⑩ class 类继承
⑪ asyncawait
⑫ 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 来实现更新页面部分内容的操作:
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 错误
11、怎么在vue中点击别的区域输入框不会失去焦点?

答:阻止事件的默认行为

具体操作:监听你想点击后不会丢失 input 焦点的那个元素的 mousedown 事件,回调里面调用 event.preventDefault(),会阻止使当前焦点丢失这一默认行为。

12、vue中data的属性可以和methods中的方法同名吗?为什么?

答:不可以

因为,Vue会把methods和data的东西,全部代理到Vue生成的对象中,会产生覆盖所以最好不要同名

13、怎么给vue定义全局的方法?

Vue.prototype.方法名称

14、Vue 2.0 不再支持在 v-html 中使用过滤器怎么办?

解决方法:

①全局方法(推荐)

Vue.prototype.msg = function(msg){
  return msg.replace("\n""
"
} <div v-html="msg(content)"></div> ②computed方法 computed:{ content:function(msg){ return msg.replace("\n""
"
) } } <div>{{content}}</div> ③$options.filters(推荐) filters:{ msg:function(msg){ return msg.replace(/\n/g,"
"
) } },    data:{ content:"XXXX" } <div v-html="$options.filters.msg(content)"></div> 14、怎么解决vue打包后静态资源图片失效的问题? 答:将静态资源的存放位置放在src目录下 16、怎么解决vue动态设置img的src不生效的问题? <img class="logo" :src="logo" alt="公司logo"> data() { return { logo:require("./../assets/images/logo.png"), }; } 因为动态添加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()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 var 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.$data, this.$options.data())就可以将当前状态的data重置为初始状态。 20、vue怎么实现强制刷新组件? 答:① v-ifthis.$forceUpdate v-if 当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.$forceUpdate <button @click="reload()">刷新当前组件</button> methods: { reload() { this.$forceUpdate() } } 21、vue如何优化首页的加载速度? ① 第三方js库按CDN引入(一、cdn引入 二、去掉第三方库引入的import 三、把第三方库的js文件从打包文件里去掉) ② vue-router路由懒加载 ③ 压缩图片资源 ④ 静态文件本地缓存 http缓存:推荐网站:cnblogs.com/chinajava/p service worker离线缓存:,缺点:需要在HTTPS站点下,推荐:lzw.me/a/pwa-service-wo ⑤ 服务器端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算法吗? 推荐网站:cnblogs.com/wind-lanyan 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用于修改某些操作的默认行为,也可以理解为在目标对象之前架设一层拦截,外部所有的访问都必须先通过这层拦截,因此提供了一种机制,可以对外部的访问进行过滤和修改。 var 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对象上调用。 var object = { proxy: new Proxy(target, handler) }; Proxy对象也可以作为其它对象的原型对象。 var proxy = new Proxy({}, { get: function(target, property) { return 35; } }); let obj = Object.create(proxy); obj.time // 35 上面代码中,proxy对象是obj的原型对象,obj本身并没有time属性,所以根据原型链,会在proxy对象上读取属性,从而被拦截。 同一个拦截函数,可以设置多个操作。 var 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、怎么缓存当前的组件?缓存后怎么更新? <keep-alive> <router-view></router-view> </keep-alive> <!-- 这里是需要keepalive的 --> <keep-alive> <router-view v-if="$route.meta.keepAlive"></router-view> </keep-alive> <!-- 这里不会被keepalive --> <router-view v-if="!$route.meta.keepAlive"></router-view> { path: '', name: '', component: , meta: {keepAlive: true} // 这个是需要keepalive的 }, { path: '', name: '', component: , meta: {keepAlive: false} // 这是不会被keepalive的 } 如果缓存的组件想要清空数据或者执行初始化方法,在加载组件的时候调用activated钩子函数,如下: activated: function () { this.data = ''; } 26、axios怎么解决跨域的问题? 使用axios直接进行跨域访问不可行,我们需要配置代理 代理可以解决的原因: 因为客户端请求服务端的数据是存在跨域问题的,而服务器和服务器之间可以相互请求数据,是没有跨域的概念(如果服务器没有设置禁止跨域的权限问题),也就是说,我们可以配置一个代理的服务器可以请求另一个服务器中的数据,然后把请求出来的数据返回到我们的代理服务器中,代理服务器再返回数据给我们的客户端,这样我们就可以实现跨域访问数据 1.配置BaseUrl import axios from 'axios' Vue.prototype.$axios = axios axios.defaults.baseURL = '/api' //关键代码 2.配置代理 在config文件夹下的index.js文件中的proxyTable字段中,作如下处理: proxyTable: { '/api': { target:'http://api.douban.com/v2', // 你请求的第三方接口 changeOrigin:true, // 在本地会创建一个虚拟服务端,然后发送请求的数据,并同时接收请求的数据, //这样服务端和服务端进行数据的交互就不会有跨域问题 pathRewrite:{ // 路径重写, '^/api': '' // 替换target中的请求地址,也就是说以后你在请求http://api.douban.com/v2/XXXXX //这个地址的时候直接写成/api即可。 } } } 1. 在具体使用axios的地方,修改url如下即可 axios.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是api.douban.com/v2/movie。 至此,纯前端配置代理解决axios跨域得到解决 27、怎么实现路由懒加载呢? 第一种(最常用): const Foo = () => import('./Foo.vue') const router = new VueRouter({ routes: [ { path: '/foo', component: Foo } ] }) 第二种: const router = new Router({ routes: [ { path: '/index', component: (resolve) => { require(['../components/index'], resolve) // 这里是你的模块 不用import去引入了 } } ] }) 第三种(官方推荐): // 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组件中获取我们的全部路由信息,并且渲染我们的左侧菜单栏,让动态路由实现。 二、实现 ① 初始化公共路由 //只显示主要代码 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数组的结构 router.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() }) ③ 动态加载路由 import 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 } ④ 渲染菜单 <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中就可以了。 export 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 方法: 注意: 这个功能只在 HTML5 history 模式下可用。 const router = new VueRouter({ routes: [...], scrollBehavior (to, from, savedPosition) { // return 期望滚动到哪个的位置 } }) scrollBehavior 方法接收 to 和 from 路由对象。第三个参数 savedPosition 当且仅当 popstate 导航 (通过浏览器的 前进/后退 按钮触发) 时才可用。 scrollBehavior (to, from, savedPosition) { return { x: 0, y: 0 } } 对于所有路由导航,简单地让页面滚动到顶部。 返回 savedPosition,在按下 后退/前进 按钮时,在滚动条位置,就会像浏览器的原生表现那样: scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } } 模拟『滚动到锚点』的行为 scrollBehavior (to, from, savedPosition) { if (to.hash) { return { selector: to.hash } } } 还可以利用路由元信息更细颗粒度地控制滚动。 routes: [ { 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写这个 router.afterEach((to,from,next) => { window.scrollTo(0,0); }); 30、vue-router如何响应路由参数的变化? 当使用路由参数时,比如: {path:/list/:id’component:Foo}/list/aside导航到 /list/foo,原来的组件实例会被复用。 因为两个路由都渲染同个组件Foo,比起销毁再创建,复用则更加高效。 不过,这也意味着组件的生命周期钩子不会再被调用。 如果跳转到相同的路由还会报以下错误 这个时候我们需要重写push方法,在src/router/index.js 里面import VueRouter from 'vue-router'下面写入下面方法即可 const routerPush = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return routerPush.call(this, location).catch(error=> error) } 如何响应不同的数据呢? ① 复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch (监测变化) $route 对象: const User = { template: '...', watch: { '$route' (to, from) { // 对路由变化作出响应... } } } ② 使用beforeRouteUpdate const 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深层监听 watch:{ obj:{ handler(newValue,oldValue){ console.log('obj changed') }, deep: true,//深度遍历 immediate: true //默认第一次绑定的时候不会触发watch监听,值为true时可以在最初绑定的时候执行 } } 方法② :指定key watch: { "dataobj.name": { handler(newValue, oldValue) { console.log("obj changed"); } } } 方法③:computed computed(){ ar(){ return this.obj.name } } 33、v-for循环时为什么要加key? key的作用主要是为了高效的更新虚拟DOM,是因为Virtual DOM 使用Diff算法实现的原因。 当某一层有很多相同的节点时,也就是列表节点时,Diff算法的更新过程默认情况下也是遵循以上原则。 比如一下这个情况 我们希望可以在BC之间加一个F,Diff算法默认执行起来是这样的: 即把C更新成FD更新成CE更新成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, 也就是把HTMLCSS全都写进JavaScript了,'all in js'; Vue推荐的做法是webpack+vue-loader的单文件组件格式,即html,css,jd写在同一个文件; ④ 数据绑定: vue实现了数据的双向绑定,react数据流动是单向的 ⑤ state对象在react应用中不可变的,需要使用setState方法更新状态;在vue中,state对象不是必须的,数据由data属性在vue对象中管理

你可能感兴趣的:(vue,面试)