vue面试题大全

188 阅读2分钟

01-v-if和v-for哪个优先级高

<body>
  <div id="demo">
    <h1>v-for和v-if谁的优先级高?应该如何正确使用避免性能问题?</h1>
    <p v-for="child in children" v-if="isFolder">{{child.title}}</p>
  </div>
  <script src="../../dist/vue.js"></script>
  <script>
    // 创建实例
    const app = new Vue({
      el: '#demo',
      data() {
        return {
          children: [
            { title: 'foo' },
            { title: 'bar' },
          ]
        }
      },
      computed: {
        isFolder() {
          return this.children && this.children.length > 0
        }
      },
    });
    console.log(app.$options.render);
  </script>
</body>

src\compiler\codegen\index.js

function genElement(el, state) {
	if (el.for && !el.forProcessed) {
	    return genFor(el, state)
	} else if (el.if && !el.ifProcessed) {
	    return genIf(el, state)
	}
}

两者同级时,渲染函数如下:

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("v-for和v-if谁的优先
级高?应该如何正确使用避免性能问题?")]),_v(" "),
_l((children),function(child){return (isFolder)?_c('p',
[_v(_s(child.title))]):_e()})],2)}
})

_l内包含了isFolder的条件判断 先遍历 所以v-for优先级更加高 两者不同级时,渲染函数如下

