Vue

126 阅读7分钟

1.v-show和v-if的区别

v-show通过css的display属性控制显示和隐藏
v-if是组件的渲染和销毁,而不是显示和隐藏
适用场景:频繁切换显示状态用v-show,否则用v-if

2.为何在v-for中使用key

必须用key,且不能是index和random
diff算法中通过tag和key来判断是否是sameNode
减少渲染次数,提升渲染性能

3.描述Vue组件生命周期(父子组件)

单组件生命周期图
父子组件生命周期关系

4.Vue组件如何通讯

父子组件: props  this.$emit
跨级组件: 自定义事件  event.$emit  event.$on...
Vuex

5.描述组件渲染和更新过程

一个组件渲染到页面,修改data触发更新(数据驱动视图)
其背后原理是什么,需要掌握哪些要点
考察对流程了解的全面程度
    1.响应式:监听data属性getter setter(包括数组)
    2.模板编译模板到render函数,再到vnode
    3.vdom:patch(elem,vnode) 和patch(vnode,newVnode)
组件渲染/更新过程
    1.初次渲染过程
    2.更新过程
    3.异步渲染
初次渲染过程
    解析模板为render函数(或在开发环境已完成,vue-loader)
    触发响应式,监听data属性getter setter
    触发render函数,生成vnode, patch(elem,vnode)
        执行render函数会触发getter    
更新过程
    修改data,触发setter(此前在getter中已被监听)
    重新执行render函数,生成newVnode
    patch(vnode,newVnode)
异步渲染
    回顾$nextTick
    汇总data的修改,一次性更新视图
    减少DOM操作次数,提高性能
模板的渲染和更新过程
    渲染和响应式的关系
    渲染和模板编译的关系
    渲染和vdom的关系

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

input元素的value等于this.name
绑定input事件this.name=$event.target.value
data更新触发render

7.对MVVM的理解

数据驱动视图  Vue
setState     React

8.computed有何特点

缓存,data不变不会重新计算
提高性能

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

重复创建实例造成多实例共享一个数据对象

10.ajax请求必须放在哪个生命周期里

mounted
js是单线程,ajax异步获取数据
放在mounted之前没用,只会让逻辑更加混乱

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

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

12.自定义v-model

父组件

<template>
  <div class="about">
    <p>{{name}}</p>
    <MyInput v-model="name"/>
  </div>
</template>

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

子组件

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

<script>
export default {
    model:{
        prop:'text',
        event:'change'
    },
    props:{
        text:String
    }
}
</script>

总结:

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

13.多个组件有相同逻辑,如何抽离

mixin
    1.多个组件有相同的逻辑,抽离出来
    2.mixin并不是完美的解决方案,会有一些问题
    3.Vue3提出的Composition API旨在解决这些问题
mixin的缺点
    1.变量来源不明确,不利于阅读
    2.mixin可能会造成命名冲突
    3.mixin和组件可能出现多对多的关系,复杂度较高

14.何时要使用异步组件

加载大组件
路由异步加载

15.何时使用keep-alive

缓存组件,不需要重复渲染
如多个静态tab页的切换
这也是优化性能的一种方式

16.何时需要使用beforeDestroy

解除自定义事件 event.$off
清除定时器
解除自定义DOM事件,如window.scroll

17.什么是作用域插槽

父组件
<template>
  <my-slot :url="website.url">
    <template v-slot="slotProps">
      {{slotProps.slotData}}
    </template>
  </my-slot>
</template>
子组件
<template>
    <a :href="url">
        <slot :slotData="data">
            默认值
        </slot>
    </a>
</template>

18.Vuex中action和mutation有何区别

action中处理异步,mutation不可以
mutation做原子操作
action可以整合多个mutation

19.Vue-router常用的路由模式

hash
    hash的特点
        hash变化会触发网页跳转,即浏览器的前进、后退
        hash变化不会刷新页面,SPA必需的特点
        hash不会提交到服务端
