vue基础知识

175 阅读13分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1、vue2生命周期?

1. 生命周期

Vue实例完整的生命周期,开始创建--> 初始化数据 --> 编译模板 --> 挂载DOM --> 渲染,更新 --> 渲染,卸载等一系列过程。

image.png

1. new Vue()
2. 初始化事件,生命周期
  • Vue构造函数下的initMixin()方法中,initEvents,initLifecycle,initRender

触发beforeCreate钩子函数 ,访问不到data,computed等的方法和数据

3. 初始化注入,校验
  • initMixin中initInjections,initProvide,initState(initData是observe响应式入口)
  • 双向数据绑定,data响应式变化在此完成

触发created钩子函数 ,实例配置上的options包括data,computed等配置完成,可以访问各种数据,获取接口数据等;但是渲染节点还未挂载到DOM,所以不能访问 $el属性

4. 判断有无el,没有的话直到el挂载;有的话判断有无template,没有的话,获取el中的outHTML;有的话转为render
  • 重写$mount,重新获取el,执行mountComponent()方法,进入方法,创建vm.$el
  • 将模板编译为render函数

触发beforeMount钩子函数

5. 创建vm.$el并替换el
  • 执行updateComponent方法
  • 首次执行,将 vm.$el真实DOM与Vnode比较,并存放在$el
  • vm._update(vm._render())Vnode转为真实DOM 并渲染到界面

触发mounted钩子函数,此时已经是真实DOM

6. 当data发生变化时,触发beforeUpadate钩子函数
7. 响应式数据更新,旧的Vnode与新的Vnode比较,渲染成真实DOM,派发patch

触发updated钩子函数,DOM已经更新完成

8. 当调用destroy时,触发beforeDestroy钩子函数,this仍能获取到实例
9. 实例销毁,事件子组件等都被移除销毁

触发destroyed钩子函数

2. created和mounted的区别?

  • created 初始化属性,进行了双向绑定;模板渲染成html前调用
  • mounted 初始化页面完成,模板已经渲染成html,vnode转为真实DOM渲染到界面上。

3. Vue有父子组件时的生命周期?

创建,从外到里,渲染,从里到外

  • 加载渲染:父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

  • 更新过程:父beforeUpdate->子beforeUpdate->子updated->父updated

  • 销毁过程:父beforeDestroy->子beforeDestroy -> 子destroyed -> 父destroyed

4. 一般在哪个生命周期请求异步数据?

我们可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面加载时间,用户体验更好;

  • SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。

5. keep-alive的生命周期有哪些?

设置了keep-alive会增加两个生命周期钩子(activated与deactivated),同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。

  • 不使用keep-alive:beforeRouteEnter --> created --> mounted --> destroyed

  • 使用keep-alive:beforeRouteEnter --> created --> mounted --> activated ... > beforeRouteLeave --> deactivated

  • 使用keep-alive再次进入缓存页面:beforeRouteEnter --> activated .. > beforeRouteLeave --> deactivated

2、watch 和 computed 的区别?

  • 是否缓存: computed支持缓存,computed依赖的值data或者props传过来的值,发生变化时才会计算;watch不支持缓存,watch第一次不执行,只有值发生变化时才会执行,如果需要立即执行可设置,immediate: true;watch 默认是浅监听,可以进行深度监听,deep: true
  • 是否支持异步: computed不支持异步;watch支持异步;
  • 运用场景: 当需要数值计算,有依赖值时使用computed;数据变化时执行异步或者开销较大时,使用watch
<template>
  <div>
    <p>{{ name }}</p>
    <button>改变值类型watch</button>
    <p>{{ msg.info }}</p>
    <button>改变引用类型watch</button>
    <p>{{ num }}</p>
    <p>{{ sum }}</p>
  </div>
</template>
<script>
export default {
  name: 'watch-demo',
  data() {
    return {
      name: '值类型',
      msg: {
        info: '引用类型'
      },
    }
  },
  watch: {
    // watch默认是浅度监听
    name(val, oldVal) {
      console.log(val, oldVal, '值类型')
    },
    // wacth进行深度监听需要添加deep: true;不能获取到oldVal
    msg: {
      handler(val, oldVal) {
        console.log(val, oldVal, '引用类型')
      },
      deep: true, // 深度监听
      immediate: true // 立即执行,watch第一次绑定不执行,只有值发生变化才会执行,如果需要立即执行需要为true
    }
  },
  computed: {
    sum() {
      console.log(this.num,'this.num')
      return this.num * 2;
    }
  },
  methods: {
    watchName() {
      this.name = '改变后的值类型';
    },
    watchMsg() {
      this.msg.info = '改变后的引用类型';
    }
  }
}
</script>