(function anonymous(
) {
with(this){return _c('div',{attrs:{"id":"demo"}},[_c('h1',[_v("v-for和v-if谁的优先
级高?应该如何正确使用避免性能问题?")]),_v(" "),
(isFolder)?_l((children),function(child){return _c('p',
[_v(_s(child.title))])}):_e()],2)}
})

结论

  1. 显然v-for优先于v-if被解析(把你是怎么知道的告诉面试官)
  2. 如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能
  3. 要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
  4. 如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项

02-Vue组件data为什么必须是个函数而Vue的根实例则没有此限制?

源码中找答案:src\core\instance\state.js - initData()

<body>
  <div id="demo">
    <h1>vue组件data为什么必须是个函数? </h1>
    <comp></comp>
    <comp></comp>
  </div>
  <script src="../../dist/vue.js"></script>
  <script>
    Vue.component('comp', {
      template: '<div @click="counter++">{{counter}}</div>',
      data: { counter: 0 }
    })
    // 创建实例
    const app = new Vue({
      el: '#demo',
    });
  </script>
</body>

Vue组件可能存在多个实例,如果使用对象形式定义data,则会导致它们共用一个data对象,那么状态 变更将会影响所有组件实例,这是不合理的;采用函数形式定义,在initData时会将其作为工厂函数返 回全新data对象,有效规避多实例之间状态污染问题。而在Vue根实例创建过程中则不存在该限制,也 是因为根实例只能有一个,不需要担心这种情况

03-你知道vue中key的作用和工作原理吗?说说你对它的理解。

源码中找答案:src\core\vdom\patch.js - updateChildren()

<body>
  <div id="demo">
    <p v-for="item in items" :key="item">{{item}}</p>
  </div>
  <script src="../../dist/vue.js"></script>
  <script>
    // 创建实例
    const app = new Vue({
      el: '#demo',
      data: { items: ['a', 'b', 'c', 'd', 'e'] },
      mounted() {
        setTimeout(() => {
          this.items.splice(2, 0, 'f')
        }, 2000);
      },
    });
  </script>
</body>

在这里插入图片描述 不加key 他们都认为是相同的元素 在进行深度递归 更新文本节点会频繁更新 在这里插入图片描述

执行vm._update时,会patchVnode,进一步updateChildren
结论

  1. key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两 个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操 作量,提高性能。
  2. 另外,若不设置key还可能在列表更新时引发一些隐蔽的bug
  3. vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分 它们,否则vue只会替换其内部属性而不会触发过渡效果。

04-你怎么理解vue中的diff算法?

源码分析1:必要性,lifecycle.js - mountComponent() 组件中可能存在很多个data中的key使用 源码分析2:执行方式,patch.js - patchVnode() patchVnode是diff发生的地方,整体策略:深度优先,同层比较 源码分析3:高效性,patch.js - updateChildren()

1.diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即diff),将变化的地方更新在真 实DOM上;另外,也需要diff高效的执行对比过程,从而降低时间复杂度为O(n)。 2.vue 2.x中为了降低Watcher粒度,每个组件只有一个Watcher与之对应,只有引入diff才能精确找到 发生变化的地方。 3.vue中diff执行的时刻是组件实例执行其更新函数时,它会比对上一次渲染结果oldVnode和新的渲染 结果newVnode,此过程称为patch。 4.diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据它们是否拥有子节点或者文 本节点做不同操作;比较两组子节点是算法的重点,首先假设头尾节点可能相同做4次比对尝试,如果 没有找到相同节点才按照通用方式遍历查找,查找结束再按情况处理剩下的节点;借助key通常可以非 常精确找到相同节点,因此整个patch过程非常高效。

05-组件化

总体思路: 组件化定义、优点、使用场景和注意事项等方面展开陈述,同时要强调vue中组件化的一些特点。

源码分析1:组件定义

// 组件定义
Vue.component('comp', {
	template: '<div>this is a component</div>'
})

组件定义,src\core\global-api\assets.js

<template>
	<div>
		this is a component
	</div>
</template>

vue-loader会编译template为render函数,最终导出的依然是组件配置对象。

源码分析2:组件化优点 lifecycle.js - mountComponent() 组件、Watcher、渲染函数和更新函数之间的关系 源码分析3:组件化实现 构造函数,src\core\global-api\extend.js 实例化及挂载,src\core\vdom\patch.js - createElm()

总结

  1. 组件是独立和可复用的代码组织单元。组件系统是 Vue 核心特性之一,它使开发者使用小型、独 立和通常可复用的组件构建大型应用;
  2. 组件化开发能大幅提高应用开发效率、测试性、复用性等;
  3. 组件使用按分类有:页面组件、业务组件、通用组件;
  4. vue的组件是基于配置的,我们通常编写的组件是组件配置而非组件,框架后续会生成其构造函 数,它们基于VueComponent,扩展于Vue;
  5. vue中常见组件化技术有:属性prop,自定义事件,插槽等,它们主要用于组件通信、扩展等;
  6. 合理的划分组件,有助于提升应用性能;
  7. 组件应该是高内聚、低耦合的;
  8. 遵循单向数据流的原则。

06-vue设计理念

在vue的官网上写着大大的定义和特点: 渐进式JavaScript框架 易用、灵活和高效 所以阐述此题的整体思路按照这个展开即可。 渐进式JavaScript框架: 与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易 于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使 用时,Vue 也完全能够为复杂的单页应用提供驱动。

易用性 vue提供数据响应式、声明式模板语法和基于配置的组件系统等核心特性。这些使我们只需要关注应用 的核心业务即可,只要会写js、html和css就能轻松编写vue应用。 灵活性 渐进式框架的最大优点就是灵活性,如果应用足够小,我们可能仅需要vue核心特性即可完成功能;随 着应用规模不断扩大,我们才可能逐渐引入路由、状态管理、vue-cli等库和工具,不管是应用体积还是 学习难度都是一个逐渐增加的平和曲线。 高效性 超快的虚拟 DOM 和 diff 算法使我们的应用拥有最佳的性能表现。 追求高效的过程还在继续,vue3中引入Proxy对数据响应式改进以及编译器中对于静态内容编译的改进 都会让vue更加高效。

07-vue要求组件模版只有一个根元素

从三方面考虑:

  1. . new Vue({el:'#app'})
  2. 单文件组件中,template下的元素div。其实就是"树"状数据结构中的"根"。
  3. diff算法要求的,源码中,patch.js里patchVnode()。

08-MVC、MVP和MVVM更多

在这里插入图片描述 mvc简单理解

  • 这三者都是框架模式,它们设计的目标都是为了解决Model和View的耦合问题。
  • MVC模式出现较早主要应用在后端,如Spring MVC、ASP.NET MVC等,在前端领域的早期也有应用,如Backbone.js。它的优点是分层清晰,缺点是数据流混乱,灵活性带来的维护性问题。
  • MVP模式在是MVC的进化形式,Presenter作为中间层负责MV通信,解决了两者耦合问题,但P层过于臃肿会导致维护问题。
  • MVVM模式在前端领域有广泛应用,它不仅解决MV耦合问题,还同时解决了维护两者映射关系的大量繁杂代码和DOM操作代码,在提高开发效率、可读性同时还保持了优越的性能表现。

10-Vue性能优化方法?

  • 路由懒加载 component: () => import('./Foo.vue')
  • keep-alive缓存页面 包裹router-view
  • 使用v-show复用DOM 特别是渲染的dom比较繁重
  • v-for v-if 不要在同一个标签内使用
  • v-for渲染列表,item.show 为true的才渲染 可以先使用computed 对arr做一个过滤再渲染
  • 只是数据展示,不需要数据响应化,则将请求到的数据进行冻结处理 this.arr = Object.freeze(users)
  • 如果是大数据长列表,可采用虚拟滚动,只渲染少部分区域的内容 vue-virtual-scroller vue-virtual-scroll-list
  • 事件的销毁
Vue 组件销毁时,会自动解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。  
created() {
	this.timer = setInterval(this.refresh, 2000)
},
beforeDestroy() {
	clearInterval(this.timer)
}
  • 图片懒加载
对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 
等到滚动到可视区域后再去加载。
<img v-lazy="/static/img/1.png">

vue-lazyload

  • 第三方插件按需引入
  • 无状态的组件标记为函数式组件
<template functional>
	<div class="cell">
		<div v-if="props.value" class="on"></div>
		<section v-else class="off"></section>
	</div>
</template>
<script>
	export default {
		props: ['value']
	}
</script>
  • 子组件分割 频繁刷新的部分 切割为子组件
  • 变量本地化 多次对一个对象取同一个值 应该先 声明变量存储起来
  • SSR

11-vue3特性

1、更快: 虚拟DOM重写 优化slots的生成 静态树提升 静态属性提升 基于Proxy的响应式系统 2、更小: 通过摇树优化核心库体积 3、更容易维护: TypeScript + 模块化 4、更加友好: 跨平台:编译器核心和运行时核心与平台无关,使得Vue更容易与任何平台(Web、 Android、iOS)一起使用 5、更容易使用 改进的TypeScript支持,编辑器能提供强有力的类型检查和错误及警告 更好的调试支持 独立的响应化模块 Composition API

虚拟 DOM 重写----------期待更多的编译时提示来减少运行时开销,使用更有效的代码来创建虚拟节点。 组件快速路径+单个调用+子节点类型检测

跳过不必要的条件分支 JS引擎更容易优化 在这里插入图片描述

优化slots生成 -----------vue3中可以单独重新渲染父级和子级

确保实例正确的跟踪依赖关系 避免不必要的父子组件重新渲染 在这里插入图片描述

静态树提升(Static Tree Hoisting)---------使用静态树提升,这意味着 Vue 3 的编译器将能够检测到什么是静态的,然后将其提升,从而降低了渲 染成本。

跳过修补整棵树,从而降低渲染成本 即使多次出现也能正常工作 在这里插入图片描述

静态属性提升 ----------------------------- 使用静态属性提升,Vue 3打补丁时将跳过这些属性不会改变的节点。

在这里插入图片描述

基于 Proxy 的数据响应式 ----------- Vue 2的响应式系统使用 Object.defineProperty 的getter 和 setter。Vue 3 将使用 ES2015 Proxy 作为其观察机制,这将会带来如下变化:

组件实例初始化的速度提高100% 使用Proxy节省以前一半的内存开销,加快速度,但是存在低浏览器版本的不兼容 为了继续支持 IE11,Vue 3 将发布一个支持旧观察者机制和新 Proxy 版本的构建

在这里插入图片描述

高可维护性 --------- Vue 3 将带来更可维护的源代码。它不仅会使用 TypeScript,而且许多包被解耦,更加模块化。

12-vue扩展 现有组件

mixin slot

this.$set原理

必须是 响应式式数据 否则只是普通设置值

 defineReactive(ob.value, key, val)
 ob.dep.notify()    queueWatcher

this.$delete原理

必须是响应式数据 否则删除无效

function del (target, key) {   //Vue.prototype.$delete = del
    if (Array.isArray(target)) {
      target.splice(key, 1)
      return
    }
    delete target[key]
    ob.dep.notify()
}

16-nextTick原理

事件循环 + nextTick 将函数放入callbacks, Promise.resolve().then(flushCallbacks)
flushCallbacks是遍历执行callbacks

四种方案 Promise MutationObserver setImmediate setTimeout 两个微任务两个宏任务

17 修改数据 页面也更新 经历了什么?

initState(vm)
initData(vm)
function initData(vm) {
    // vm._data = typeof data === 'function' ? getData(data, vm) : data
    // proxy(vm, `_data`, key)
    observer(data)
}
function observe(value) {
    if (!isObject(value)) { return }// 如果typeof value 不是对象则返回undefined
    return new Observer(value)
}
class Observer {
    constructor(value) {
        this.dep = new Dep()
        value.__ob__ = this
        /**是数组则给数组的__proto__的方法进行拦截改造 
        ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']**/
        this.observeArray(value)// value是数组则 observe(items[i])
        this.walk(value)   //value是对象则 defineReactive(obj, key)
    }
}

export function defineReactive(obj, key, val = obj[key], customSetter) {
    const dep = new Dep()//这个dep和key 一 一 对应的
    let childOb = observe(val) //只要是引用类型就有值
    Object.defineProperty(obj, key, {
        get: function reactiveGetter() {
            if (Dep.target) { // 初始化阶段是没有的   等到挂载时才会有
                dep.depend()//收集依赖
                if (childOb) {
                    childOb.dep.depend()
                    if (Array.isArray(val)) {// 如果是数组 则 observe(items[i])
                        dependArray(val)
                    }
                }
            }
            return val
        },
        set: function reactiveSetter(newVal) {
            val = newVal
            childOb =  observe(newVal)
            dep.notify()
        }
    })
}


在这里插入图片描述

把data里面的所有数组 方法都做了改造

import { def } from '../util/index'
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    ob.dep.notify()
    return result
  })
})

