19道高频vue面试题,顺便写一下自己的答案

Vue路由hash模式和history模式

1. hash模式

早期的前端路由的实现就是基于 location.hash 来实现的。其实现原理很简单,location.hash 的值就是 URL# 后面的内容。比如下面这个网站,它的 location.hash 的值为 '#search'

https://interview2.poetries.top#search

hash 路由模式的实现主要是基于下面几个特性

  • URLhash 值只是客户端的一种状态,也就是说当向服务器端发出请求时,hash 部分不会被发送;
  • hash 值的改变,都会在浏览器的访问历史中增加一个记录。因此我们能通过浏览器的回退、前进按钮控制 hash 的切换;
  • 可以通过 a 标签,并设置 href 属性,当用户点击这个标签后,URLhash 值会发生改变;或者使用 JavaScript 来对 loaction.hash 进行赋值,改变 URLhash 值;
  • 我们可以使用 hashchange 事件来监听 hash 值的变化,从而对页面进行跳转(渲染)
window.addEventListener("hashchange", funcRef, false);

每一次改变 hashwindow.location.hash),都会在浏览器的访问历史中增加一个记录利用 hash 的以上特点,就可以来实现前端路由“更新视图但不重新请求页面”的功能了

特点 :兼容性好但是不美观

2. history模式

history采用HTML5的新特性;且提供了两个新方法: pushState()replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更

window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);

这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。

history 路由模式的实现主要基于存在下面几个特性:

  • pushStaterepalceState 两个 API 来操作实现 URL 的变化 ;
  • 我们可以使用 popstate 事件来监听 url 的变化,从而对页面进行跳转(渲染);
  • history.pushState()history.replaceState() 不会触发 popstate 事件,这时我们需要手动触发页面跳转(渲染)。

特点 :虽然美观,但是刷新会出现 404 需要后端进行配置

Vue.set的实现原理

  • 给对应和数组本身都增加了dep属性
  • 当给对象新增不存在的属性则触发对象依赖的watcher去更新
  • 当修改数组索引时,我们调用数组本身的splice去更新数组(数组的响应式原理就是重新了splice等方法,调用splice就会触发视图更新)

基本使用

以下方法调用会改变原始数组:push(), pop(), shift(), unshift(), splice(), sort(), reverse(),Vue.set( target, key, value )

  • 调用方法:Vue.set(target, key, value )
    • target:要更改的数据源(可以是对象或者数组)
    • key:要更改的具体数据
    • value :重新赋的值
<div id="app">{
  {user.name}} {
  {user.age}}</div>
<div id="app"></div>
<script>
    // 1. 依赖收集的特点:给每个属性都增加一个dep属性,dep属性会进行收集,收集的是watcher
    // 2. vue会给每个对象也增加一个dep属性
    const vm = new Vue({
     
        el: '#app',
        data: {
      // vm._data  
            user: {
     name:'poetry'}
        }
    });
    // 对象的话:调用defineReactive在user对象上定义一个age属性,增加到响应式数据中,触发对象本身的watcher,ob.dep.notify()更新 
    // 如果是数组 通过调用 splice方法,触发视图更新
    vm.$set(vm.user, 'age', 20); // 不能给根属性添加,因为给根添加属性 性能消耗太大,需要做很多处理

    // 修改肯定是同步的 -> 更新都是一步的  queuewatcher
</script>

相关源码

// src/core/observer/index.js 44
export class Observer {
    // new Observer(value)
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
   