<style scoped lang='less'>
.nameClass {
  font-size: 20px;
}
.nameColor {
  color: #333;
}
</style>

image.png

3、class和style用法?

<template>
  <div>
    <p :class="{nameClass: isTrue, nameColor: isColor}">{{ name }}</p>
    <button :class="[isTrue ? 'nameClass' : '']">改变值类型watch</button>
    <p :style="{fontSize: '18px'}">{{ msg.info }}</p>
    <button :style="[baseStyle, overStyle]">改变引用类型watch</button>
  </div>
</template>
<script>
export default {
  name: 'watch-demo',
  data() {
    return {
      name: '值类型',
      msg: {
        info: '引用类型'
      },
      isTrue: true,
      isColor: true,
      baseStyle: {
        width: '300px',
        height: '30px'
      },
      overStyle: {
        fontSize: '20px'
      }
    }
  },
}
</script>

<style scoped lang='less'>
.nameClass {
  font-size: 20px;
}
.nameColor {
  color: #333;
}
</style>

4、条件渲染v-if、v-show的区别?

  • v-if第一次是false时不会渲染,只有当变成true时才会渲染,会进行dom的重构与销毁;v-show是dom一直存在,只是dispaly的显示与隐藏

  • v-if 用于更新不是很频繁的;v-show用于经常切换的场景

5、v-if、v-show、v-html的原理?

  • v-if会调用addIfCondition方法,生成Vnode节点时会忽略v-if为false的节点,render的时候不会渲染

  • v-show会生成Vnode节点,render也会渲染为真实节点,只是render过程中节点的属性display为none

  • v-html是将innerHTML作为v-html的值,会先移除节点下的所有节点,调用html方法,添加innerHTML属性。

6、循环渲染v-for为什么要加key?

加上key以后,在Vnode转为真实DOM的时候,diff算法判断sameVnode是否相同就是判断key是否相同,tag是否相同,使用key减少了DOM操作

7、v-for可以与v-if一起使用吗?为什么?

不推荐一起使用,v-for的优先级高于v-if,这样v-if会出现在每个v-for中,可以在上一层div中使用v-if,这样先进行判断是否显示,再进行循环,但只想渲染一部分数据时,这样使用很方便

8、组件间的传递方式?

1. 父传子: props

  • props只能父组件向子组件传值,子组件的数据会随着父组件不断更新。

  • props 可以显示定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。

  • props属性名规则:若在props中使用驼峰形式,模板中需要使用短横线的形式

父组件:

<template>
    <div>
        <Emit :number="num"></Emit>
    </div>
</template>

<script>
    import Emit from './Emit.vue'
    export default {
        name: "item-demo",
        data() {
            return {
                num: 1,
            }
        },
        components: {
            Emit
        }
    }
</script>

子组件:

<template>
    <div>
        <h5>父传子:</h5>
        <h5>{{number}}</h5>
    </div>
</template>

<script>
    export default {
        name: "emit",
        props: {
            number1: {
                type: 'Number',
                default: () => {
                    return 0
                }
            },
        }
    }
</script>

2. 子传父: this.$emit

  • $emit绑定一个自定义事件,当这个事件被执行的时就会将参数传递给父组件,而父组件通过v-on监听并接收参数。

父组件:

<template>
    <div>
        <!--btnclick不写参数,则默认直接传入子组件传入的id,浏览器事件(例onclick)若不写参数,默认传入的是event事件-->
        <Emit @emitclick="btnclick"></Emit>
    </div>
</template>

<script>
    import Emit from './Emit.vue'
    export default {
        name: "item-demo",
        components: {
            Emit
        },
        methods: {
            btnclick(id) {
                console.log('btnclick',id)
            }
        }
    }
</script>

子组件:

template>
    <div>
        <h5>子传父:</h5>
        <ul>
            <li
                v-for="item in categories"
                :key="item.id"
                @click="btnclick(item.id)"
            >
                {{item.name}}
            </li>
        </ul>
    </div>
</template>

