vue--面试题

目录

0. Vue的最大优势是什么?

1. Vue和jQuery区别是什么?

2. mvvm和mvc区别是什么?

3. Vue常用修饰符有哪些?

4. Vue2.x兼容IE哪个版本以上

5.v-html和v-text的区别

6. 对Vue渐进式的理解

7. 说出至少4个Vue指令及作用

8. 为什么避免v-for和v-if在一起使用

9. Vue 中怎么自定义过滤器

10. Vue中:key作用, 为什么不能用索引

11. 数组更新有的时候v-for不渲染

12. computed和watch

13. class和style

14. 条件渲染--v-show和v-if的区别

15. 为何v-for中要用key

16. 事件

17. 事件、按键修饰符

18. 表单

19. props、$emit--父子组件间的通信--vue组件如何通讯

20. 自定义事件实现兄弟之间通信--vue组件如何通讯

21. 描述vue组件生命周期(有父子组件的情况)--组件渲染和更新的过程

22. vue如何实现自己的 v-model(自定义 v-model)

23. vue更新之后如何获取最新的DOM($nextTick)

24. slot是什么

25. 动态组件

26. vue如何异步加载组件

27. vue如何缓存组件--keep-alive

28. vue组件如何抽离公共逻辑--mixin

 29. vuex

30. vue响应式

31. 虚拟DOM(Virtual DOM简称VDOM)

32. diff算法

33. 模板编译

34. 组件渲染/更新的过程

35. 路由

36. 双向数据绑定v-model的实现原理

37. 为何组件data必须是一个函数

38. ajax请求应该放在哪个生命周期

39. 如何将组件所有的props传递给子组件

40. 何时需要使用beforeDestory

41. vuex中action和mutation的区别

42. 如何配置vue-router异步加载

43. 用vnode描述一个DOM结构

44. vue常见的性能优化

45. Proxy实现响应式

46. 基于vue实现一个购物车(组件结构,vuex state 数据结构)

47. 请说下封装 vue 组件的过程

48.  讲一下组件的命名规范

49. Vue 的 nextTick 的原理是什么?

50. vue生命周期总共分为几个阶段?

51. 第一次加载页面会触发哪几个钩子函数?(必会)

52. 跟keep-alive有关的生命周期是哪些?

53. 自定义指令(v-check、v-focus)的方法有哪些?它有哪些钩子函数?还有哪些钩子函数参数?

54.  is这个特性你有用过吗?主要用在哪些方面?

55. 路由之间是怎么跳转的?有哪些方式

56. vue-router怎么配置路由

57.  vue-router的钩子函数都有哪些

58. 路由传值的方式有哪几种

59.  怎么定义vue-router的动态路由?怎么获取传过来的动态参数?

60.  Vue的路由实现模式:hash模式和history模式

61. 请说出路由配置项常用的属性及作用

62. 编程式导航使用的方法以及常用的方法(必会)

63. Vue如何去除URL中的#(必会)

64. 说一下你在vue中踩过的坑

65. $route和​$router的区别?

 


 

0. Vue的最大优势是什么?

​ 简单易学, 轻量级整个源码js文件不大, 双向数据绑定, 数据驱动视图, 组件化, 数据和视图分离,

​ vue负责关联视图和数据, 作者中国人(尤雨溪), 文档都是中文的, 入门教程非常多, 上手简单.

​ 相比传统网页, vue是单页面可以只刷新某一部分


1. Vue和jQuery区别是什么?

jQuery应该算是一个插件, 里面封装了各种易用的方法, 方便你使用更少的代码来操作dom标签

​ Vue是一套框架, 有自己的规则和体系与语法, 特别是设计思想MVVM, 让数据和视图关联绑定, 省略了很多DOM操作. 然后指令还给标签注入了更多的功能


2. mvvm和mvc区别是什么?

​ MVC:

也是一种设计模式, 组织代码的结构, 是model数据模型, view视图, Controller控制器, 在控制器这层里编写js代码, 来控制数据和视图关联

​ MVVM:

即Model-View-ViewModel的简写。即模型-视图-视图模型, VM是这个设计模式的核心, 连接v和m的桥梁, 内部会监听DOM事件, 监听数据对象变化来影响对方. 我们称之为数据绑定

传统组件只是静态渲染,更新还要依赖于操作DOM,而有了VUE和React以后就有了数据驱动视图。所谓的数据驱动视图就是我们不再自己去操作DOM,如果想改什么地方的话直接去改vue/react里面的数据就可以了,这就和传统组件有着本质的区别,也正是因为这一点我们在做vue/react开发的时候更多的时候是去关注数据也就是业务逻辑,而不是DOM

vue中的数据驱动视图是MVVM,react中的数据驱动视图是React setState

view层里面有什么点击事件、各种DOM事件监听的时候都可以去修改model层的数据

通过修改model(data部分)里面的数据去驱动view

vm部分就是view和model连接部分的代码

 


3. Vue常用修饰符有哪些?

​ .prevent: 提交事件不再重载页面;

​ .stop: 阻止单击事件冒泡;

​ .once: 只执行一次这个事件


4. Vue2.x兼容IE哪个版本以上

不支持ie8及以下,部分兼容ie9 ,完全兼容10以上, 因为vue的响应式原理是基于es5的Object.defineProperty(),而这个方法不支持ie8及以下


 

5.v-html和v-text的区别

v-html会将包含的代码进行转义

v-text不会转义

<template>
  <div>
      <p>文本插值:{{message}}</p>
      <p>JS 表达式 {{flag ? 'yes' : 'no'}} (只能是表达式,不能是 js 语句)</p>

      <p :id="dynamicId">动态属性</p>

      <hr>

    <p v-html="rawHtml">
        <span>【注意】使用 v-html 之后,将会覆盖子元素,还有XSS(跨站脚本攻击)风险</span>
    </p>
    <p v-text="tHtml"></p>
  </div>
</template>

<script>
export default {
    data () {
        return {
            message:'hello vue',
            flag:true,
            dynamicId: `id-${Date.now()}`,
            rawHtml:'指令 - 原始 html <b>加粗</b> <i>斜体</i>',
            tHtml:'指令 - 原始 html <b>加粗</b> <i>斜体</i>'
        }
    }
}
</script>


6. 对Vue渐进式的理解

​ 渐进式代表的含义是:主张最少, 自底向上, 增量开发, 组件集合, 便于复用


7. 说出至少4个Vue指令及作用

​ v-for 根据数组的个数, 循环数组元素的同时还生成所在的标签

​ v-show 显示内容

​ v-if 显示与隐藏

​ v-else 必须和v-if连用 不能单独使用 否则报错

​ v-bind 动态绑定 作用: 及时对页面的数据进行更改, 可以简写成:分号

​ v-on 给标签绑定函数,可以缩写为@,例如绑定一个点击函数 函数必须写在methods里面

​ v-text 解析文本

​ v-html 解析html标签


8. 为什么避免v-for和v-if在一起使用

Vue 处理指令时,v-for 比 v-if 具有更高的优先级, 虽然用起来也没报错好像, 但是性能不高, 如果你有5个元素被v-for循环, v-if也会分别执行5次.


9. Vue 中怎么自定义过滤器

Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和v-bind表达式

​ 全局的用Vue.filter()