    this.value = value
    this.dep = new Dep() // 给所有对象类型增加dep属性
  }
}
// src/core/observer/index.js 201
export function set (target: Array<any> | Object, key: any, val: any): any {
   
  // 1.是开发环境 target 没定义或者是基础类型则报错
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
   
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${
     (target: any)}`)
  }
  // 2.如果是数组 Vue.set(array,1,100); 调用我们重写的splice方法 (这样可以更新视图)
  if (Array.isArray(target) && isValidArrayIndex(key)) {
   
    target.length = Math.max(target.length, key)
    // 利用数组的splice变异方法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // 3.如果是对象本身的属性,则直接添加即可
  if (key in target && !(key in Object.prototype)) {
   
    target[key] = val // 直接修改属性值  
    return val
  }
  // 4.如果是Vue实例 或 根数据data时 报错,(更新_data 无意义)
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
   
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  // 5.如果不是响应式的也不需要将其定义成响应式属性
  if (!ob) {
   
    target[key] = val
    return val
  }
  // 6.将属性定义成响应式的
  defineReactive(ob.value, key, val)
  // 通知视图更新
  ob.dep.notify()
  return val
}

我们阅读以上源码可知,vm.$set 的实现原理是:

  • 如果目标是数组 ,直接使用数组的 splice 方法触发相应式;
  • 如果目标是对象 ,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式处理,则是通过调用 defineReactive 方法进行响应式处理( defineReactive 方法就是 Vue 在初始化对象时,给对象属性采用 Object.defineProperty 动态添加 gettersetter 的功能所调用的方法)

前端vue面试题详细解答

Vue组件之间通信方式有哪些

Vue 组件间通信是面试常考的知识点之一,这题有点类似于开放题,你回答出越多方法当然越加分,表明你对 Vue 掌握的越熟练。 Vue 组件间通信只要指以下 3 类通信父子组件通信隔代组件通信兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信

组件传参的各种方式

组件通信常用方式有以下几种

  • props / $emit 适用 父子组件通信
    • 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过$emit 触发事件来做到的
  • ref$parent / $children(vue3废弃) 适用 父子组件通信
    • ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    • $parent / $children:访问访问父组件的属性或方法 / 访问子组件的属性或方法
  • EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
    • 这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件
  • $attrs / $listeners(vue3废弃) 适用于 隔代组件通信
    • $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( classstyle 除外 )。当一个组件没有声明任何 prop时,这里会包含所有父作用域的绑定 ( classstyle 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用
    • $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
  • provide / inject 适用于 隔代组件通信
    • 祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题, 不过它的使用场景,主要是子组件获取上级组件的状态 ,跨级组件间建立了一种主动提供与依赖注入的关系
  • $root 适用于 隔代组件通信 访问根组件中的属性或方法,是根组件,不是父组件。$root只对根组件有用
  • Vuex 适用于 父子、隔代、兄弟组件通信
    • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )
    • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

根据组件之间关系讨论组件通信最为清晰有效

  • 父子组件:props/$emit/$parent/ref
  • 兄弟组件:$parent/eventbus/vuex
  • 跨层级关系:eventbus/vuex/provide+inject/$attrs + $listeners/$root

下面演示组件之间通讯三种情况: 父传子、子传父、兄弟组件之间的通讯

1. 父子组件通信

使用props,父组件可以使用props向子组件传递数据。

父组件vue模板father.vue:

<template>
  <child :msg="message"></child>
</template>

<script>
import child from './child.vue';
export default {
     
  components: {
     
    child
  },
  data () {
     
    return {
     
      message: 'father message';
    }
  }
}
</script>

子组件vue模板child.vue:

<template>
    <div>{
  {msg}}</div>
</template>

<script>
export default {
     
  props: {
     
    msg: {
     
      type: String,
      required: true
    }
  }
}
</script>

回调函数(callBack)

父传子:将父组件里定义的method作为props传入子组件

// 父组件Parent.vue:
<Child :changeMsgFn="changeMessage">
methods: {
   
    changeMessage(){
   
        this.message = 'test'
    }
}
// 子组件Child.vue:
<button @click="changeMsgFn">
props:['changeMsgFn']

子组件向父组件通信

父组件向子组件传递事件方法,子组件通过$emit触发事件,回调给父组件

父组件vue模板father.vue:

<template>
    <child @msgFunc="func"></child>
</template>

<script>
import child from './child.vue';
export default {
     
    components: {
     
        child
    },
    methods: {
     
        func (msg) {
     
            console.log(msg);
        }
    }
}
</script>

子组件vue模板child.vue:

<template>
    <button @click="handleClick">点我</button>
</template>

<script>
export default {
     
    props: {
     
        msg: {
     
            type: String,
            required: true
        }
    },
    methods () {
     
        handleClick () {
     
          //........
          this.$emit('msgFunc');
        }
    }
}
</script>

2. provide / inject 跨级访问祖先组件的数据

父组件通过使用provide(){return{}}提供需要传递的数据

export default {
   
  data() {
   
    return {
   
      title: '我是父组件',
      name: 'poetry'
    }
  },
  methods: {
   
    say() {
   
      alert(1)
    }
  },
  // provide属性 能够为后面的后代组件/嵌套的组件提供所需要的变量和方法
  provide() {
   
    return {
   
      message: '我是祖先组件提供的数据',
      name: this.name, // 传递属性
      say: this.say
    }
  }
}

子组件通过使用inject:[“参数1”,”参数2”,…]接收父组件传递的参数

<template>
  <p>曾孙组件</p>
  <p>{
  {message}}</p>
</template>
<script>
export default {
     
  // inject 注入/接收祖先组件传递的所需要的数据即可 
  //接收到的数据 变量 跟data里面的变量一样 可以直接绑定到页面 {
     {}}
  inject: [ "message","say"],
  mounted() {
     
    this.say();
  },
};
</script>

3. $parent + $children 获取父组件实例和子组件实例的集合

  • this.$parent 可以直接访问该组件的父实例或组件
  • 父组件也可以通过 this.$children 访问它所有的子组件;需要注意 $children 并不保证顺序,也不是响应式的
<!-- parent.vue -->
<template>
<div>
  <child1></child1>   
  <child2></child2> 
  <button @click="clickChild">$children方式获取子组件值</button>
</div>
</template>
<script>
import child1 from './child1'
import child2 from './child2'
export default {
     
  data(){
     
    return {
     
      total: 108
    }
  },
  components: {
     
    child1,
    child2  
  },
  methods: {
     
    funa(e){
     
      console.log("index",e)
    },
    clickChild(){
     
      console.log(this.$children[0].msg);
      console.log(this.$children[1].msg);
    }
  }
}
</script>
<!-- child1.vue -->
<template>
  <div>
    <button @click="parentClick">点击访问父组件</button>
  </div>
</template>
<script>
export default {
     
  data(){
     
    return {
     
      msg:"child1"
    }
  },
  methods: {
     
    // 访问父组件数据
    parentClick(){
     
      this.$parent.funa("xx")
      console.log(this.$parent.total);
    }
  }
}
</script>
<!-- child2.vue -->
<template>
  <div>
    child2
  </div>
</template>
<script>
export default {
     
  data(){
     
    return {
     
     msg: 'child2'
    }
  }
}
</script>

4. $attrs + $listeners多级组件通信

$attrs 包含了从父组件传过来的所有props属性

// 父组件Parent.vue:
<Child :name="name" :age="age"/>

// 子组件Child.vue:
<GrandChild v-bind="$attrs" />

// 孙子组件GrandChild
<p>姓名:{
   {
   $attrs.name}}</p>
<p>年龄:{
   {
   $attrs.age}}</p>

$listeners包含了父组件监听的所有事件

// 父组件Parent.vue:
<Child :name="name" :age="age" @changeNameFn="changeName"/>

// 子组件Child.vue:
<button @click="$listeners.changeNameFn"></button>

5. ref 父子组件通信

// 父组件Parent.vue:
<Child ref="childComp"/>
<button @click="changeName"></button>
changeName(){
   
    console.log(this.$refs.childComp.age);
    this.$refs.childComp.changeAge()
}

// 子组件Child.vue:
data(){
   
    return{
   
        age:20
    }
},
methods(){
   
    changeAge(){
   
        this.age=15
  }
}

6. 非父子, 兄弟组件之间通信

vue2中废弃了broadcast广播和分发事件的方法。父子组件中可以用props$emit()。如何实现非父子组件间的通信,可以通过实例一个vue实例Bus作为媒介,要相互通信的兄弟组件之中,都引入Bus,然后通过分别调用Bus事件触发和监听来实现通信和参数传递。Bus.js可以是这样:

// Bus.js

// 创建一个中央时间总线类  
class Bus {
     
  constructor() {
     
    this.callbacks = {
   };   // 存放事件的名字  
  }  
  $on(name, fn) {
     
    this.callbacks[name] = this.callbacks[name] || [];  
    this.callbacks[name].push(fn);  
  }  
  $emit(name, args) {
     
    if (this.callbacks[name]) {
     
      this.callbacks[name].forEach((cb) => cb(args));  
    }  
  }  
}  

// main.js  
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上  
// 另一种方式  
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能  
<template>
    <button @click="toBus">子组件传给兄弟组件</button>
</template>

<script>
export default{
     
    methods: {
     
    toBus () {
     
      this.$bus.$emit('foo', '来自兄弟组件')
    }
  }
}
</script>

另一个组件也在钩子函数中监听on事件

export default {
   
  data() {
   
    return {
   
      message: ''
    }
  },
  mounted() {
   
    this.$bus.$on('foo', (msg) => {
   
      this.message = msg
    })
  }
}

7. $root 访问根组件中的属性或方法

  • 作用:访问根组件中的属性或方法
  • 注意:是根组件,不是父组件。$root只对根组件有用
var vm = new Vue({
   
  el: "#app",
  data() {
   
    return {
   
      rootInfo:"我是根元素的属性"
    }
  },
  methods: {
   
    alerts() {
   
      alert(111)
    }
  },
  components: {
   
    com1: {
   
      data() {
   
        return {
   
          info: "组件1"
        }
      },
      template: "<p>{
   { info }} <com2></com2></p>",
      components: {
   
        com2: {
   
          template: "<p>我是组件1的子组件</p>",
          created() {
   
            this.$root.alerts()// 根组件方法
            console.log(this.$root.rootInfo)// 我是根元素的属性
          }
        }
      }
    }
  }
});

8. vuex

  • 适用场景: 复杂关系的组件数据传递
  • Vuex作用相当于一个用来存储共享变量的容器

  • state用来存放共享变量的地方
  • getter,可以增加一个getter派生状态,(相当于store中的计算属性),用来获得共享变量的值
  • mutations用来存放修改state的方法。
  • actions也是用来存放修改state的方法,不过action是在mutations的基础上进行。常用来做一些异步操作

小结

  • 父子关系的组件数据传递选择 props$emit进行传递,也可选择ref
  • 兄弟关系的组件数据传递可选择$bus,其次可以选择$parent进行传递
  • 祖先与后代组件数据传递可选择attrslisteners或者 ProvideInject
  • 复杂关系的组件数据传递可以通过vuex存放共享的变量

Vue-router 路由模式有几种

vue-router3 种路由模式:hashhistoryabstract,对应的源码如下所示

switch (mode) {
   
    case 'history':
    this.history = new HTML5History(this, options.base)
    break
    case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
      break
    case 'abstract':
    this.history = new AbstractHistory(this, options.base)
      break
  default:
    if (process.env.NODE_ENV !== 'production') {
   
      assert(false, `invalid mode: ${
     mode}`)
    }
}

其中,3 种路由模式的说明如下:

  • hash: 使用 URL hash 值来作路由,支持所有浏览器
  • history : 依赖 HTML5 History API 和服务器配置
  • abstract : 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.

vue-router守卫

导航守卫 router.beforeEach 全局前置守卫

  • to: Route: 即将要进入的目标(路由对象)
  • from: Route: 当前导航正要离开的路由
  • next: Function: 一定要调用该方法来 resolve 这个钩子。(一定要用这个函数才能去到下一个路由,如果不用就拦截)
  • 执行效果依赖 next 方法的调用参数。
  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
  • next(false):取消进入路由,url地址重置为from路由地址(也就是将要离开的路由地址)
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值