2022前端经典vue面试题(持续更新中)

本文深入探讨Vue面试中的关键知识点,包括从零构建Vue路由的思路、Vue2.x与Vue3.x的响应式原理差异、虚拟DOM的优缺点。此外,还讲解了computed与watch的区别、Vue组件通信方式、双向数据绑定原理等。文章旨在帮助读者全面理解Vue核心概念,并为面试做好准备。
摘要由CSDN通过智能技术生成

如果让你从零开始写一个vue路由,说说你的思路

思路分析:

首先思考vue路由要解决的问题:用户点击跳转链接内容切换,页面不刷新。

  • 借助hash或者history api实现url跳转页面不刷新
  • 同时监听hashchange事件或者popstate事件处理跳转
  • 根据hash值或者state值从routes表中匹配对应component并渲染

回答范例:

一个SPA应用的路由需要解决的问题是 页面跳转内容改变同时不刷新 ,同时路由还需要以插件形式存在,所以:

  1. 首先我会定义一个createRouter函数,返回路由器实例,实例内部做几件事
  • 保存用户传入的配置项
  • 监听hash或者popstate事件
  • 回调里根据path匹配对应路由
  1. router定义成一个Vue插件,即实现install方法,内部做两件事
  • 实现两个全局组件:router-linkrouter-view,分别实现页面跳转和内容显示
  • 定义两个全局变量:$route$router,组件内可以访问当前路由和路由器实例

前端vue面试题详细解答

Vue3.0 和 2.0 的响应式原理区别

Vue3.x 改用 Proxy 替代 Object.defineProperty。因为 Proxy 可以直接监听对象和数组的变化,并且有多达 13 种拦截方法。

相关代码如下

import {
    mutableHandlers } from "./baseHandlers"; // 代理相关逻辑
import {
    isObject } from "./util"; // 工具方法

export function reactive(target) {
   
  // 根据不同参数创建不同响应式对象
  return createReactiveObject(target, mutableHandlers);
}
function createReactiveObject(target, baseHandler) {
   
  if (!isObject(target)) {
   
    return target;
  }
  const observed = new Proxy(target, baseHandler);
  return observed;
}

const get = createGetter();
const set = createSetter();

function createGetter() {
   
  return function get(target, key, receiver) {
   
    // 对获取的值进行放射
    const res = Reflect.get(target, key, receiver);
    console.log("属性获取", key);
    if (isObject(res)) {
   
      // 如果获取的值是对象类型,则返回当前对象的代理对象
      return reactive(res);
    }
    return res;
  };
}
function createSetter() {
   
  return function set(target, key, value, receiver) {
   
    const oldValue = target[key];
    const hadKey = hasOwn(target, key);
    const result = Reflect.set(target, key, value, receiver);
    if (!hadKey) {
   
      console.log("属性新增", key, value);
    } else if (hasChanged(value, oldValue)) {
   
      console.log("属性值被修改", key, value);
    }
    return result;
  };
}
export const mutableHandlers = {
   
  get, // 当获取属性时调用此方法
  set, // 当修改属性时调用此方法
};

虚拟 DOM 的优缺点?

优点:

  • 保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作的实现必须是普适的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化的情况下,依然可以提供还不错的性能,即保证性能的下限;
  • 无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
  • 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。

缺点:

  • 无法进行极致优化: 虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化。

computed和watch区别

  1. 当页面中有某些数据依赖其他数据进行变动的时候,可以使用计算属性computed

Computed本质是一个具备缓存的watcher,依赖的属性发生变化就会更新视图。 适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理

<template>{
   {
   fullName}}</template>
export default {
   
    data(){
   
        return {
   
            firstName: 'zhang',
            lastName: 'san',
        }
    },
    computed:{
   
        fullName: function(){
   
            return this.firstName + ' ' + this.lastName
        }
    }
}
  1. watch用于观察和监听页面上的vue实例,如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch为最佳选择

Watch没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。当我们需要深度监听对象中的属性时,可以打开deep:true选项,这样便会对对象中的每一项进行监听。这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用unWatch手动注销

<template>{
   {
   fullName}}</template>
export default {
   
    data(){
   
        return {
   
            firstName: 'zhang',
            lastName: 'san',
            fullName: 'zhang san'
        }
    },
    watch:{
   
        firstName(val) {
   
            this.fullName = val + ' ' + this.lastName
        },
        lastName(val) {
   
            this.fullName = this.firstName + ' ' + val
        }
    }
}

computed:

  • computed是计算属性,也就是计算值,它更多用于计算值的场景
  • computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算
  • computed适用于计算比较消耗性能的计算场景

watch:

  • 更多的是「观察」的作用,类似于某些数据的监听回调,用于观察props $emit或者本组件的值,当数据变化时来执行回调进行后续操作
  • 无缓存性,页面重新渲染时值不变化也会执行

小结:

  • computedwatch都是基于watcher来实现的
  • computed属性是具备缓存的,依赖的值不发生变化,对其取值时计算属性方法不会重新执行
  • watch是监控值的变化,当值发生变化时调用其对应的回调函数
  • 当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed
  • 如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化

回答范例

思路分析

  • 先看computed, watch两者定义,列举使用上的差异
  • 列举使用场景上的差异,如何选择
  • 使用细节、注意事项
  • vue3变化