​ 局部的用filters属性


10. Vue中:key作用, 为什么不能用索引

​ :key是给v-for循环生成标签颁发唯一标识的, 用于性能的优化

​ 因为v-for数据项的顺序改变,Vue 也不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素

​ :key如果是索引, 因为索引是连续的, 如果删除其中某一个, 会导致最后一个被删除

​ 当我们再删除的时候, :key再根据数据来把新旧的dom对比时, 删除:key不存在的对应的标签(添加也是一样的插入到指定位置, 别的都不会动)


11. 数组更新有的时候v-for不渲染

​ 因为vue内部只能监测到数组顺序/位置的改变/数量的改变, 但是值被重新赋予监测不到变更, 可以用 Vue.set() / vm.$set()


12. computed和watch

computed有缓存,data不变时则不会重新计算

watch如何深度监听?

watch监听引用类型需要深度监听,拿不到oldValue

computed,

<template>
  <div>
      <p>num {{num}}</p>
      <p>double1 {{double1}}</p>
      <input v-model="double2">
  </div>
</template>

<script>
export default {
    data () {
        return {
            /*如果num:20一直不变,那么double1、double2里面的 
            this.num*2 是不会重新计算的,而是会缓存起来*/
            num:20
        }
    },
    computed: {
        double1(){
            return this.num*2;
        },
        double2:{
            get(){
                return this.num*2;
            },
            set(val){
                this.num=val/2;
            }
        }
    }
}
</script>

watch,

<template>
    <div>
        <input v-model="name">
        <input v-model="info.city">
    </div>
</template>

<script>
export default {
    data () {
        return {
            name:'lwj',
            info:{
                city:'重庆'
            }
        }
    },
    watch: {
        name(oldVal,val){
            console.log('watch name',oldVal,val);//值类型,可以正常拿到
        },
        info:{
            handler(oldVal,val){
                console.log('watch name',oldVal,val);//引用类型,拿不到
                //因为引用类型的传递是地址传递,因为指针相同,此时已经指向了新的val
            },
            deep:true //深度监听
        }
    }
}
</script>


13. class和style

使用动态属性和驼峰式写法

<template>
  <div>
      <p :class="{black: isBlack,yello: isYellow}">使用 class</p>
      <p :class="[black,yellow]">使用 class (数组)</p>
      <p :style="styleData">使用 style</p>
  </div>
</template>

<script>
export default {
    data () {
        return {
            isBlack:true,
            isYellow:true,

            black:'black',
            yellow:'yellow',

            styleData:{
                fontSize:'40px',
                color:'orange',
                backgroundColor:'#ccc'
            }
        }
    }
}
</script>


14. 条件渲染--v-show和v-if的区别

  • v-if/v-else的用法,可使用变量,也可以使用 === 表达式
  • v-if---组件真正的渲染和销毁(通过销毁和创建DOM节点),而不是显示和隐藏
  • v-show--通过CSS的display:none/block;控制其隐藏或者显示
  • 频繁切换显示状态用v-show,否则用v-if

使用场景:

如果是一次性的或者说是更新不是很频繁,就用v-if,反之用v-show

<template>
  <div>
      <p v-if="type === 'a'">A</p>
       <p v-else-if="type === 'b'">B</p>
       <p v-else>other</p>

       <p v-show="type === 'a'">A by v-show</p>
       <p v-show="type === 'b'">B by v-show</p>
  </div>
</template>

<script>
export default {
    data () {
        return {
            type: 'a'
        }
    }
}
</script>


15. 为何v-for中要用key

必须用key,且不能是index和random

diff算法中通过tag和key来判断,是否是sameNode

可以减少渲染的次数,提升渲染性能

如何遍历对象--也可以用v-for

key不能乱写,:key="唯一标识" 唯一标识可以是item里面id index等,因为vue组件高度复用,增加Key可以标识组件的唯一性,为了更好地区别各个组件, key的作用主要是为了高效的更新虚拟DOM

v-for和v-if不能同时使用;因为在渲染的时候v-for会先渲染,那么这样一来渲染的每一个里面都会包含v-if,也就是v-if会重复执行很多次,这是不允许的

<template>
    <div>
        <p>遍历数组</p>
        <ul>
            <li v-for="(item,index) in listArr" :key="item.id">
                {{index}} -- {{item.id}} -- {{item.title}}
            </li>
        </ul>
        <p>遍历数组</p>
        <ul>
            <li v-for="(val,key,index) in listObj" :key="key">
                {{index}} -- {{key}} -- {{val.title}}
            </li>
        </ul>
    </div>
</template>

<script>
export default {
    data () {
        return {
            listArr:[
                //数组结构中最好有id,便于区分
                {id:'a',title:'标题1'},
                {id:'b',title:'标题2'},
                {id:'c',title:'标题3'}
            ],
            listObj:{
                a:{title:'标题1'},
                b:{title:'标题2'},
                c:{title:'标题3'}
            },
        }
    }
}
</script>


16. 事件

<template>
  <div>
      <p>{{num}}</p>
      <button @click="increment1">+1</button>
      <button @click="increment2(2,$event)">+2</button>
  </div>
</template>

<script>
export default {
    data () {
        return {
            num: 0
        }
    },
    methods: {
        increment1(event){
            console.log('event',event,event.__proto__.constructor);//event是原生的
            console.log(event.target);//事件在什么地方被监听的
            console.log(event.currentTarget);//事件在什么地方被触发的
            this.num++;
            //注意:事件是被挂载到当前元素
        },
        increment2(val,event){
            console.log(event.target);//事件在什么地方被监听的
            this.num=this.num+val;
        }
    }
}
</script>


17. 事件、按键修饰符

事件修饰符,

      <!--阻止单击事件继续传播-->

      <a v-on:click.stop="doThis"></a>

      <!--提交事件不再重载页面-->

      <form v-on:submit.prevent="onSumit"></form>

      <!--修饰符可以串联-->

      <a v-on:click.stop.prevent="doThat"></a>

      <!--添加事件监听器时使用事件捕获模式-->

      <!--即内部元素触发的事件先在此处理,然后才交由内部元素进行处理-->

      <di v-on:click.capture="doThis"></di>

      <!--只当在 event.target 是当前元素自身时触发处理函数-->

      <!--即事件不是从内部元素触发的-->

      <div> v-on:click.self="doThat"</div>

按键修饰符,

     <!--即使 Alt 或 Shift 被同一按下时也会触发-->

      <button @click.ctrl="onClick"></button>

      <!--有且只有 Ctrl 被按下时才触发-->

      <button @click.ctrl.exact="onCtrlClick"></button>

      <!--没有任何系统修饰符被按下时才触发-->

      <button @click.exact="onClick"></button>

        ......


18. 表单