18-vue-router导航钩子

全局路由 beforeEach afterEach

路由内 beforeEnter

组件内 beforeRoute-Enter、Update、Leave

旧组件 beforeRouteLeave beforeEach beforeRouteUpdate 动态路由 路由变化 但匹配的还是一样的组件 beforeEnter 新组件beforeRouteEnter。 afterEach

19-keep-alive

this.cache = Object.create(null)
    this.keys = []
在render函数内部有
if ( // 走这里代表 不用缓存
 	(include && (!name || !matches(include, name))) ||
   (exclude && name && matches(exclude, name))
 ) {
   return vnode
 }

	// 没有key就创建一个key
  const { cache, keys } = this
     const key = vnode.key == null
       ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
       : vnode.key
 if (cache[key]) {
   vnode.componentInstance = cache[key].componentInstance
   remove(keys, key)
   keys.push(key)
 } else { // 需要缓存 但是之前还没有缓存的  将当前的虚拟dom作为候选人
   this.vnodeToCache = vnode
   this.keyToCache = key
 }
在patch内有

  function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
      const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
      // 首次渲染走这里
      if (isDef(i = i.hook) && isDef(i = i.init)) {//存在init钩子,则执行 创建实例并挂载
        i(vnode, false /* hydrating */)
      }
      // 再次渲染走这里
      if (isDef(vnode.componentInstance)) {// 如果组价实例存在
        initComponent(vnode, insertedVnodeQueue)//属性初始化
        insert(parentElm, vnode.elm, refElm)//把上一次的DOM插入到了父元素中
        if (isTrue(isReactivated)) { //keep-alive关心的东西
          reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
        }
        return true
      }
    }
  }

Vue computed 实现

<div id="app">
 <div>{{c}}</div>
  <div>{{c}}</div>
</div>

 data() {
  return {
     a: 1,
     b: 2
   }
 },
computed: {
   c() {
     return this.a * 2 + this.b * 2
   }
 },
 mounted() {      
     this.a = 3
     this.b = 4    
 }

在这里插入图片描述

在这里插入图片描述

watcher底层原理

在这里插入图片描述

v-model底层原理

v-model在内部为不同的输入元素使用不同的属性并抛出不同的事件

text 和 textarea 元素使用 value 属性和 input 事件 checkbox 和 radio 使用 checked 属性和 change 事件 select 字段将 value 作为 prop 并将 change 作为事件

//Parent    
    <Child v-model="num">  {{num}}
//Child
<div @click="">Add</div>  this.$emit('input', this.value + 1)
props: ['value']

在这里插入图片描述