前端必会vue面试题

Vue中如何进行依赖收集?

  • 每个属性都有自己的dep属性,存放他所依赖的watcher,当属性变化之后会通知自己对应的watcher去更新
  • 默认会在初始化时调用render函数,此时会触发属性依赖收集 dep.depend
  • 当属性发生修改时会触发watcher更新dep.notify()

前端必会vue面试题_第1张图片

依赖收集简版

let obj = { name: 'poetry', age: 20 };

class Dep {
    constructor() {
      this.subs = [] // subs [watcher]
    }
    depend() {
      this.subs.push(Dep.target)
    }
    notify() {
      this.subs.forEach(watcher => watcher.update())
    }
}
Dep.target = null;
observer(obj); // 响应式属性劫持

// 依赖收集  所有属性都会增加一个dep属性,
// 当渲染的时候取值了 ,这个dep属性 就会将渲染的watcher收集起来
// 数据更新 会让watcher重新执行

// 观察者模式

// 渲染组件时 会创建watcher
class Watcher {
    constructor(render) {
      this.get();
    }
    get() {
      Dep.target = this;
      render(); // 执行render
      Dep.target = null;
    }
    update() {
      this.get();
    }
}
const render = () => {
    console.log(obj.name); // obj.name => get方法
}

// 组件是watcher、计算属性是watcher
new Watcher(render);

function observer(value) { // proxy reflect
    if (typeof value === 'object' && typeof value !== null)
    for (let key in value) {
        defineReactive(value, key, value[key]);
    }
}
function defineReactive(obj, key, value) {
    // 创建一个dep
    let dep = new Dep();

    // 递归观察子属性
    observer(value);

    Object.defineProperty(obj, key, {
        get() { // 收集对应的key 在哪个方法(组件)中被使用
            if (Dep.target) { // watcher
                dep.depend(); // 这里会建立 dep 和watcher的关系
            }
            return value;
        },
        set(newValue) {
            if (newValue !== value) {
                observer(newValue);
                value = newValue; // 让key对应的方法(组件重新渲染)重新执行
                dep.notify()
            }
        }
    })
}

// 模拟数据获取,触发getter
obj.name = 'poetries'

// 一个属性一个dep,一个属性可以对应多个watcher(一个属性可以在任何组件中使用、在多个组件中使用)
// 一个dep 对应多个watcher 
// 一个watcher 对应多个dep (一个视图对应多个属性)
// dep 和 watcher是多对多的关系

前端vue面试题详细解答

异步组件是什么?使用场景有哪些?

分析

因为异步路由的存在,我们使用异步组件的次数比较少,因此还是有必要两者的不同。

体验

大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们

import { defineAsyncComponent } from 'vue'
// defineAsyncComponent定义异步组件,返回一个包装组件。包装组件根据加载器的状态决定渲染什么内容
const AsyncComp = defineAsyncComponent(() => {
  // 加载函数返回Promise
  return new Promise((resolve, reject) => {
    // ...可以从服务器加载组件
    resolve(/* loaded component */)
  })
})
// 借助打包工具实现ES模块动态导入
const AsyncComp = defineAsyncComponent(() =>
  import('./components/MyComponent.vue')
)

回答范例

  1. 在大型应用中,我们需要分割应用为更小的块,并且在需要组件时再加载它们。
  2. 我们不仅可以在路由切换时懒加载组件,还可以在页面组件中继续使用异步组件,从而实现更细的分割粒度。
  3. 使用异步组件最简单的方式是直接给defineAsyncComponent指定一个loader函数,结合ES模块动态导入函数import可以快速实现。我们甚至可以指定loadingComponenterrorComponent选项从而给用户一个很好的加载反馈。另外Vue3中还可以结合Suspense组件使用异步组件。
  4. 异步组件容易和路由懒加载混淆,实际上不是一个东西。异步组件不能被用于定义懒加载路由上,处理它的是vue框架,处理路由组件加载的是vue-router。但是可以在懒加载的路由组件中使用异步组件

v-if和v-show的区别

  • 手段:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐;
  • 编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
  • 编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;
  • 性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
  • 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换。

什么是递归组件?举个例子说明下?

分析

递归组件我们用的比较少,但是在TreeMenu这类组件中会被用到。

体验

组件通过组件名称引用它自己,这种情况就是递归组件