<template>
  <div>
      <p>输入框:{{name}}</p>
      <!--.trim截取前后空格-->
      <input type="text" v-model.trim="name">
      <!--.lazy类似于防抖的效果-->
      <input type="text" v-model.lazy="name">
       <!--.number转换为数字-->
      <input type="text" v-model.number="name">

      <p>多行文本:{{desc}}</p>
      <textarea v-model="desc"></textarea>
      <!-- <textarea >{{desc}}</textarea> 是不允许的-->

      <p>复选框:{{checked}}</p>
      <input type="checkbox" v-model="checked">

      <p>多个复选框:{{checkedNames}}</p>
      <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
      <label for="jack">Jack</label>
      <input type="checkbox" id="john" value="John" v-model="checkedNames">
      <label for="john">John</label>
      <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
      <label for="mike">Mike</label>
      
      <p>单选:{{gender}}</p>
      <input type="radio" id="male" value="male" v-model="gender">
      <label for="male">男</label>
      <input type="radio" id="female" value="female" v-model="gender">
      <label for="female">女</label>

      <p>下拉列表:{{selected}}</p>
      <select v-model="selected">
          <option value="" disabled>请选择</option>
          <option>A</option>
          <option>B</option>
          <option>C</option>
      </select>

      <p>下拉列表(多选):{{selectedList}}</p>
      <select v-model="selectedList" multiple>
          <option value="" disabled>请选择</option>
          <option>A</option>
          <option>B</option>
          <option>C</option>
      </select>
  </div>
</template>

<script>
export default {
    data () {
        return {
            name: 'lwj',
            age: 23,
            desc: '自我评价',

            checked: true,
            checkedNames: [],

            gender: 'male',

            selected: '',
            selectedList: []
        }
    }
}
</script>


19. props、$emit--父子组件间的通信--vue组件如何通讯

props:父组件向子组件传递信息

 $emit:子组件向父组件触发事件

有父子组件的情况:

父子组件:props和this.$emit

自定义事件:event.$on 、event.$off、 event.$emit

vuex

index.vue,

<template>
  <div>
      <Input @add="addHandler"/>
      <List :list="list" @delete="deleteHandler" />
  </div>
</template>

<script>
import Input from "./Input.vue"
import List from "./List.vue"

export default {
    components: {
        Input,
        List
    },
    data () {
        return {
            list:[
                {
                    id: 'id-1',
                    title: '标题1'
                },
                {
                    id: 'id-2',
                    title: '标题2'
                }
            ]
        }
    },
    methods: {
        addHandler(title){
            this.list.push({
                id: `id-${Date.now}`,
                title
            })
        },
        deleteHandler(id){
            this.list = this.list.filter(item=>item.id!==id);
        }
    }
}
</script>

Input.vue,

<template>
  <div>
      <input type="text" v-model="title">
      <button @click="addTitle">add</button>
  </div>
</template>

<script>
export default {
    data () {
        return {
            title: ''
        }
    },
    methods: {
        addTitle(){
            //调用父组件的事件
            this.$emit('add',this.title);

            this.title='';
        }
    }
}
</script>

List.vue,

<template>
  <div>
      <ul>
          <li v-for="item in list" :key="item.id">
              {{item.title}}
              <button @click="deleteItem(item.id)">删除</button>
          </li>
      </ul>
  </div>
</template>

<script>

export default {
    props:{
        list:{
            type: Array,
            default(){
                return [];
            }
        }
    },
    methods: {
        deleteItem(id){
            this.$emit('delete',id);
        }
    }
}
</script>

<style>

</style>


20. 自定义事件实现兄弟之间通信--vue组件如何通讯

event.js,

import Vue from 'vue'

export default new Vue()

Input.vue,

<template>
  <div>
      <input type="text" v-model="title">
      <button @click="addTitle">add</button>
  </div>
</template>

<script>
import event from './event'

export default {
    data () {
        return {
            title: ''
        }
    },
    methods: {
        addTitle(){
            //调用父组件的事件   this.$emit--调用父组件
            this.$emit('add',this.title);

            //调用自定义事件 event.$emit--调用自定义事件
            event.$emit('onAddTitle',this.title);

            this.title='';
        }
    }
}
</script>

List.vue,

<template>
  <div>
      <ul>
          <li v-for="item in list" :key="item.id">
              {{item.title}}
              <button @click="deleteItem(item.id)">删除</button>
          </li>
      </ul>
  </div>
</template>

<script>
import event from './event';

export default {
    props:{
        list:{
            type: Array,
            default(){
                return [];
            }
        }
    },
    methods: {
        deleteItem(id){
            this.$emit('delete',id);
        },
        addTitleHandler(title){
            console.log('on add title',title);
        }
    },
    mounted () {
        //绑定自定义事件 
        event.$on('onAddTitle',this.addTitleHandler);
    },
    beforeDestroy () {
        //及时销毁,否则可能造成内存泄露
        event.$off('onAddTitle',this.addTitleHandler);
    }
}
</script>

<style>

</style>


21. 描述vue组件生命周期(有父子组件的情况)--组件渲染和更新的过程

单组件:

created:只是初始化了vue实例,只是存在于js内存中的一个变量并没有开始渲染

mounted:组件真正的在网页上绘制/渲染完成了

  • beforeDestroy:解除绑定、销毁子组件以及事件监听器,在做以上之前我们要解除自定义事件的绑定、setTimeot、setInterval要销毁、自己绑定的一些window和document事件要销毁
  • 在创建JS模型时是父组件先创建完后--beforeCreate->created-再是子组件再创建--beforeCreate--created
  • 在渲染时是子组件先渲染完后父组件才渲染--父beforeMount->子beforeMount->子mounted->父mounted   
  • 在数据更新时,更新前是父组件先执行 beforeUpdate触发更新,再子组件执行beforeUpdate;只有在子组件都更新完也就是子组件先执行updated,再是父组件updated
  • 在进行销毁时,销毁前父组件先执行beforeDestroy,再子组件执行beforeDestroy;只有在子组件销毁完后也就是子组件先执行destroyed,父组件才执行destroyed


22. vue如何实现自己的 v-model(自定义 v-model)

index.vue

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

<script>
import CustomVModel from "./CustomVModel.vue"
export default {
    components: {
        CustomVModel
    },
    data () {
        return {
            name: 'lwj'
        }
    }
}
</script>

CustomVModel.vue

<template>
<!-- 
    3.
 :value="text" @input="$emit('change',$event.target.value)"
 -->
  <input type="text" :value="text" @input="$emit('change',$event.target.value)">
</template>

<!--
注意点:
    1.上面的input使用了 :value 而不是 v-model
    2.上面的 change 和 model.event 要对应起来
    3.text 属性要对应起来
-->

<script>
export default {
    //2.
    model: {
        prop: 'text', //对应 props里的 text
        event: 'change'
    },
    //1.
    props: {
        text: String,
        default() {
            return ''
        }
    }
}
</script>


23. vue更新之后如何获取最新的DOM($nextTick)

 vue是异步渲染

data改变之后,DOM不会立刻渲染

$nextTick会在DOM渲染之后被触发,以获取最新的DOM节点

<template>
  <div class="app">
      <ul ref="ul1">
          <li v-for="(item,index) in list" :key="index">
              {{item}}
          </li>
      </ul>
      <button @click="addItem">添加一页</button>
  </div>
</template>

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

            //1.异步渲染,$nextTick 待 DOM 渲染完后再回调
            //2.页面渲染时会将 data 的修改做整合,多次 data 修改只会渲染一次
            this.$nextTick(()=>{
                //获取DOM元素
                // 在元素中利用 ref="xxx" 的形式,通过 this.$refs.xxx即可拿到对应的元素的DOM节点
                const ulElem = this.$refs.ul1;
                console.log(ulElem.childNodes.length);
            });
        }
    }
}
</script>