H5 history(需要服务端配合返回index.html,前端配置404的路由情况)
    url规范的路由,但跳转时不刷新页面
    history.pushState  //切换路由
    window.onpopstate //监听浏览器的前进后退
hash - window.onhashchange  
H5 history - history.pushState 和 window.onpopsatte
两者如何选择
    to B的系统推荐用hash,简单易用,对url规范不敏感
    to C的系统,可以考虑选择H5 history,但需要服务端支持(做seo搜索引擎优化)
    能选择简单的,就别用复杂的,考虑成本和收益

20.如何配置Vue-router异步加载

() => import('../components/xxx.vue')

21.请用vnode描述一个DOM结构

js描述虚拟DOM

<div id="div1" class="container">
    <p>vdom</p>
    <ul style="font-size:20px">
        <li>a</li>
    </ul>
</div>
{
    tag: 'div'
    props: {
        className: 'container',
        id: 'div1'
    }
    children: [
        {
            tag: 'p',
            children: 'vdom'
        },
        {
            tag: 'ul',
            props: { style: 'font-size: 20px' }
            children: [
                {
                    tag: 'li',
                    children: 'a'
                }
                //...
            ]
         }
     ]
}

22.监听data变化的核心API

Object.defineProperty
以及深度监听、监听数组
有何缺点

23.Vue如何监听数组变化

Object.defineProperty不能监听数组变化
重新定义原型,重写push pop等方法,实现监听
Proxy可以原生支持监听数组

24.描述响应式原理

组件data的数据一旦变化,立刻触发视图的更新
核心APIObject.defineProperty
Object.defineProperty的缺点
    1.深度监听需要一次性递归
    2.无法监听新增属性/删除属性(Vue.set/Vue.delete)
    3.无法原生监听数组,需要特殊处理
Proxy代理 Reflect反射
Reflect作用
    和Proxy能力一一对应
    规范化、标准化、函数式
    替代掉Object的工具函数
Object.defineProperty一次性递归完成
Proxy不是一次性递归,get(访问)到哪一层递归到哪一层
    深度监听,性能更好
    可监听新增/删除属性
    可监听数组变化
Proxy能兼容Object.defineProperty的问题
Proxy无法兼容所有浏览器,无法polyfill

25.diff算法的时间复杂度

O(n)
在O(n^3)基础上做了一些调整

26.简述diff算法过程

patch(elem,vnode)和patch(vnode,newVnode)
patchVnode和addVnodes和removeVnodes
updateChildren(key的重要性)

27.Vue为何是异步渲染,$nextTick有何用

异步渲染(以及合并data修改),以提高渲染性能
$nextTick在DOM更新完之后,触发回调

28.Vue常见性能优化方式

合理使用v-show和v-if
合理使用computed
v-for时加key,以及避免和v-if同时使用
自定义事件、DOM事件及时销毁
合理使用异步组件
合理使用keep-alive
data层级不要太深
使用vue-loader在开发环境做模板编译
Webpack层面的优化
前端通用的性能优化:如图片懒加载
使用SSR

29.Proxy实现响应式

Object.defineProperty的缺点
    1.深度监听需要一次性递归
    2.无法监听新增属性/删除属性(Vue.set/Vue.delete)
    3.无法原生监听数组,需要特殊处理
Proxy代理 Reflect反射
Reflect作用
    和Proxy能力一一对应
    规范化、标准化、函数式
    替代掉Object的工具函数
Object.defineProperty一次性递归完成
Proxy不是一次性递归,get(访问)到哪一层递归到哪一层
    深度监听,性能更好
    可监听新增/删除属性
    可监听数组变化
Proxy能兼容Object.defineProperty的问题
Proxy无法兼容所有浏览器,无法polyfill

30.Object.defineProperty的基本用法