<script>
    export default {
        name: "emit",
        data() {
            return {
                categories:[
                    {id:'1',name:'热门推荐'},
                    {id:'2',name:'手机数码'},
                    {id:'3',name:'家用家电'},
                ]
            }
        },
        methods: {
            btnclick(id) {
		//$emit('事件名',传入的值)
                this.$emit('emitclick',id)
            }
        }
    }
</script>

3. 兄弟组件: eventBus

eventBus事件总线适用于父子组件非父子组件等之间的通信,使用方式:

import  eventBus from './eventBus'

eventBus.$on('')
eventBus.$emit()
eventBus.$off()

使用步骤如下:

  • 创建事件中心管理组件之间的通信

eventBus.js

import Vue from 'Vue'
export default new Vue()
  • 发送事件 有两个兄弟组件,first,second

first.vue:

<template> 
    <div>
        <button @click="add">加法</button>
    </div> 
</template> 
<script> 
// 引入事件中心
import eventBus from './eventBus'
export default { 
    data() {
        return { 
            num:0
        }
    },
    methods:{
        add() {
            eventBus.$emit('addition', {
                num:this.num++
            })
        }
    } 
} 
</script>
  • 接收事件 second.vue:
<template> 
    <div>
        求和: {{ count }}
    </div> 
</template> 
<script> 
// 引入事件中心
import eventBus from './eventBus'
export default { 
    data() {
        return { 
            count:0
        }
    },
    mounted() {
        EventBus.$on('addition', param => {
            this.count = this.count + param.num;
        })
    } 
} 
</script>

在上述代码中,这就相当于将num值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不同组件通过它来通信。

如果项目过大,使用这种方式进行通信,后期维护起来会很困难。

4. 父组件获取子组件内容:$children,$refs / ref

  • 一般不用$children,因为不能保证顺序,需要使用下标值来取值,要是改变需求时就需要经常改动,不方便

  • 使用$refs,需要在使用组件时加上ref属性

父组件:

<template>
    <div>
        <Emit ref="aaa"></Emit>
        <button @click="showRef">点击显示子组件ref内容</button>
    </div>
</template>

<script>
    import Emit from './Emit.vue'
    export default {
        name: "item-demo",
        components: {
            Emit
        },
        methods: {
            showRef() {
                console.log(this.$refs.aaa.name);
            }
        }
    }
</script>

子组件:

export default {
    name: "emit",
    data() {
        return {
            name: '我是子组件Emit的name',
        }
    },
}

5. 子组件获取父组件内容:$parent,$root

一般不用$parent,因为在开发中,一个子组件可能有好几个父组件,使用$parent耦合性太高,所以一般不使用;可以使用$root来访问根组件的实例

6. 父子组件依赖注入:provide,inject

这种方式是Vue中的 依赖注入,该方法用于 父子组件之间的通信。这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。

provide / inject是Vue提供的两个钩子,和datamethods是同级的。并且provide的书写形式和data一样。

  • provide 钩子用来发送数据或方法
  • inject钩子用来接收数据或方法

父组件:

provide() {
    return {
        num: this.num
    }
}

子组件:

inject: ['num']

还可以这样写,这样写就可以访问父组件中的所有属性:

provide() {
    return {
        app: this
    }
}
data() {
    return {
        num: 1
    }
}

inject: ['app']

注意:  依赖注入所提供的属性是非响应式的。

7. $attrs,$listeners

考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给组件C传递数据,这种隔代的数据,该使用哪种方式呢?

  • 如果是用props/$emit来一级一级的传递,确实可以完成,但是比较复杂;
  • 如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;
  • 如果使用Vuex,的确也可以,但是如果仅仅是传递数据,那可能就有点浪费了。

针对上述情况,Vue引入了$attrs / $listeners,实现组件之间的跨代通信。

先来看一下inheritAttrs,它的默认值true,继承所有的父组件属性除props之外的所有属性;inheritAttrs:false 只继承class属性 。

  • $attrs:继承所有的父组件属性(除了prop传递的属性、class 和 style ),一般用在子组件的子元素上
  • $listeners:该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)

A组件(APP.vue):

<template>
    <div id="app">
        //此处监听了两个事件,可以在B组件或者C组件中直接触发
        <child1 :p-child1="child1" :p-child2="child2" @test1="onTest1" @test2="onTest2"></child1>
    </div>