24. slot是什么

父组件向子组件里插入一段内容

基本用法:

index.vue

<template>
  <div>
      <SloteDemo :url="website.url">
          {{website.title}}
      </SloteDemo>
  </div>
</template>

<script>
import SloteDemo from "./SlotDemo.vue"
export default {
    components: {
        SloteDemo,
    },
    data () {
        return {
            website: {
                url: 'http://baidu.com/',
                title: 'baidu',
                subTitle: '百度'
            }
        }
    }
}
</script>

SloteDemo.vue

<template>
  <a :href="url">
      <slot>
          默认内容,即父组件没有设置内容时,这里显示
      </slot>
  </a>
</template>

<script>
export default {
    props: ['url'],
}
</script>

作用域插槽

index.vue

<template>
  <div>
      <ScopedSlotDemo :url="website.url">
          <!--
              2.在父组件里通过 v-slot='xxx'
              再通过 xxx.yyy.属性 即可获取到子组件里的对应的内容
          -->
          <template v-slot="slotProps">
              {{slotProps.sloteData.title}}
          </template>
      </ScopedSlotDemo>
  </div>
</template>

<script>
import ScopedSlotDemo from "./ScopedSlotDemo.vue"
export default {
    components: {
        ScopedSlotDemo,
    },
    data () {
        return {
            website: {
                url: 'http://baidu.com/',
                title: 'baidu',
                subTitle: '百度'
            }
        }
    }
}
</script>

ScopedSlotDemo.vue

<template>
    <a :href="url">
        <!--
            1.子组件里定义一个动态属性yyy,使其对应到 website
        -->
      <slot :sloteData="website">
          <!--默认值显示: subTitle ,即父组件不传递时显示-->
         {{website.subTitle}}
      </slot>
  </a>
</template>

<script>
export default {
    props: ['url'],
    data () {
        return {
            website: {
                url: 'http://taobao.com',
                title: 'taobao',
                subTitle: '淘宝'
            }
        }
    }
}
</script>

具名插槽

index.vue

<template>
  <div>
      <NamedSlot>
          <template v-slot:header>
              <h1>将插入 header slot 中</h1>
          </template>

          <p>将插到 main slot 中,即未命名的 slot</p>

          <template v-slot:footer>
              <h1>将插入 footer slot 中</h1>
          </template>
      </NamedSlot>
  </div>
</template>

<script>
import NamedSlot from "./NamedSlot.vue"
export default {
    components: 
        NamedSlot
    },
}
</script>

NamedSlot.vue

<template>
  <div class="container">
      <header>
          <slot name="header"></slot>
      </header>
      <div class="main">
          <slot></slot>
      </div>
      <footer>
          <slot name="footer"></slot>
      </footer>
  </div>
</template>

<script>
export default {

}
</script>


25. 动态组件

用法::is="component-name"

场景:需要根据数据,动态渲染的场景。即组件类型不确定

 component可以配合keep-alive来保存被隐藏组件隐藏之前的状态

keep-alive的用法:

参数:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存
  • max - 数字。最多可以缓存多少组件实例

<keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中

当组件在 <keep-alive> 内被切换,它的 activateddeactivated 这两个生命周期钩子函数将会被对应执行

<keep-alive> 是用在其一个直属的子组件被开关的情形。如果你在其中有 v-for 则不会工作

includeexclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

<script src="js/vue.js"></script>
<div id="app">
        <button @click="toggle">切换</button>
        <keep-alive>
            <component v-bind:is="name"></component>
        </keep-alive>
    </div>

    <template id="info">
        <div>
           <p>我是详情页</p>
           <input type="checkbox">
        </div>
    </template>

    <template id="me">
        <div>
           <p>我是个人中心页</p>
           <img src="images/gt2.png" alt="">
        </div>
    </template>

        //全局组件
        Vue.component("info", {
            template: `#info`,
        });

        Vue.component("me", {
            template: `#me`,
        });


        let vue = new Vue({
            el: '#app',
            // 这里就是MVVM中的Model
            data: {
                isShow: true,
                name: "info"
            },
            // 专门用于存储监听事件回调函数
            methods: {
                toggle() {
                    this.isShow = !this.isShow;
                    this.name = this.name === "info" ? "me" : "info";
                }
            },
            // template: `
            // `
        });


26. vue如何异步加载组件

import()函数

按需加载,异步加载大组件

何时使用异步组件?

加载大组件

路由异步加载(多路由情况下)

index.vue

<template>
  <div>
    <FormDemo v-if="showFormDemo" />
    <button @click="showFormDemo = true">show form demo</button>
  </div>
</template>

<script>
export default {
    components: {
        FormDemo: ()=> import('../BaseUse/FormDemo.vue')
    },
    data () {
        return {
            showFormDemo: false,
        }
    }
}
</script>

FormDemo.vue

<template>
  <div>
      <p>输入框:{{name}}</p>
      <!--.trim截取前后空格-->
      <input type="text" v-model.trim="name">
      <!--.lazy类似于防抖的效果-->
      <input type="text" v-model.lazy="name">
       <!--.number转换为数字-->
      <input type="text" v-model.number="name">

      <p>多行文本:{{desc}}</p>
      <textarea v-model="desc"></textarea>
      <!-- <textarea >{{desc}}</textarea> 是不允许的-->

      <p>复选框:{{checked}}</p>
      <input type="checkbox" v-model="checked">

      <p>多个复选框:{{checkedNames}}</p>
      <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
      <label for="jack">Jack</label>
      <input type="checkbox" id="john" value="John" v-model="checkedNames">
      <label for="john">John</label>
      <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
      <label for="mike">Mike</label>
      
      <p>单选:{{gender}}</p>
      <input type="radio" id="male" value="male" v-model="gender">
      <label for="male">男</label>
      <input type="radio" id="female" value="female" v-model="gender">
      <label for="female">女</label>

      <p>下拉列表:{{selected}}</p>
      <select v-model="selected">
          <option value="" disabled>请选择</option>
          <option>A</option>
          <option>B</option>
          <option>C</option>
      </select>

      <p>下拉列表(多选):{{selectedList}}</p>
      <select v-model="selectedList" multiple>
          <option value="" disabled>请选择</option>
          <option>A</option>
          <option>B</option>
          <option>C</option>
      </select>
  </div>
</template>

<script>
export default {
    data () {
        return {
            name: 'lwj',
            age: 23,
            desc: '自我评价',

            checked: true,
            checkedNames: [],

            gender: 'male',

            selected: '',
            selectedList: []
        }
    }
}
</script>


27. vue如何缓存组件--keep-alive

频繁切换,不需要重复渲染的时候

何时使用keep-alive?

缓存组件,不需要重复渲染。例如多个静态tab也的切换

异步组件和keep-alive都是优化性能

index.vue

<template>
  <div>
    <KeepAlive />
  </div>
</template>

<script>
import KeepAlive from "./KeepAlive.vue"
export default {
    components: {
        KeepAlive,
    },
}
</script>

KeepAlive.vue