computed特点:具有响应式的返回值

const count = ref(1)
const plusOne = computed(() => count.value + 1)

watch特点:侦测变化,执行回调

const state = reactive({
    count: 0 })
watch(
  () => state.count,
  (count, prevCount) => {
   
    /* ... */
  }
)

回答范例

  1. 计算属性可以从组件数据派生出新数据,最常见的使用方式是设置一个函数,返回计算之后的结果,computedmethods的差异是它具备缓存性,如果依赖项不变时不会重新计算。侦听器可以侦测某个响应式数据的变化并执行副作用,常见用法是传递一个函数,执行副作用,watch没有返回值,但可以执行异步操作等复杂逻辑
  2. 计算属性常用场景是简化行内模板中的复杂表达式,模板中出现太多逻辑会是模板变得臃肿不易维护。侦听器常用场景是状态变化之后做一些额外的DOM操作或者异步操作。选择采用何用方案时首先看是否需要派生出新值,基本能用计算属性实现的方式首选计算属性.
  3. 使用过程中有一些细节,比如计算属性也是可以传递对象,成为既可读又可写的计算属性。watch可以传递对象,设置deepimmediate等选项
  4. vue3watch选项发生了一些变化,例如不再能侦测一个点操作符之外的字符串形式的表达式; reactivity API中新出现了watchwatchEffect可以完全替代目前的watch选项,且功能更加强大

基本使用

// src/core/observer:45;

// 渲染watcher  /  computed watcher  /  watch
const vm = new Vue({
   
    el: '#app',
    data: {
   
        firstname:'张',
        lastname:'三'
    },
    computed:{
    // watcher  =>   firstname lastname
        // computed 只有取值时才执行

        // Object.defineProperty .get
        fullName(){
    // firstName lastName 会收集fullName计算属性
            return this.firstname + this.lastname
        }
    },
    watch:{
   
        firstname(newVal,oldVal){
   
            console.log(newVal)
        }
    }
});

setTimeout(() => {
   
    debugger;
    vm.firstname = '赵'
}, 1000);

相关源码

// 初始化state
function initState (vm: Component) {
   
  vm._watchers = []
  const opts = vm.$options
  if (opts.props) initProps(vm, opts.props)
  if (opts.methods) initMethods(vm, opts.methods)
  if (opts.data) {
   
    initData(vm)
  } else {
   
    observe(vm._data = {
   }, true /* asRootData */)
  }

  // 初始化计算属性
  if (opts.computed) initComputed(vm, opts.computed) 

  // 初始化watch
  if (opts.watch && opts.watch !== nativeWatch) {
    
    initWatch(vm, opts.watch)
  }
}

// 计算属性取值函数
function createComputedGetter (key) {
   
  return function computedGetter () {
   
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
   
      if (watcher.dirty) {
    // 如果值依赖的值发生变化,就会进行重新求值
        watcher.evaluate(); // this.firstname lastname
      }
      if (Dep.target) {
    // 让计算属性所依赖的属性 收集渲染watcher
        watcher.depend()
      }
      return watcher.value
    }
  }
}

// watch的实现
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
   
    const vm: Component = this
    debugger;
    if (isPlainObject(cb)) {
   
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {
   }
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options) // 创建watcher,数据更新调用cb
    if (options.immediate) {
   
      try {
   
        cb.call(vm, watcher.value)
      } catch (error) {
   
        handleError(error, vm, `callback for immediate watcher "${
     watcher.expression}"`)
      }
    }
    return function unwatchFn () {
   
      watcher.teardown()
    }
}

Vue 组件通讯有哪几种方式

  1. props 和 e m i t 父组件向子组件传递数据是通过 p r o p 传递的,子组件传递数据给父组件是通过 emit 父组件向子组件传递数据是通过 prop 传递的,子组件传递数据给父组件是通过 emit父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过emit 触发事件来做到的
  2. p a r e n t , parent, parent,children 获取当前组件的父组件和当前组件的子组件
  3. a t t r s 和 attrs 和 attrslisteners A->B->C。Vue 2.4 开始提供了 a t t r s 和 attrs 和 attrslisteners 来解决这个问题
  4. 父组件中通过 provide 来提供变量,然后在子组件中通过 inject 来注入变量。(官方不推荐在实际业务中使用,但是写组件库时很常用)
  5. $refs 获取组件实例
  6. envetBus 兄弟组件数据传递 这种情况下可以使用事件总线的方式
  7. vuex 状态管理

双向数据绑定的原理

Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:

  1. 需要observe的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化
  2. compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
  3. Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退。
  4. MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果。

怎样理解 Vue 的单向数据流

数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会 防止从子组件意外改变父级组件的状态 ,从而导致你的应用的数据流向难以理解

注意 :在子组件直接用 v-model 绑定父组件传过来的 prop 这样是不规范的写法 开发环境会报警告

如果实在要改变父组件的 prop 值,可以在 data 里面定义一个变量 并用 prop 的值初始化它 之后用$emit 通知父组件去修改

有两种常见的试图改变一个 prop 的情形 :

  1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data 属性并将这个 prop用作其初始值
props: ['initialCounter'],
data: function () {
   
  return {
   
    counter: this.initialCounter
  }
}
  1. 这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
   
  normalizedSize: function () {
   
    return this.size.trim
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值