</template>
<script>
import Child1 from './Child1.vue';
export default { 
    components: { Child1 },
    data() {
        return {
            child1: 'B组件',
            child2: 'C组件'
        }
    },
    methods: {
        onTest1() { 
            console.log('test1 running');
        },
        onTest2() {
            console.log('test2 running');
        }
    } 
}; 
</script>

B组件(Child1.vue):

<template>
    <div class='child-1'>
        <p>props: {{pChild1}}</p>
        <p>$attrs: {{$attrs}}</p>
        <child2 v-bind="$attrs" v-on="$listeners"></child2>
    </div>
</template>
<script>
import Child2 from './Child2.vue';
export default { 
    components: { Child2 },
    props: {
        pChild1: {
            type: 'String',
            default: () => {
                return ''
            }
        }
    },
    inheritAttrs: false,
    mounted() {
        // 触发APP.vue中的test1方法
        this.$emit('test1');
    } 
}; 
</script>

C组件(Child2.vue):

<template>
    <div class='child-2'>
        <p>props: {{pChild2}}</p>
        <p>$attrs: {{$attrs}}</p>
    </div>
</template>
<script>
export default { 
    props: {
        pChild2: {
            type: 'String',
            default: () => {
                return ''
            }
        }
    },
    inheritAttrs: false,
    mounted() {
        // 触发APP.vue中的test2方法
        this.$emit('test2');
    } 
}; 
</script>

在上述代码中:

  • C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性
  • 在B组件中通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)

9、子组件可以直接修改父组件的数据吗?

不行,因为在vue中是单向数据流,父组件通过props传给子组件数据,父组件中的数据发生变化时,子组件也会更新。为了防止意外改变父组件状态,使数据流变得难以理解,导致数据流混乱。

10、v-model如何实现的,语法糖是什么?v-model可以被用在自定义组件上吗?如果可以,如何使用?

1. 作用在表单元素上

动态绑定了input的value,指向了message变量,并且在触发input事件的时候去动态把message设置为目标值:

  • $event 当前触发的事件对象
  • $event.target 当前触发的事件对象的dom
  • $event.target.value 当前dom的value值
<input v-model="message" />
// 相当于
<input
    v-bind:value="message"
    v-on:input="message = $event.target.value
>

2. 作用在组件上

通过prop和$emit实现,语法糖是:

  • 把value用作prop
  • 把input用作event

(1) 父组件使用子组件自定义的v-model

<template>
  <div>
    <p>{{ name }}</p>
    <CustomVModel v-model="name"></CustomVModel>
  </div>
</template>

<script>
import CustomVModel from './customVModel.vue'
export default {
  name: 'modelIndex',
  data() {
    return {
      name: '自定义v-model'
    }
  },
  components: {
    CustomVModel
  }
}
</script>

(2) 子组件使用input来实现v-model

<template>
  <div>
    <input 
      type="text"
      :value="text1"
      @input="$emit('change1', $event.target.value)"
    /> 
    <!--
      1. 使用value而不是v-model
      2. change1与model.event对应起来
      3. vaule的text1与model.prop对应
    -->
  </div>
</template>

<script>
export default {
  name: 'customVModel',
  data() {
    return {
      
    }
  },
  model: {
    prop: 'text1', // 与input中的value值对应
    event: 'change1' // 与@input中的方法名对应
  },
  props: {
    text1: {
      type: String,
      default: () => {
        return ''
      }
    }
  }
}
</script>

image.png

image.png

11、$nextTick原理及作用?

1. $nextTick

vue是异步渲染,data改变后,DOM不会立即渲染,$nextTick会在DOM渲染后触发,以获取最新DOM节点,nextTick() 也用作优化。

使用场景

  • 修改数据后立刻获得 更新后的DOM结构 ,可以使用nextTick()

  • 在vue生命周期中,如果在 created() 钩子函数进行 DOM操作 ,也一定要放在nextTick() 的回调函数中,因为created()钩子函数中,DOM还未渲染,这时候也没办法操作DOM,所以要操作DOM,必须放在nextTick() 中。

<template>
  <div>
    <ul ref='ulref'>
      <li v-for="(item,index) in list" :key="index">
        {{ item }}
      </li>
    </ul>
    <button @click="addItem">添加数据</button>
  </div>
</template>

<script>