<template>
  <div>
      <button @click="changeState('A')">A</button>
      <button @click="changeState('B')">B</button>
      <button @click="changeState('C')">C</button>

      <keep-alive><!--tab切换-->
        <KeepAliveStateA v-if="state === 'A'" />
        <KeepAliveStateB v-if="state === 'B'"/>
        <KeepAliveStateC v-if="state === 'C'"/>
      </keep-alive>
  </div>
</template>

<script>
import KeepAliveStateA from "./KeepAliveStateA.vue"
import KeepAliveStateB from "./KeepAliveStateB.vue"
import KeepAliveStateC from "./KeepAliveStateC.vue"
export default {
    components: {
        KeepAliveStateA,
        KeepAliveStateB,
        KeepAliveStateC
    },
    data () {
        return {
            state: 'A'
        }
    },
    methods: {
        changeState(state){
            this.state=state;
        }
    }
}
</script>

KeepAliveStateA.vue

<template>
  <p>state A</p>
</template>

<script>
export default {
    mounted () {
        console.log('A mounted');
    },
    destroyed () {
        console.log('A  destroyed');
    }
}
</script>

KeepAliveStateB.vue

<template>
  <p>state B</p>
</template>

<script>
export default {
    mounted () {
        console.log('B mounted');
    },
    destroyed () {
        console.log('B  destroyed');
    }
}
</script>

KeepAliveStateC.vue

<template>
  <div>
      <button @click="changeState('A')">A</button>
      <button @click="changeState('B')">B</button>
      <button @click="changeState('C')">C</button>

    <KeepAliveStateA v-if="state === 'A'" />
    <KeepAliveStateB v-if="state === 'B'"/>
    <KeepAliveStateC v-if="state === 'C'"/>
  </div>
</template>

<script>
import KeepAliveStateA from "./KeepAliveStateA.vue"
import KeepAliveStateB from "./KeepAliveStateB.vue"
import KeepAliveStateC from "./KeepAliveStateC.vue"
export default {
    components: {
        KeepAliveStateA,
        KeepAliveStateB,
        KeepAliveStateC
    },
    data () {
        return {
            state: 'A'
        }
    },
    methods: {
        changeState(state){
            this.state=state;
        }
    }
}
</script>


28. vue组件如何抽离公共逻辑--mixin

多个组件有相同逻辑的时候,抽离出来

但是mixin并不是完美的解决方案,会出现一些问题,例如变量来源不明确,不利于阅读、多个mixin可能会造成命名冲突、mixin和组件可能出现多对多的关系,复杂度高

Vue3提出的Composition API旨在解决这些问题

index.vue

<template>
  <div>
    <MixinDemo />
  </div>
</template>

<script>
import MixinDemo from "./MixinDemo.vue"
export default {
    components: {
        MixinDemo
    },
 
}
</script>

mixin.js

export default {
    data() {
        return {
            city: '重庆'
        }
    },
    methods: {
        showName() {
            console.log(this.name);
        }
    },
    mounted() {
        console.log('mixin mounted', this.name);
    }
}

MixinDemo.vue

<template>
  <div>
      <p>{{name}} {{major}} {{city}}</p>
      <button @click="showName">显示姓名</button>
  </div>
</template>

<script>
import myMixin from './mixin'
export default {
    mixins: [myMixin],//可以添加多个,会自动合并起来
    data () {
        return {
            name: 'lwj',
            major: 'web'
        }
    },
    mounted () {
        console.log('component mounted',this.name);
    }
}
</script>


 29. vuex

对于这一部分可参考:vuex相关知识


 

30. vue响应式

组件data的数据一旦变化,立刻触发视图的更新

核心API--Object.defineProperty(vue3.0前)

vue3.0中是用proxy实现响应式

proxy兼容性不好,且无法polyfill

Object.defineProperty的基本用法:

监听对象,

//触发更新视图
function updateView() {
    console.log('视图更新');
};

//重新定义属性,监听起来
function defineReactive(target, key, value) {
    //深度监听
    observer(value);

    //核心API
    Object.defineProperty(target, key, {
        get() {
            return value;
        },
        set(newValue) {
            if (newValue !== value) {
                //深度监听
                observer(newValue);

                //设置新值
                //注意,value 一直在闭包中,此处设置完之后,再get时也是会获取最新的值
                value = newValue;

                //触发更新视图
                updateView();
            }
        }
    })
}

//监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        //不是对象或数组
        return target;
    }

    //重新定义各个属性(for in 也可遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
};

//准备数据
const data = {
    name: 'zs',
    age: 20,
    info: {
        address: '重庆' //需要深度监听
    }
};

//监听数据
observer(data);

//测试
data.name = 'ls';
data.age = 21;
data.x = '100'; //新增属性,监听不到--所以有vue.set
delete data.name //删除属性,监听不到--所以有vue.delete
data.info.address = '上海'; //深度监听 

监听数组,

Object.defineProperty不能监听数组变化

需要重新定义一原型,重写push  pop等方法,以此来实现监听

proxy可以原生支持监听数组变化

//触发更新视图
function updateView() {
    console.log('视图更新');
};

//重新定义数组原型
const oldArrayProperty = Array.prototype;
//创建新对象,原型指向 oldArrayProperty,再扩展新的方法时不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice', 'slice', 'split'].forEach(mothodName => {
    arrProto[mothodName] = function() {
        updateView(); //触发视图更新
        oldArrayProperty[mothodName].call(this, ...arguments);
        //等价于
        // Array.prototype.push.call(this, ...arguments);
    };
});

//重新定义属性,监听起来
function defineReactive(target, key, value) {
    //深度监听
    observer(value);

    //核心API
    Object.defineProperty(target, key, {
        get() {
            return value;
        },
        set(newValue) {
            if (newValue !== value) {
                //深度监听
                observer(newValue);

                //设置新值
                //注意,value 一直在闭包中,此处设置完之后,再get时也是会获取最新的值
                value = newValue;

                //触发更新视图
                updateView();
            }
        }
    })
};

//监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        //不是对象或数组
        return target;
    }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto;
    }

    //重新定义各个属性(for in 也可遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
};

//准备数据
const data = {
    name: 'zs',
    age: 20,
    info: {
        address: '重庆' //需要深度监听
    },
    nums: [10, 20, 50]
};

//监听数据
observer(data);

//测试
//监听对象
// data.name = 'ls';
// data.age = 21;
// data.x = '100'; //新增属性,监听不到--所以有vue.set
// delete data.name //删除属性,监听不到--所以有vue.delete
// data.info.address = '上海'; //深度监听 

// 监听数组
data.nums.push(4); //监听数组

Object.defineProperty的缺点:

深度监听,需要递归到底,一次性计算量大

无法监听新增/删除属性,因此我们需要Vue.set 和 Vue.delete这两个API去做

无法元素监听数组,需要特殊处理


31. 虚拟DOM(Virtual DOM简称VDOM)

操作操作是非常耗费性能的,以前是用jQuery,可以自行控制DOM操作的时机,手动调整

VDOM--用JS模拟DOM结构,计算出最小的变更,操作DOM

vdom的核心价值在于:减少DOM渲染的范围


32. diff算法

参考网址:

GitHub - snabbdom/snabbdom: A virtual DOM library with focus on simplicity, modularity, powerful features and performance.A virtual DOM library with focus on simplicity, modularity, powerful features and performance. - GitHub - snabbdom/snabbdom: A virtual DOM library with focus on simplicity, modularity, powerful features and performance.https://github.com/snabbdom/snabbdom

