1. 为什么选择Vue?

  1. Vue的核心是数据驱动视图,是渐进式、轻量级且非常容易上手的框架。

  2. 它是渐进式框架(可以帮助开发者更快更容易地构建和维护复杂的Web应用程序)核心思想是将Web应用程序分解成一系列可重用的模块,这些模块可以被多个应用程序共享,从而提高开发效率。支持组件化,虚拟DOM

  3. 它借助了MVVM设计模式思想,做双向数据绑定,有一套自己的数据响应式的系统。

2. Vue为什么不兼容IE8以下

  1. Vue的核心双向绑定以及响应式原理都是基于object.defineProperty,而object.definePropertyIE8及以下版本都不支持。

  2. Vue也需要Promise的支持,而IE8也并不支持。

3. 如何理解Vue单向数据流

简单来说:就是父组件通过props传递数据给子组件,所形成的一个单项的数据流。

  1. 为什么是单项的? 是由父组件传给自子组件的

    • 因为如果子组件更新了props数据,会引起父组件以及与引用父组件数据的相关的组件数据的改变,页面也会随之而变,这就造成了数据严重的混乱以及不可控。

    • 同时也是为了组件间更好的解耦

  2. 子组件如何更新父组件的值

    1. 通过emit自定义事件进行传值。

    2. 父组件可以传递一个方法给子组件

    3. 通过$parent方法来更改父组件的值

    4. vuex共享仓库

4. vue中的key为什么不建议用index作为值

Vue中的Key是虚拟DOM的标识,当使用v-for渲染一个列表,当渲染原数据发生改变时,Vue会根据你的新数据生成最新的虚拟DOM,随后将新生成的虚拟DOM与旧的虚拟DOM进行对比,根据Key值看哪些数据可以复用,哪些不行需要生成新的。

  1. 虚拟DOM的对比规则(diff运算)

    • 旧的虚拟dom找到新的虚拟dom根据相同key值进行对比

      • 若两者虚拟dom内容相同,则直接复用之前的真实dom

      • 若不同,则生成新的真实dom,随后用以替换掉之前的真实dom元素

    • 旧的虚拟dom未找到新的虚拟dom

      • 则直接生成新的真实dom渲染到页面上

  2. 用index作为key可能引发的问题

    1. 若对数据进行逆序添加,逆序删除等破环了index作为key值原本顺序的操作

      • 会导致不能复用之前的真实dom,需要新生成。效率低下

    2. 若结构中还包含输入类的dom元素

      • 会导致错误的dom更新,造成界面混乱。

  3. 开发中如何选择key值得数据

    1. 最好时用每条数据得唯一标识符,如id,学号,手机号,身份证号。

    2. 当不需要对数据进行逆序得添加以及删除得改动,等破坏顺序得操作,可以使用index值

5. Vue组件间通信

整理vue中8种常规的通信方案

  1. 通过 props 传递

  2. 通过 $emit 触发自定义事件

  3. 使用 ref

  4. EventBus

  5. $parent或 $root

  6. attrs 与 listeners

  7. Provide 与 Inject

  8. Vuex