export default {
  name: 'nextTick',
  data() {
    return {
      list: ['a', 'b', 'c']
    }
  },
  methods: {
    addItem() {
      this.list.push(`${Date.now()}`)
      this.list.push(`${Date.now()}`)

      // 这样写的结果是:点击一次添加输出3,点击两次输出5,期望第一次得到5,第二次为7
      // 获取DOM元素,this.$refs.名字
      const urItem = this.$refs.ulref;
      console.log(urItem.childNodes.length)// 3,5....

      // 1. 异步渲染,$nextTick 等 DOM 渲染完再回调
      // 2. 页面渲染会将data的修改做整合,多次data修改只渲染一次
      this.$nextTick(() => {
        const urItem = this.$refs.ulref;
        console.log(urItem.childNodes.length)// 5,7....
      })
    }
  }
                                                                                             
}
</script>

2. 原理分析

12、slot是什么?有什么作用?原理是什么?

1. slot插槽

slot插槽,使组件具有扩展性。插槽slot是子组件的一个模板标签元素,这个元素是否显示,及怎么显示,由父组件决定。例如title标签,现有的是有title标题,但是也可能有图标,或者按钮等,这时候可以使用插槽。

插槽类型:

  • 默认插槽: 也叫匿名插槽,slot没有指定name属性,一个组件内只能有一个匿名插槽

子组件:

<template>
  <div class="slot-main">
    <div>
      {{message}}
      <br>
      <slot><button>默认值按钮</button></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'slot-demo',
  data() {
    return {
      message: '我是子组件插槽Slot',
    }
  }
}
</script>

父组件:

<template>
  <div>
    <Slot>
      <span>更改默认值</span>
    </Slot>
  </div>
</template>

image.png

  • 具名插槽: 带有name具体名字的插槽,一个组件可以出现多个具名插槽

子组件:

<template>
  <div class="slot-main">
    <div>
      {{message}}
      <br>
      <slot><button>默认值按钮</button></slot><br>
      <slot name="center"><span>具名插槽</span></slot><br>
      <slot name="right"><span>需要name</span></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'slot-demo',
  data() {
    return {
      message: '我是子组件插槽Slot',
    }
  }
}
</script>

父组件:

<template>
  <div>
    <Slot>
      <template v-slot:center>
        <span>具名插槽父组件更改时用v-slot:name</span>
      </template>
    </Slot>
  </div>
</template>

image.png

  • 作用域插槽: 可以是匿名插槽,也可以是具名插槽,不同点是在子组件渲染作用于插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件传递过来的数据决定如何渲染该插槽

子组件:

<template>
  <div class="slot-main">
    <div>
      <slot v-bind:user="planguages">
        <p v-for="item in planguages" :key="item">
          {{ item }}
        </p>
      </slot>
    </div>
  </div>
</template>

<script>
export default {
  name: 'slot-demo',
  data() {
    return {
      planguages: ['js', 'vue', 'ts']
    }
  }
}
</script>

父组件:

<template>
  <div>
    <Slot>
      <template v-slot:default="slot">
        <span v-for="item in slot.user" :key="item">
          {{ item }}-
        </span>
      </template>
    </Slot>
  </div>
</template>

image.png

2. 原理分析

当子组件vm实例化时,获取到父组件传入的slot标签的内容,存放在vm.$slot中,默认插槽为vm.$slot.default,具名插槽为vm.$slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用$slot中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

13、动态组件

动态组件,需要使用v-bind:is来绑定组件名

<template>
  <div>
    <div v-for="(val,key) in listInfo" :key="key">
      <!-- :is动态绑定组件名,不能直接绑定组件 -->
      <component :is='val.type'></component> 
    </div>
  </div>
</template>

<script>
import  NextTick from './nextTick.vue'
export default {
  name: 'isComponent',
  data() {
    return{
      listInfo: {
        1: {
          type: 'NextTick' // 组件名
        },
        2: {
          type: 'text'
        },
        3: {
          type: 'img'
        }
      }
    }
  },
  components: {
    NextTick
  }
}
</script>

14、异步加载组件

当一个组件特别大的时候,通过某一触发条件加载时,可以使用异步加载

<template>
  <div>
    <button @click="loadAsync">点击加载异步组件</button>
    <NextTick v-if="show"></NextTick>
  </div>
</template>

<script>
// import  NextTick from './nextTick.vue'
export default {
  name: 'importCom',
  data() {
   return {
     show: false
   } 
  },
  components: {
    // 异步加载组件
    NextTick: () => import('./nextTick.vue')
  },
  methods: {
    loadAsync() {
      this.show = true
    }
  }
  
}
</script>