diff是vdom中最核心、最关键的部分

diff算法能在日常使用vue/react中体现出现(如:key)

diff即对比,是一个广泛的概念

只比较同一层的,相同的进行比较,不相同的直接删除重建


33. 模板编译

模板开发是vue开发中最常用的部分,即与使用相关联的原理

模板不是html,有指令、插值、JS表达式,能实现判断、循环等,但是 html 是标记语言,只有JS才能实现判断、循环。因此,模板一定是转换为某种JS代码,这样的一个过程就是模板编译

vue template complier 将模板编译成 render 函数,执行 render 函数生成 vnode

先来了解一下js里的with语法:

        const obj = {
            a: 100,
            b: 200
        };
        //使用 with,能改变 {} 内自由变量的查找方式
        //将 {} 内自由变量当做 obj 的属性来查找
        with(obj) {
            console.log(a); //100
            console.log(b); //200
            console.log(c); //c is not undefined
        }

with语法:

  • 改变 {} 内自由变量的查找规则,当做 obj 的属性来查找
  • 如果找不到匹配的 obj 属性,就会报错
  • with 要谨慎使用,它打破了作用域规则,易读性变差

通过 cnpm install vue-template-compiler --save 安装模板

const compiler = require('vue-template-compiler');

//插值
// const template = `<p>{{message}}</p>`;
//with(this){return _c('p',[_v(_s(message))])}


//表达式
// const template = `<p>{{flag ? message : 'no message found'}}</p>`;
//with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}


//属性和动态属性
// const template = `
//     <div id="div1" class="container">
//         <img :src="imgUrl" />
//     </div>
// `;
//with(this){return _c('div',{staticClass:"container",attrs:{"id":"div1"}},[_c('img',{attrs:{"src":imgUrl}})])}


//条件
// const template = `
//     <div>
//         <p v-if="flag === 'a'">A</p>
//         <p v-else>B</p>
//     </div>
// `;
//with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}


//循环
// const template = `
//     <ul>
//         <li v-for="item in list" :key="item.id">{{item.title}}</li>
//     </ul>
// `;
//with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}


//事件
// const template = `
//     <button @click="clickHandler">submit</button>
// `;
//with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}


//v-model
const template = `
    <input type="text" v-model="name" />
`;
//with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},
//domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

//编译
const res = compiler.compile(template);
console.log(res.render);


//_c -- createElement(也就是h函数)
//_v -- createTextVNode
//_s toString
// _l -- renderList
//_o -- markOnce
//_n -- toNmber
// _t -- renderSlot
//_q -- looseEqual
// _i -- looseIndexOf
// _m -- renderStatic
// _f -- resolveFilter
//_k -- checkKeyCodes
//_b --  bindObjectProps
//_e -- createEmptyVNode
// _u--resolveScopedSlots
//_g-- bindObjectListeners
// _d --bindDynamicKeys
// _p -- prependModifier


34. 组件渲染/更新的过程

初次渲染过程:

  1. 解析模板为 render 函数。如果在开发环境下集成webpack用vue-loader,这个是在开发环境下做完的,在编译或打包的时候就已经完成了,那么在页面刷新执行的时候其实就已经有了render函数;在项目里面大部分情况下是在开发环境下就完成了的,如果我们自己做一些小demo直接去引用去写的话,那么就也有可能是在浏览器中去执行的
  2. 触发响应式,监听data属性getter、setter。我们对组件里的data去执行响应式的监听,监听属性的getter、setter以及数组,这是触发响应式
  3. 执行render函数,生成vnode,然后再执行patch(elem,vnode);elem--组件对应的节点,那么这个页面就渲染上了

更新过程:

  • 修改data,触发setter(此前在getter中已经被监听了)
  • 此时若去修改的话机会重新执行render函生成newVnode
  • 然后再进行patch(vnode,newVnode)

异步渲染:

回顾$nextTick

汇总data的修改,一次性更新视图

减少DOM操作次数,提高性能


35. 路由

a.如何用JS实现hash路由 :

网页 url 组成部分:

hash变化会触发网页跳转,即浏览器的前进、后退

hash变化不会刷新页面,SPA(单页面应用)必须的特点

hash永远不会提交到server端(前端自生自灭)

通过 window.onhashchange 进行监听

    <p>hash test</p>
    <button id="btn1">修改 hash</button>
        <script>
        //hash变化包括:JS修改url、手动修改url的hash、浏览器的前进和后退
        window.onhashchange = (e) => {
            console.log('old url', e.oldURL);
            console.log('new url', e.newURL);

            console.log('hash', location.hash);
        };

        //页面初次加载,获取hash
        document.addEventListener('DOMContentLoaded', () => {
            console.log('hash:', location.hash);
        });

        //js修改url
        document.getElementById('btn1').addEventListener('click', () => {
            location.href = '#/user';
        });
    </script>

如何用JS实现H5 history路由:

用url规范的路由,但跳转时不刷新页面

主要通过:history.pushState、window.onpopstate实现,需要后端支持

    <p>history API test</p>
    <button id="btn1">修改 url</button>

    <script>
        //页面初次加载,获取path
        document.addEventListener('DOMContentLoaded', () => {
            console.log('load:', location.pathname);
        });

        //打开一个新的路由
        document.getElementById('btn1').addEventListener('click', () => {
            const state = {
                name: 'page1'
            };
            console.log('切换路由到', 'page1');
            history.pushState(state, '', 'page1');
        });

        //监听浏览器前进、后退
        window.onpopstate = (e) => {
            console.log('onpopstate', e.state, location.pathname);
        };
    </script>

to B 的系统(也就是一些后台管理系统)推荐用hash,简单易用 ,对url规范不敏感

to C的系统(也就是非后台管理系统),可以考虑选择H5 history,但需要服务端支持

能选择简单的,就别用复杂的,要考虑成本和收益


36. 双向数据绑定v-model的实现原理

v-model一般是放在input输入框元素里,它的value对应的是this.name(value=this.name),而name就是v-model绑定的变量data

绑定v-model之后这个input元素会被添加一个事件,也就是绑定input事件,事件里每次input就会把当前的target.value这个值赋值给this.name(this.name=$event.target.value)

然后data更新会触发re-render,也就实现了所谓的双向绑定


37. 为何组件data必须是一个函数

看似export default{}导出的是一个对象,实际上.vue文件在编译后这个vue实际上就是一个class(类),这个组件是个class,在每个地方使用这个组件的时候相当于对这个类进行实例化,在实例化的时候去执行这个data,如果data不是一个函数的话,那每一个组件的实例数据都一样了,也就是出现了共享。如果data是一个函数,那么在不同组件中实例化时就会分别去执行data这个函数,而data这个函数又是在闭包中,自然也就不会相互影响

每个组件都是 Vue 的实例, 为了独立作用域, 不让变量污染别人的变量


38. ajax请求应该放在哪个生命周期