监听对象 监听数组
复杂对象,深度监听
几个缺点:
    Object.defineProperty的缺点
        1.深度监听需要一次性递归  原数据的对象属性有多深,就要递归多深
        2.无法监听新增属性/删除属性(Vue.set/Vue.delete)
        3.无法原生监听数组,需要特殊处理

31.vdom(虚拟DOM)

vdom是实现Vue和React的重要基石
diff算法是vdom中最核心、最关键的部分
DOM操作非常耗费性能
    以前使用JQuery,可以自行控制DOM操作的时机,手动调整 
    Vue和React是数据驱动视图,如何有效控制DOM操作
        vdom:用js模拟DOM,计算出最小的变更,操作DOM
通过snabbdom学习vdom
    简洁强大的vdom库,易学易用
    Vue参考它实现的vdom和diff
    Vue重写了vdom代码,优化了性能
    但vdom的基本理念不变

32.snabbdom重点总结

h函数
vnode
patch函数

33.vdom总结

用js模拟DOM结构(vnode)
新旧vndoe对比,得到最小的更新范围更新DOM
数据驱动视图模式下,有效控制DOM操作

34.diff算法

diff即对比,是一个广泛的概念,如linux diff git diff
两个对象也可以做diff 如jiff
两棵树做diff,如这里的vdom diff

树diff的时间复杂度O(n^3)
    1.遍历tree1   2.遍历tree2   3.排序
1000个节点,要计算1亿次,算法不可用

35.diff算法的优化

只比较同一层级,不跨级比较
tag不同,则直接删掉重建,不再深度比较
tag和key,两者都相同,则认为是相同节点,不再深度比较

36.深入diff算法源码

vdom(虚拟DOM)对比的过程 sec children text
    vnode patch函数 patchVnode函数 addVnodes 
    removeVnodes updateChildren函数(key的重要性)     
当面试官问到 讲讲vue的diff算法的时候,应该怎么回答呢?
    首先,我们拿到新旧节点的数组,然后初始化四个指针,分别指向新旧节点的开始位置和结
    束位置,进行两两对比,若是 新的开始节点和旧开始节点相同,则都向后面移动,若是结
    尾节点相匹配,则都前移指针。若是新开始节点和旧结尾节点匹配上了,则会将旧的结束节
    点移动到旧的开始节点前。若是旧开始节点和新的结束节点相匹配,则会将旧开始节点移动
    到旧结束节点的后面。若是上述节点都没配有匹配上,则会进行一个兜底逻辑的判断,判断
    开始节点是否在旧节点中,若是存在则复用,若是不存在则创建。最终跳出循环,进行裁剪
    或者新增,若是旧的开始节点小于旧的结束节点,则会删除之间的节点,反之则是新增新的
    开始节点到新的结束节点。
之前也是对vue的diff算法一知半解,现在对diff算法有了更深刻的理解。
ps: 之前被问过 vue 的diff算法是深度优先遍历还是广度优先算法,在patchVnode过程中会
调用updateChildren,所以 vue 的diff算法是个深度优先算法。

37.vdom和diff总价

vdom核心概念很重要:h、vnode、path、diff、key等
vdom存在的价值更加重要:数据驱动视图,控制DOM操作

38.模板编译

前置知识:JS的with语法
vue template complier 将模板编译为render函数
执行render函数生成vnode
with语法 
const obj = {a:100,b:200}
console.log(obj.a)
console.log(obj.b
console.log(obj.c) //undefined

//使用with,能改变{}内自由变量的查找方式
//将{}内自由变量,当做obj的属性来查找
//如果找不到匹配的obj属性,就会报错
with(obj){
    console.log(a)
    console.log(b)
    console.log(c) //会报错
}
with要慎用,它打破了作用域规则,易读性变差
模板一定是转换为某种JS代码,即编译模板
模板编译为render函数,执行render函数  返回vnode
基于vnode再执行patch和diff
使用webpack vue-loader,会在开发环境下编译模板
vue组件render代替tempalte
React一直都用render(没有模板)