15、如何保存页面的当前状态?

1. localStorage / sessionStorage

通过localStorage / sessionStorage 把当前状态通过JSON.stringify()保存。

优点:

  • 兼容性好,不需要额外工具
  • 简单快捷,基本可以满足大部分需求

缺点:

  • 状态通过 JSON 方法储存(相当于深拷贝),如果状态中有特殊情况(比如 Date 对象、Regexp 对象等)的时候会得到字符串而不是原来的值。

2. keep-alive

当组件在keep-alive内被切换时组件的状态会被保留

例如在项目中,切换两个tab,想要保存当前组件的状态

<keep-alive>
    <Master v-if="tab === 'master'" :tab='tab' :isDetail='isDetail'></Master>
    <Heavywork v-if="tab === 'heavywork'" :tab='tab' :isDetail='isDetail'></Heavywork>
</keep-alive>

16、对keep-alive的理解,它是如何实现的,具体缓存的是什么?

1. keep-alive是什么?

keep-alive保存组件当前状态到内存中,在切换回来后仍有当前状态,防止重复渲染DOM。keep-alive缓存动态组件时会缓存不活动的组件实例,而不是销毁他们。

设置了keep-alive会增加两个生命周期钩子(activated与deactivated):

  • 不使用keep-alive:beforeRouteEnter --> created --> mounted --> destroyed

  • 使用keep-alive:beforeRouteEnter --> created --> mounted --> activated ... > beforeRouteLeave --> deactivated

  • 使用keep-alive再次进入缓存页面:beforeRouteEnter --> activated .. > beforeRouteLeave --> deactivated

keep-alive可以设置以下props属性:

  • include: 字符串或正则表达式。只有名称匹配的组件会被缓存

  • exclude: 字符串或正则表达式。任何名称匹配的组件都不会被缓存

  • max: 数字。最多可以缓存多少组件实例 匹配首先检查组件自身的 name 选项,如果 name 选项不可用,则匹配它的局部注册名称 (父组件 components 选项的键值),匿名组件不能被匹配。

2. 使用场景:

使用原则:当我们在某些场景下不需要让页面重新加载时我们可以使用keepalive

例如:

当我们从首页–>列表页–>商品详情页–>再返回,这时候列表页应该是需要keep-alive

从首页–>列表页–>商品详情页–>返回到列表页(需要缓存)–>返回到首页(需要缓存)–>再次进入列表页(不需要缓存),这时候可以按需来控制页面的keep-alive

在路由中设置keepAlive属性判断是否需要缓存

{
    path: '/',
    name: 'xxx',
    component: () => import('../src/pages/xxx.vue'),
    meta: {
        keepAlive: true // 需要被缓存
    }
}

使用keep-alive:

<div>
    <keep-alive>
        <!-- 需要缓存的组件 -->
        <router-view v-if="$route.meta.keepAlive"></router-view>
    </keep-alive>
    <!-- 不需要缓存的组件 -->
    <router-view v-if="!$route.meta.keepAlive"></router-view>
</div>

3. 原理分析:

keep-alive是vue中内置的一个组件

  • 该组件没有template,而是用了render,在组件渲染的时候会自动执行render函数。

    • 首先获取组件的key值
    • 拿到key值去this.cache对象中寻找是否有该值,如果有则表示该组件有缓存,即命中缓存;如果没有,以该组件的key为键,将其存入到this.cache中,并且把key存入到this.keys中
    • 判断this.keys中缓存组件的数量是否超过了设置的最大缓存数this.max,如果超过了,则把第一个缓存组件删掉
  • 在mounted钩子函数中观测include和exclude的变化,如果发生变化,说明需要缓存的组件或者不需要缓存的组件发生了变化,执行pruneCache函数

  • 该函数对this.cache遍历,取出name值与新的缓存规则匹配,如果匹配不上,则表示在新的缓存规则下该组件已经不需要被缓存,则调用pruneCacheEntry函数将其从this.cache对象剔除即可

keep-alive具体是通过 cache 数组缓存所有组件的 vnode实例。当cache内原有组件被使用时会将该组件key从keys数组中删除,然后push到keys数组最后,以便清除最不常用组件。

4. 缓存后如何获取数据

  • beforeRouteEnter