放在mounted里,也就是组件渲染完成、DOM加载完成。因为本质上JS是单线程的,ajax是异步获取数据、异步加载的机制;放在mounted里,当组件渲染完成、DOM加载完成之后再触发ajax请求,请求完后再去渲染数据;如果是放在mounted之前,其实没有什么用,因为虽然在很早之前就拿到了ajax渲染的数据,但是只要是JS没有渲染完,这个数据拿过来也是异步的、处于排队过程中,并不会有什么提前的效果,因此放在mounted之前只会让逻辑看起来更乱


39. 如何将组件所有的props传递给子组件

$props

<User v-bind="$props" />


40. 何时需要使用beforeDestory

解绑自定义事件evetn.$off

清除定时器

解绑自定义的DOM事件,如window scroll等


41. vuex中action和mutation的区别

action中处理异步,mutation不可以

mutation做原子操作

action可以整合多个mutation


42. 如何配置vue-router异步加载


43. 用vnode描述一个DOM结构


44. vue常见的性能优化

合理使用v-show和v-if

合理使用computed(因为computed有缓存)

v-for中加key,以及避免和v-if同时使用(因为v-for优先级高于v-if,那么每次v-for的时候v-if就会重新计算一次,这样就很浪费性能)

自定义事件以及DOM事件要及时销毁(否则可能导致)

合理使用异步组件

合理使用keep-alive

data层级不要太深

使用vue-loader在开发环境中做模板编译(预编译)

前端通用的性能优化,例如图片懒加载等

使用SSR


45. Proxy实现响应式

基本使用:

target:对应data

key:获取谁就设置谁

receiver:其实就是proxyData

const data = {
    name: 'zhangsan',
    age: 20
};

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        const result = Reflect.get(target, key, receiver);
        console.log('get', key);
        return result; //返回结果
    },
    set(target, key, val, receiver) {
        const result = Reflect.set(target, key, val, receiver);
        console.log('set', key, val);
        console.log('result', result);
        return result; //是否设置成功
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key);
        console.log('delete property', key);
        console.log('esult', result);
        return result; //是否删除成功
    }
})

对象:

const data = ['a', 'b', 'c'];

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        //只处理非原型的属性(本身)
        const ownKeys = Reflect.ownKeys(target);
        if (ownKeys.includes(key)) {
            console.log('get', key);
        }

        const result = Reflect.get(target, key, receiver);
        return result; //返回结果
    },
    set(target, key, val, receiver) {
        //不重复修改数据(重复的数据不处理)
        const oldVal = target[key];
        if (val === oldVal) {
            return true
        }

        const result = Reflect.set(target, key, val, receiver);
        console.log('set', key, val);
        return result; //是否设置成功
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key);
        console.log('delete property', key);
        console.log('esult', result);
        return result; //是否删除成功
    }
})

 数组:

 

 Reflect的作用:

和Proxy能力一一对应

规范化、标准化、函数式

替代掉Object上的工具函数

Proxy实现响应式:

//创建响应式
function reactive(target = {}) {
    if (typeof target !== 'object' || target == null) {
        //不是对象或数组,则返回
        return target;
    }

    //代理配置
    const proxyConf = {
        get(target, key, receiver) {
            //只处理非原型的属性(本身)
            const ownKeys = Reflect.ownKeys(target);
            if (ownKeys.includes(key)) {
                console.log('get', key);
            }

            const result = Reflect.get(target, key, receiver);

            return reactive(result); //深度监听
        },
        set(target, key, val, receiver) {
            //不重复修改数据(重复的数据不处理)
            if (val === target[key]) {
                return true
            }

            const ownKeys = Reflect.ownKeys(target);
            if (ownKeys.includes(key)) {
                console.log('已有的key', key);
            } else {
                console.log('新增的key', key);
            }

            const result = Reflect.set(target, key, val, receiver);
            console.log('set', key, val);
            return result; //是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key);
            console.log('delete property', key);
            return result; //是否删除成功
        }
    };

    //生成代理对象
    const observed = new Proxy(target, proxyConf);
    return observed;
}

//测试数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        city: '重庆',
        a: {
            b: {
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        }
    }
};

const proxyData = reactive(data);

深度监听,性能更好

可监听、新增、删除属性

可监听数组变化

Proxy能规避ObjectefineProperty的问题,但是无法兼容所有浏览器、无法polyfill


46. 基于vue实现一个购物车(组件结构,vuex state 数据结构)

1.state数据结构设计

用数据描述所有的内容

数据要结构化,易于程序操作(遍历、查找)

数据要可扩展,以便增加新的功能

例如:


2.组件设计:

从功能上拆分层次:也就是说我们的组件是分层的,这个分层需要从功能上去拆分

尽量让组件原子化:所谓原子化也就是一个组件尽量只做一个功能,也叫稀松复杂度,让组件去稀松。整个功能看上去很复杂,但是我们通过将组件做成原子化,让每个组件都实现其中的一点功能,那么组件就会变得很简单

区分容器组件(只管理数据)&UI组件(只显示视图):一般来说数据管理的容器组件是整个顶级组件也就是最外层的那个组件

例如设计一个todoList:

3.Vue实现购物车

data数据结构设计,

用数据描述所有的内容

数据要结构化,易于程序操作(遍历、查找)

数据要可扩展,以便增加新的功能

 注意:这里的“加入购物车”、“增加、减少”、以及“总价”其实是一个功能

组件设计和组件通讯,

从功能上拆分层次:也就是说我们的组件是分层的,这个分层需要从功能上去拆分

尽量让组件原子化:所谓原子化也就是一个组件尽量只做一个功能,也叫稀松复杂度,让组件去稀松。整个功能看上去很复杂,但是我们通过将组件做成原子化,让每个组件都实现其中的一点功能,那么组件就会变得很简单

区分容器组件(只管理数据)&UI组件(只显示视图):一般来说数据管理的容器组件是整个顶级组件也就是最外层的那个组件

 

参考示例:Vue实现购物车案例https://blog.csdn.net/weixin_43285360/article/details/121912058https://blog.csdn.net/weixin_43285360/article/details/121912058


47. 请说下封装 vue 组件的过程

​ 首先,组件可以提升整个项目的开发效率。能够把页面抽象成多个相对独立的模块,解决了我们传统项目开发:效率低、难维护、复用性等问题。

  • 分析需求:确定业务需求,把页面中可以复用的结构,样式以及功能,单独抽离成一个组件,实现复用

  • 具体步骤:Vue.component 或者在new Vue配置项components中, 定义组件名, 可以在props中接受给组件传的参数和值,子组件修改好数据后,想把数据传递给父组件。可以采用$emit方法。


48.  讲一下组件的命名规范

​ 给组件命名有两种方式(在Vue.Component/components时),一种是使用链式命名"my-component",一种是使用大驼峰命名"MyComponent",

​ 因为要遵循W3C规范中的自定义组件名 (字母全小写且必须包含一个连字符),避免和当前以及未来的 HTML 元素相冲突


49. Vue 的 nextTick 的原理是什么?

​ 1. 为什么需要 nextTick ,Vue 是异步修改 DOM 的并且不鼓励开发者直接接触 DOM,但有时候业务需要必须对数据更改--刷新后的 DOM 做相应的处理,这时候就可以使用 Vue.nextTick(callback)这个 api 了。