5.1 父子组件之间通信

  • Props与$emit

    1. 父页面中,在子组件child的声明的标签上,声明属性并进行传递,子组件通过props来接收

    // 在父组件中,定义num变量,并通过标签变量n传递给子组件
    <child :n="num"> </child>
    1. 子组件接收父页面的值通过props有两种接收数据模式

      • 可以通过数组的方式,默认不对传递来的数据做任何处理以及校验:props:['n']

      • 可以通过对象的方法,对父组件传递过来的数据进行验证。

        // child.vue  子页面
        // 1. 基础类型检查,检查n是否为数字类型,若不是,则控制台会报错
        props: {
            n: Number,
        
        // 2. 多个类型检查
         // 带有默认值的数字
            propD: {
              type: Number,
              default: 100
            },
        
        // 自定义验证函数
            propF: {
              validator: function (value) {
                // 这个值必须匹配下列字符串中的一个
                return ['success', 'warning', 'danger'].indexOf(value) !== -1
              }
            }
        },
        
    2. 子组件通过$emit(事件名称,传递给父组件的数据)事件传递数据给父组件进行修改。

      // child 子页面
      methods:{
        changeNum(){
          this.$emit('numChange', '12')
        }
      }
      // father 父页面  在页面中定义方法handleChange,接收子组件numChange事件的数据。
      <child :n="num" @numChange="handleChange"> </child>
      
      methods:{
        handleChange(num){
          console.log(num)  //12
        }
      }
  • Ref

    • 父修改子组件的值:在子组件上定义Ref,父组件可直接修改子组件中data的值,或者调用子组件中的方法,传值给子组件.

      <child :n="num" @numChange="changFatherNum" ref="childRef">  </child>
      
       methods: {
          changFatherNum(nn) {
             this.num = nn
             this.$refs.childRef.childN = 458
             // 调用子组件的方法,并传值给子组件
             this.$refs.childRef.changeSonNum('我传给你值')
          },  
      },
  • $parent:获得父组件实例上的所有属性以及方法。

    • 可用于子组件修改父组件上的值。

      1. 子组件调用this.$parent.属性或方法进行修改。

    this.$parent.grandPa = '我修改你的值了,笨B'
    
    • 也可用于兄弟组件相互传值,将父组件当作跳转的平台eventBus。

      兄弟组件

      this.$parent.$emit('getInputValue', this.inputValue)

      另一个兄弟组件

      this.$parent.$on('getInputValue',this.getBrotherValue)

  • children:获得当前实例的直接子组件。需要注意 children 并不保证顺序,也不是响应式的。


  1. 子组件修改父组件的值,父组件可以将方法直接传递给子组件,子组件调用此方法传值,如:

    1. 父组件:

    <template>
      <div>
        //传递给子组件自己的方法
        <child :receive="receive"></child>
      </div>
    </template>
    
    export default {
      name:'father',
      
      methods: {
        receive(data){
          console.log(data);
        }
      },
    
    
    1. 子组件

    <template>
      <div>
        <input type="text" v-model="info" @keyup.enter="add"> 传值给父组件 </input>
      </div>
    </template>
    
    export default {
      name:'child',
      data(){
        return{
          info:''
        }
      }
      props:['receive']
      methods: {
        add(){
          //调用父组件传来的方法,修改父组件的值
          this.receive(this.info)
        }
      },
    

5.2 兄弟组件进行传值

  1. 使用EventBus,当作兄弟组件传值的一个中转站,因为Vue不能直接兄弟组件之间相互传值,需要一个媒介。

    1. 定义一个eventbus.js文件

    import vue from 'Vue'
    // 默认导出
    const eventBus = new Vue()
    
    export default eventBus
    
    1. 兄弟组件导入eventbus这个js文件,并在触发事件后通过bus.$emit(事件名字,数据)上传自定义事件。

    2. 另一个兄弟组件同样需要导入bus文件,并可以在mounted方法中,调用bus.$on(事件名字,回调函数)

    <template>
      <div>
        <h2>{{uncleValue}}</h2>
      </div>
    </template>
    
    <script>
    import bus from './eventBus'
    export default {
      name:'uncle',
      data() {
        return {
          uncleValue: ''
        }
      },
      mounted() {
        //接收兄弟组件的事件getInputValue,并且调用当前页面的getBrotherValue方法进行接收
        bus.$on('getInputValue',this.getBrotherValue)
      },
      methods: {
        getBrotherValue(data){
          console.log(data);
          this.uncleValue = data
        }
      },
      // 当组件被销毁时,调用off销毁自定义事件
      beforedestory(){
        bus.$off('getInputValue')
      }
    }
    </script>
    
    • 注意:

      • 组件被销毁时,对应自定义的事件名(bus.$emit())事件,也需要销毁

      • 在beforeDestroy生命周期组件中调用:this.xxx.$off('getInputValue')

5.3 祖先组件与后代组件的通信

  1. Provide与inject

    • Provide:可以是一个对象或返回一个对象的函数。该对象包含可注入其子孙的property

      data() {
          return {
            grandPa: '我是你们的爹'
          }
        },
        // Provide对象函数形式:
        provide(){
          return{
            foo: this.grandPa
          }
        },
        // Provide对象形式,静态的:
        provide:{
          foo: 'bar'
        }
    • Inject:可以是一个字符串数组,也可以是一个对象。用以接收祖先组件定义的provide里面的属性。子组件可通过this.foo关键字访问到

      inject:['foo'],
      
      mounted() {
          console.log(this.foo);
        },
    • 注意事项:

      • provideinject 绑定并不是可响应的。如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。

      • 子孙组件不可更改provide传给你们的值。

      • 就是你传入数据给provide的方式不同,会影响到其在子组件中数据是否是可响应式的。

      对象的形式,以及computed形式传入给provide是可响应式的。直接传值不可响应就不会触发页面的更新。

      // 祖先组件中的数据
      data() {
        return {
          info: {
            grandma: '我是你men妈'   // 在子组件是可响应式的,grandma的改变,其在子组件中也会实时响应
          },
          grandPa: '我是你们的爹' // 在子组件中不是可响应式的,这里改变不会影响到子组件
        }
      },
      provide(){
        return{
          foo: this.grandPa,
          parent:this.info
        }
      },
      
      //  后代子孙组件
      <h2>grandPa:{{foo}}</h2>
      <h2>{{parent.grandma}}</h2>
      
      inject:['foo','parent'],
      

6. Vue生命周期函数 详解过程

6.1 Vue生命周期

与其说是Vue生命周期,不如说其内组件的生命周期,就是一个组件从创建开始 经历了数据初始化挂载更新等步骤后,最后被销毁

Vue生命周期分为三个阶段,挂载阶段,更新阶段,销毁阶段。

6.3 Vue生命周期执行顺序

生命周期.png挂载阶段

  1. beforeCreate(创建前):

    • 初始化了生命周期函数,以及v-pre、v-once指令

    • 但数据监测(getter和setter)数据代理和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,实例上的配置项还未开始,也就是说不能访问到data、computed、watch、methods上的方法和数据。

  2. Created(创建后)

    • 初始化了数据代理以数据监测,实例上new Vue({ options })配置的 options 包括 data、computed、watch、methods 等函数都配置完成。

    • 但是此时渲染的节点还未挂载到 DOM,所以不能访问到 $el属性。

  3. beforeMount(挂载前)

    • Vue开始解析模板,将你的模板生成虚拟DOM并存储在内存当中。

    • 但其还没有生成真实DOM,所以在这个阶段所进行的一切操作DOM元素都无效,因为当挂载在页面上时,是将内存中的虚拟DOM转化为真实DOM(但Vue最好不要自己操作dom元素)

  4. Mounted(挂载后):

    • 将内存中的虚拟DOM转化为真实DOM并挂载到页面上去,Vue通过vm.$el拷贝了真实DOM,以便后面数据更新可以对DOM节点进行复用

    • 但注意在这个钩子函数尽量避免自己操作DOM元素,在此阶段可以进行发送网络请求,开启定时器,绑定自定义事件等。


更新阶段

  1. beforeUpdate(更新前)

    • 当data中的数据→响应式数据发生改变的时候调用,响应式数据更新完成,但是

    • 但页面还是旧的,并没有发生改变。(页面和数据并没有保持同步)页面更新是异步的

  2. Updated(更新后):

    • 根据新数据 生成新的虚拟DOM,其与旧的虚拟DOM进行比较,看旧的DOM元素有没有可以复用的,随后对比完成,生成最新的真实DOM并挂载到页面上。


销毁阶段

  1. beforeDestroy(销毁之前):

    • 组件被销毁之前调用,这一步实例里面的data,methods仍然完全可用,this 仍能获取到实例,但不建议在此阶段操作数据,因为此时所有对数据的修改,并不会再触发更新了

    • 在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件。

  2. destroyed(销毁后)

    • 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器、自定义事件会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。

    • 但绑定的点击事件,鼠标事件等原生js事件的方法任然可以调用,因为vue帮你绑定事件也是通过操作底层的js事件方法,销毁时并不会销毁这些方法。

7. 父子组件生命周期执行顺序

  1. 加载渲染阶段

    beforeCreated → 父Created → 父beforeMountbeforeCreate → 子create →子beforeMount → 子mounted

    注意事项:子组件挂载完成之后,父组件还未挂载完成,所以子组件不能够拿到在父组件mounted方法中获取的数据。

  2. 更新阶段

    • beforeUpdate → 子beforeUpdate → 子updated → 父updated

  3. 销毁阶段

    • beforeDestory → 子beforeDestory → 子destoryed → 父destoryed