beforeRouteEnter(to, from, next) {
    next(vm => {
        console.log(vm)
        // 每次进入路由执行
        vm.getData() // 获取数据
    })
}
  • activated
activated() {
    this.getData() // 获取数据
}

注意:服务器端渲染期间avtived不被调用

17、过滤器的作用,如何实现一个过滤器?

filters过滤器,不会修改数据,而是过滤数据(计算属性computed,方法methods都是通过修改数据来处理数据格式的输出显示)。使用方法是在插值表达式 {{ }}中,放在操作符 | 后。

使用场景:

  • 需要格式化数据,例如将价格加上单位显示 例如将数据拼接加上单位返回:
<span>{{ row | configText }}</span>

configText(item) {
    return window.i18nTool.$t('{item.cpu}核/{item.memory}GB/{item.sysDisk}GB', {
        'item.cpu': item.cpu,
        'item.memory': item.memory,
        'item.sysDisk': item.sysDisk
    });
},

18、data为什么是一个函数而不是对象?

  • data如果是一个对象,当多个实例引用同一个对象时,最后指向同一个对象,一个改变,其他的也会改变

  • 为了组件复用, 每个组件都有自己的数据,data是函数就拥有自己组件的私有数据,不会影响其他组件中的数据

19、vue单页应用与多页应用的区别?

  • SPA单页应用(SinglePage Web Application)只有一个主页面的应用,一个html,一开始只加载一次js,css等;所有的内容都包含在主页面中,模块化加载组件;切换组件仅刷新局部资源。

  • MPA多页应用(MultiPage Application),多个独立页面的应用,有多个html,也会执行多个js,css等;切换页面时会刷新整页资源。

SPA优点:

  • 页面切换快,体验好
  • 公用的资源只加载一次
  • 局部刷新代码,代码可复用

SPA缺点:

  • SEO搜索引擎难度高;
  • 初次加载耗时多;

20、简述mixins、extends的覆盖逻辑?

1. mixins,extends

mixins,extends都是用于合并、扩展组件的,两者都可使用mergeOptions方法实现合并。

  • mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中。Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。

  • extends主要是为了扩展单文件组件,接受一个对象或构造函数。很少用到

data,provide的合并策略: mixins/extends只会将自己有的但是组件上没有的内容混合到组件上,重复的默认使用组件上的;如果data里的值是对象,将递归内部对象继续按照该策略合并

props,methods,inject,compouted,组件,过滤器,指令属性,el,prosData的合并策略: mixins/extends只会将自己有的但是组件上没有的内容混合到组件上

watch的合并策略: 合并watch监控的回调方法,执行顺序是先mixins/extends里watch定义的回调,然后是组件的回调

HOOKS生命周期钩子的合并策略: 同一种钩子的回调函数会被合并成数组,先mixins/extends中的钩子函数,再组件的

抽离相同代码baseInfo组件:

<script>
export default {
    name: 'baseInfo',
    computed: {
        isApp() {
            return this.systemType === 'app';
        },
        productAlias() {
            return this.$route.query.productAlias;
        },
        productName() {
            return this.$route.query.productName;
        }
    },
};
</script>

使用mixins:

<BreadcrumbItem>{{ productAlias }}</BreadcrumbItem>

import baseInfo from '../common/baseInfo';
export default {
   mixins: [baseInfo], 
}

2. mergeOptions执行过程:

  • 规范化选项(normalizeProps、normalizelnject、normalizeDirectives)

  • 对未合并的选项,进行判断,然后进行合并。根据一个通用 Vue 实例所包含的选项进行分类逐一判断合并,如 props、data、 methods、watch、computed、生命周期等,将合并结果存储在新定义的 options 对象里。

  • 返回合并结果 options

21、什么是mixin?mixin与mixins的区别?

1. mixin

  • mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。

  • Mixin 使我们能够为 Vue 组件编写可插拔和可重用的功能。

  • 如果希望在多个组件之间重用一组组件选项,例如生命周期 hook、 方法等,则可以将其编写为 mixin,并在组件中简单的引用它。

  • 然后将 mixin 的内容合并到组件中。如果你要在 mixin 中定义生命周期 hook,那么它在执行时将优化于组件自已的 hook。

2. mixin 与 mixins

  • mixin 用于全局混入,会影响到每个组件实例,通常插件都是这样做初始化的。
  • mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。 另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。