​ 2. 理解原理前的准备 首先需要知道事件循环中宏任务和微任务这两个概念,常见的宏任务有 script, setTimeout, setInterval, setImmediate, I/O, UI rendering 常见的微任务有 process.nextTick(Nodejs),Promise.then(), MutationObserver;

​ 3. 理解 nextTick 的原理正是 vue 通过异步队列控制 DOM 更新和 nextTick 回调函数先后执行的方式。如果大家看过这部分的源码,会发现其中做了很多 isNative()的判断,因为这里还存在兼容性优雅降级的问题。可见 Vue 开发团队的深思熟虑,对性能的良苦用心。


50. vue生命周期总共分为几个阶段?

Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue 的生命周期。

1)beforeCreate

​ 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

2)created

​ 在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer), 属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。

3)beforeMount

​ 在挂载开始之前被调用:相关的 render 函数首次被调用。

4)mounted

​ el 被新创建的 vm.替换,并挂载到实例上去之后调用该钩子。如果实例挂载了一个文档内元素,当被调用时el 也在文档内。

5)beforeUpdate

​ 数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务端进行。

6)updated

​ 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

7)activated

​ keep-alive 组件激活时调用。该钩子在服务器端渲染期间不被调用。

8)deactivated

​ keep-alive 组件停用时调用。该钩子在服务器端渲染期间不被调用。

9)beforeDestroy

​ 实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。

10)destroyed

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

11)errorCaptured(2.5.0+ 新增)

​ 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。


51. 第一次加载页面会触发哪几个钩子函数?(必会)

当页面第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子函数


52. 跟keep-alive有关的生命周期是哪些?

前言:在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染,在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染

生命周期函数:在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。

1、activated钩子:在在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。

2、Activated钩子调用时机:*第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用,并且给因为组件被缓存了,再次进入缓存路由、组件时,不会触发这些钩子函数,beforeCreate created beforeMount mounted 都不会触发

1、deactivated钩子:组件被停用(离开路由)时调用。

2、deactivated钩子调用时机:使用keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了,这个钩子可以看作beforeDestroy的替代,如果你缓存了组件,要在组件销毁的的时候做一些事情,可以放在这个钩子里,组件内的离开当前路由钩子beforeRouteLeave => 路由前置守卫 beforeEach =>全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)


53. 自定义指令(v-check、v-focus)的方法有哪些?它有哪些钩子函数?还有哪些钩子函数参数?

​ 全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives

​ 钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)

​ 钩子函数参数:el、binding


54.  is这个特性你有用过吗?主要用在哪些方面?

 1)动态组件

​ <component :is="componentName"></component>, componentName可以是在本页面已经注册的局部组件名和全局组件名,也可以是一个组件的选项对象。 当控制componentName改变时就可以动态切换选择组件。

 2)is的用法

​ 有些HTML元素,诸如 <ul>、<ol>、<table>和<select>,对于哪些元素可以出现在其内部是有严格限制的。

​ 而有些HTML元素,诸如 <li>、<tr> 和 <option>,只能出现在其它某些特定的元素内部。

​ <ul>

​ <card-list></card-list>

​ </ul>

​ 所以上面<card-list></card-list>会被作为无效的内容提升到外部,并导致最终渲染结果出错。应该这么写:

​ <ul>

​ <li is="cardList"></li>

​ </ul>


55. 路由之间是怎么跳转的?有哪些方式

1、<router-link to="需要跳转到页面的路径">

2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面

3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面

4、this.$touter.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数


56. vue-router怎么配置路由

在vue中配置路由分为5个步骤,分别是:

  1. 引入vue-router.js

  2. 配置路由path和组件, 和生成路由对象

  3. 把路由对象配置到new Vue中router选项下

  4. 页面使用<router-view></router-view> 承载路由

  5. <router-link to="要跳转的路径"></router-link> 设置路由导航(声明式导航方式/编程式跳转)


57.  vue-router的钩子函数都有哪些

关于vue-router中的钩子函数主要分为3类

全局钩子函数要包含beforeEach

beforeEach函数有三个参数,分别是:

​ to:router即将进入的路由对象​ from:当前导航即将离开的路由​ next:function,进行管道中的一个钩子,如果执行完了,则导航的状态就是 confirmed (确认的)否则为false,终止导航。

单独路由独享组件

​ beforeEnter,

组件内钩子

beforeRouterEnter, beforeRouterUpdate, beforeRouterLeave


58. 路由传值的方式有哪几种

Vue-router传参可以分为两大类,分别是编程式的导航 router.push和声明式的导航

router.push

字符串:直接传递路由地址,但是不能传递参数

​ this.$router.push("home")

​ 对象:

​ 命名路由 这种方式传递参数,目标页面刷新会报错 - name+params

​ this.$router.push({name:"news",params:{userId:123})

​ 查询参数 和path配对的是query

​ this.$router.push({path:"/news',query:{uersId:123})

​ 接收参数 this.$route.query

声明式导航

​ 字符串 <router-link to:"news"></router-link>

​ 命名路由 <router-link :to:"{name:'news',params:{userid:1111}}"></route-link>

​ 还可以to="/path/值" - 需要提前在路由 规则里值 /path/:key

​ 查询参数 <router-link :to="{path:'/news',query:{userId:1111}}"></router-link>

​ 还可以to="/path?key=value


59.  怎么定义vue-router的动态路由?怎么获取传过来的动态参数?

动态路由指的就是path路径上传智, 前提需要路由规则了提前配置/path/:key名, 可以写多个用/隔开, 获取使用$route.params.key名来提取对应用路径传过来的值


60.  Vue的路由实现模式:hash模式和history模式

hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用 window.location.hash 读取。特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。

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


61. 请说出路由配置项常用的属性及作用

​ 路由配置参数:

​ path : 跳转路径​ component : 路径相对于的组件​ name:命名路由​ children:子路由的配置参数(路由嵌套)​ props:路由解耦​ redirect : 重定向路由


62. 编程式导航使用的方法以及常用的方法(必会)

​ 路由跳转 : this.$router.push()​ 路由替换 : this.$router.replace()​ 后退: this.$router.back()​ 前进 :this.$router.forward()


63. Vue如何去除URL中的#(必会)

​ vue-router 默认使用 hash 模式,所以在路由加载的时候,项目中的 URL 会自带 “#”。如果不想使用 “#”, 可以使用 vue-router 的另一种模式 history:new Router ({ mode : 'history', routes: [ ]})

​ 需要注意的是,当我们启用 history 模式的时候,由于我们的项目是一个单页面应用,所以在路由跳转的时候,就会出现访问不到静态资源而出现 “404” 的情况,这时候就需要服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 “index.html” 页面


64. 说一下你在vue中踩过的坑

​ 1、第一个是给对象添加属性的时候,直接通过给data里面的对象添加属性然后赋值,新添加的属性不是响应式的

​ 【解决办法】通过Vue.set(对象,属性,值)这种方式就可以达到,对象新添加的属性是响应式的

2、 在created操作dom的时候,是报错的,获取不到dom,这个时候实例vue实例没有挂载

​ 【解决办法】通过:Vue.nextTick(回调函数进行获取)


65. $route和​$router的区别?

$route是路由信息对象,包括‘path,hash,query,fullPath,matched,name’等路由信息参数;$router是路由实例对象,包括了路由的跳转方法,实例对象等

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白小白从不日白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值