关于vue.js:前端一面经典vue面试题总结

11次阅读

共计 11187 个字符,预计需要花费 28 分钟才能阅读完成。

个别在哪个生命周期申请异步数据

咱们能够在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。

举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:

  • 能更快获取到服务端数据,缩小页面加载工夫,用户体验更好;
  • SSR 不反对 beforeMount、mounted 钩子函数,放在 created 中有助于一致性。

computed 和 watch 的区别和使用的场景?

computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值;

watch: 更多的是「察看」的作用,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作;

使用场景:

  • 当咱们须要进行数值计算,并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时,都要从新计算;
  • 当咱们须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许咱们执行异步操作 (拜访一个 API),限度咱们执行该操作的频率,并在咱们失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。

Vue 中组件生命周期调用程序说一下

组件的调用程序都是 先父后子 , 渲染实现的程序是 先子后父

组件的销毁操作是 先父后子 ,销毁实现的程序是 先子后父

加载渲染过程

父 beforeCreate-> 父 created-> 父 beforeMount-> 子 beforeCreate-> 子 created-> 子 beforeMount- > 子 mounted-> 父 mounted

子组件更新过程

父 beforeUpdate-> 子 beforeUpdate-> 子 updated-> 父 updated

父组件更新过程

父 beforeUpdate -> 父 updated

销毁过程

父 beforeDestroy-> 子 beforeDestroy-> 子 destroyed-> 父 destroyed

defineProperty 和 proxy 的区别

Vue 在实例初始化时遍历 data 中的所有属性,并应用 Object.defineProperty 把这些属性全副转为 getter/setter。这样当追踪数据发生变化时,setter 会被主动调用。

Object.defineProperty 是 ES5 中一个无奈 shim 的个性,这也就是 Vue 不反对 IE8 以及更低版本浏览器的起因。

然而这样做有以下问题:

  1. 增加或删除对象的属性时,Vue 检测不到。因为增加或删除的对象没有在初始化进行响应式解决,只能通过 $set 来调用Object.defineProperty() 解决。
  2. 无奈监控到数组下标和长度的变动。

Vue3 应用 Proxy 来监控数据的变动。Proxy 是 ES6 中提供的性能,其作用为:用于定义基本操作的自定义行为(如属性查找,赋值,枚举,函数调用等)。绝对于Object.defineProperty(),其有以下特点:

  1. Proxy 间接代理整个对象而非对象属性,这样只需做一层代理就能够监听同级构造下的所有属性变动,包含新增属性和删除属性。
  2. Proxy 能够监听数组的变动。

参考:前端 vue 面试题具体解答

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期?

考点: Vue 的变动侦测原理

前置常识: 依赖收集、虚构 DOM、响应式零碎

根本原因是 Vue 与 React 的变动侦测形式有所不同

React 是 pull 的形式侦测变动, 当 React 晓得发生变化后, 会应用 Virtual Dom Diff 进行差别检测, 然而很多组件实际上是必定不会发生变化的, 这个时候须要用 shouldComponentUpdate 进行手动操作来缩小 diff, 从而进步程序整体的性能.

Vue 是 pull+push 的形式侦测变动的, 在一开始就晓得那个组件产生了变动, 因而在 push 的阶段并不需要手动管制 diff, 而组件外部采纳的 diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的, 然而通常正当大小的组件不会有适量的 diff, 手动优化的价值无限, 因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期.

父子组件生命周期调用程序(简略)

渲染程序:先父后子,实现程序:先子后父

更新程序:父更新导致子更新,子更新实现后父

销毁程序:先父后子,实现程序:先子后父

vue 的长处

轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十 kb;

简略易学:国人开发,中文文档,不存在语言障碍,易于了解和学习;

双向数据绑定:保留了 angular 的特点,在数据操作方面更为简略;

组件化:保留了 react 的长处,实现了 html 的封装和重用,在构建单页面利用方面有着独特的劣势;

视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;

虚构 DOM:dom 操作是十分消耗性能的,不再应用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种形式;

运行速度更快: 相比拟与 react 而言,同样是操作虚构 dom,就性能而言,vue 存在很大的劣势。

v-model 的原理?

咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:

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

以 input 表单元素为例:

<input v-model='something'>

相当于

<input v-bind:value="something" v-on:input="something = $event.target.value">

如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:

父组件:<ModelChild v-model="message"></ModelChild>

子组件:<div>{{value}}</div>

props:{value: String},
methods: {test1(){this.$emit('input', '小红')
  },
},

Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题?

受古代 JavaScript 的限度,Vue 无奈检测到对象属性的增加或删除。因为 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在能力让 Vue 将它转换为响应式的。然而 Vue 提供了 Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value) 来实现为对象增加响应式属性,那框架自身是如何实现的呢?

咱们查看对应的 Vue 源码:vue/src/core/instance/index.js

export function set (target: Array<any> | Object, key: any, val: any): any {
  // target 为数组  
  if (Array.isArray(target) && isValidArrayIndex(key)) {// 批改数组的长度, 防止索引 > 数组长度导致 splcie()执行有误
    target.length = Math.max(target.length, key)
    // 利用数组的 splice 变异办法触发响应式  
    target.splice(key, 1, val)
    return val
  }
  // key 曾经存在,间接批改属性值  
  if (key in target && !(key in Object.prototype)) {target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  // target 自身就不是响应式数据, 间接赋值
  if (!ob) {target[key] = val
    return val
  }
  // 对属性进行响应式解决
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

咱们浏览以上源码可知,vm.$set 的实现原理是:

  • 如果指标是数组,间接应用数组的 splice 办法触发相应式;
  • 如果指标是对象,会先判读属性是否存在、对象是否是响应式,最终如果要对属性进行响应式解决,则是通过调用 defineReactive 办法进行响应式解决(defineReactive 办法就是 Vue 在初始化对象时,给对象属性采纳 Object.defineProperty 动静增加 getter 和 setter 的性能所调用的办法)

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, // 当批改属性时调用此办法
};

怎么缓存以后的组件?缓存后怎么更新

缓存组件应用 keep-alive 组件,这是一个十分常见且有用的优化伎俩,vue3keep-alive 有比拟大的更新,能说的点比拟多

思路

  • 缓存用keep-alive,它的作用与用法
  • 应用细节,例如缓存指定 / 排除、联合 routertransition
  • 组件缓存后更新能够利用 activated 或者beforeRouteEnter
  • 原理论述

答复范例

  1. 开发中缓存组件应用 keep-alive 组件,keep-alivevue 内置组件,keep-alive包裹动静组件 component 时,会缓存不流动的组件实例,而不是销毁它们,这样在组件切换过程中将状态保留在内存中,避免反复渲染DOM
<keep-alive>
  <component :is="view"></component>
</keep-alive>
  1. 联合属性 includeexclude能够明确指定缓存哪些组件或排除缓存指定组件。vue3中联合 vue-router 时变动较大,之前是 keep-alive 包裹 router-view,当初须要反过来用router-view 包裹keep-alive
<router-view v-slot="{Component}">
  <keep-alive>
    <component :is="Component"></component>
  </keep-alive>
</router-view>
  1. 缓存后如果要获取数据,解决方案能够有以下两种
  2. beforeRouteEnter:在有 vue-router 的 我的项目,每次进入路由的时候,都会执行beforeRouteEnter
beforeRouteEnter(to, from, next){
  next(vm=>{console.log(vm)
    // 每次进入路由执行
    vm.getData()  // 获取数据})
},
  • actived:在 keep-alive 缓存的组件被激活的时候,都会执行 actived 钩子
activated(){this.getData() // 获取数据
},
  1. keep-alive是一个通用组件,它外部定义了一个 map,缓存创立过的组件实例,它返回的渲染函数外部会查找内嵌的component 组件对应组件的 vnode,如果该组件在map 中存在就间接返回它。因为 componentis属性是个响应式数据,因而只有它变动,keep-aliverender 函数就会从新执行

你晓得哪些 Vue3 新个性?

官网列举的最值得注意的新个性:v3-migration.vuejs.org(opens new window)

  • Composition API
  • SFC Composition API语法糖
  • Teleport传送门
  • Fragments片段
  • Emits选项
  • 自定义渲染器
  • SFC CSS变量
  • Suspense

以上这些是 api 相干,另外还有很多框架个性也不能落掉

答复范例

  1. api层面 Vue3 新个性次要包含:Composition APISFC Composition API语法糖、Teleport传送门、Fragments 片段、Emits选项、自定义渲染器、SFC CSS变量、Suspense
  2. 另外,Vue3.0在框架层面也有很多亮眼的改良:
  3. 更快

    • 虚构 DOM 重写,diff算法优化
    • 编译器优化:动态晋升、patchFlags(动态标记)、事件监听缓存
    • 基于 Proxy 的响应式零碎
    • SSR优化
  4. 更小 :更好的摇树优化 tree shakingVue3 移除一些不罕用的 API
  5. 更敌对 vue3 在兼顾 vue2options API的同时还推出了composition API,大大增加了代码的逻辑组织和代码复用能力
  6. 更容易保护TypeScript + 模块化
  7. 更容易扩大

    • 独立的响应化模块
    • 自定义渲染器

说说 Vue 的生命周期吧

什么时候被调用?

  • beforeCreate:实例初始化之后,数据观测之前调用
  • created:实例创立万之后调用。实例实现:数据观测、属性和办法的运算、 watch/event 事件回调。无 $el .
  • beforeMount:在挂载之前调用,相干 render 函数首次被调用
  • mounted:了被新创建的 vm.$el 替换,并挂载到实例下来之后调用改钩子。
  • beforeUpdate:数据更新前调用,产生在虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
  • updated:因为数据更改导致的虚构 DOM 从新渲染和打补丁,在这之后会调用改钩子。
  • beforeDestroy:实例销毁前调用,实例依然可用。
  • destroyed:实例销毁之后调用,调用后,Vue 实例批示的所有货色都会解绑,所有事件监听器和所有子实例都会被移除

每个生命周期外部能够做什么?

  • created:实例曾经创立实现,因为他是最早触发的,所以能够进行一些数据、资源的申请。
  • mounted:实例曾经挂载实现,能够进行一些 DOM 操作。
  • beforeUpdate:能够在这个钩子中进一步的更改状态,不会触发重渲染。
  • updated:能够执行依赖于 DOM 的操作,然而要防止更改状态,可能会导致更新无线循环。
  • destroyed:能够执行一些优化操作,清空计时器,解除绑定事件。

ajax 放在哪个生命周期?:个别放在 mounted 中,保障逻辑统一性,因为生命周期是同步执行的, ajax 是异步执行的。复数服务端渲染 ssr 同一放在 created 中,因为服务端渲染不反对 mounted 办法。 什么时候应用 beforeDestroy?:以后页面应用 $on,须要解绑事件。分明定时器。解除事件绑定, scroll mousemove

vue-router 守卫

导航守卫 router.beforeEach 全局前置守卫

  • to: Route: 行将要进入的指标(路由对象)
  • from: Route: 以后导航正要来到的路由
  • next: Function: 肯定要调用该办法来 resolve 这个钩子。(肯定要用这个函数能力去到下一个路由,如果不必就拦挡)
  • 执行成果依赖 next 办法的调用参数。
  • next(): 进行管道中的下一个钩子。如果全副钩子执行完了,则导航的状态就是 confirmed (确认的)。
  • next(false): 勾销进入路由,url 地址重置为 from 路由地址(也就是将要来到的路由地址)
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {next();
});
router.beforeResolve((to, from, next) => {next();
});
router.afterEach((to, from) => {console.log('afterEach 全局后置钩子');
});

路由独享的守卫 你能够在路由配置上间接定义 beforeEnter 守卫

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {// ...}
    }
  ]
})

组件内的守卫你能够在路由组件内间接定义以下路由导航守卫

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创立
  },
  beforeRouteUpdate (to, from, next) {
    // 在以后路由扭转,然而该组件被复用时调用
    // 举例来说,对于一个带有动静参数的门路 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,// 因为会渲染同样的 Foo 组件,因而组件实例会被复用。而这个钩子就会在这个状况下被调用。// 能够拜访组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航来到该组件的对应路由时调用,咱们用它来禁止用户来到
    // 能够拜访组件实例 `this`
    // 比方还未保留草稿,或者在用户来到前,将 setInterval 销毁,避免来到之后,定时器还在调用。}
}

谈一下对 vuex 的集体了解

vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无奈长久化、外部外围原理是通过发明一个全局实例 new Vue)

次要包含以下几个模块:

  • State:定义了利用状态的数据结构,能够在这里设置默认的初始状态。
  • Getter:容许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到部分计算属性。
  • Mutation:是惟一更改 store 中状态的办法,且必须是同步函数。
  • Action:用于提交 mutation,而不是间接变更状态,能够蕴含任意异步操作。
  • Module:容许将繁多的 Store 拆分为多个 store 且同时保留在繁多的状态树中。

Vue 要做权限治理该怎么做?管制到按钮级别的权限怎么做?

剖析

  • 综合实际题目,理论开发中常常须要面临权限治理的需要,考查理论利用能力。
  • 权限治理个别需要是两个:页面权限和按钮权限,从这两个方面阐述即可。

思路

  • 权限治理需要剖析:页面和按钮权限
  • 权限治理的实现计划:分后端计划和前端计划论述
  • 说说各自的优缺点

答复范例

  1. 权限治理个别需要是页面权限和按钮权限的治理
  2. 具体实现的时候分后端和前端两种计划:
  3. 前端计划 会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后依据角色过滤出路由表。比方我会配置一个asyncRoutes 数组,须要认证的页面在其路由的 meta 中增加一个 roles 字段,等获取用户角色之后取两者的交加,若后果不为空则阐明能够拜访。此过滤过程完结,剩下的路由就是该用户能拜访的页面,最初通过 router.addRoutes(accessRoutes) 形式动静增加路由即可
  • 后端计划 会把所有页面路由信息存在数据库中,用户登录的时候依据其角色查问失去其能拜访的所有页面路由信息返回给前端,前端再通过addRoutes 动静增加路由信息
  • 按钮权限的管制通常会 实现一个指令 ,例如v-permission,将按钮要求角色通过值传给 v -permission 指令,在指令的 moutned 钩子中能够判断以后用户角色和按钮是否存在交加,有则保留按钮,无则移除按钮
  • 纯前端计划的长处是实现简略,不须要额定权限治理页面,然而保护起来问题比拟大,有新的页面和角色需要就要批改前端代码从新打包部署;服务端计划就不存在这个问题,通过专门的角色和权限治理页面,配置页面和按钮权限信息到数据库,利用每次登陆时获取的都是最新的路由信息,堪称一劳永逸!

可能的诘问

  1. 相似 Tabs 这类组件能不能应用 v-permission 指令实现按钮权限管制?
<el-tabs> 
  <el-tab-pane label="⽤户治理" name="first"> ⽤户治理 </el-tab-pane> 
    <el-tab-pane label="⻆⾊治理" name="third"> ⻆⾊治理 </el-tab-pane>
</el-tabs>
  1. 服务端返回的路由信息如何增加到路由器中?
// 前端组件名和组件映射表
const map = {//xx: require('@/views/xx.vue').default // 同步的⽅式
  xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的 asyncRoutes
const asyncRoutes = [{ path: '/xx', component: 'xx',...}
]
// 遍历 asyncRoutes,将 component 替换为 map[component]
function mapComponent(asyncRoutes) {
  asyncRoutes.forEach(route => {route.component = map[route.component];
    if(route.children) {route.children.map(child => mapComponent(child))
    }
    })
}
mapComponent(asyncRoutes)

Watch 中的 deep:true 是如何实现的

当用户指定了 watch 中的 deep 属性为 true 时,如果以后监控的值是数组类型。会对对象中的每一项进行求值,此时会将以后 watcher存入到对应属性的依赖中,这样数组中对象发生变化时也会告诉数据更新

源码相干

get () {pushTarget(this) // 先将以后依赖放到 Dep.target 上 
    let value 
    const vm = this.vm 
    try {value = this.getter.call(vm, vm) 
    } catch (e) {if (this.user) {handleError(e, vm, `getter for watcher "${this.expression}"`) 
        } else {throw e} 
    } finally {if (this.deep) { // 如果须要深度监控 
        traverse(value) // 会对对象中的每一项取值, 取值时会执行对应的 get 办法 
    }popTarget()}

Vue 模版编译原理晓得吗,能简略说一下吗?

简略说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经验以下阶段:

  • 生成 AST 树
  • 优化
  • codegen

首先解析模版,生成AST 语法树(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最初一步是 将优化后的 AST 树转换为可执行的代码

computed 的实现原理

computed 实质是一个惰性求值的观察者。

computed 外部实现了一个惰性的 watcher, 也就是 computed watcher,computed watcher 不会立即求值, 同时持有一个 dep 实例。

其外部通过 this.dirty 属性标记计算属性是否须要从新求值。

当 computed 的依赖状态产生扭转时, 就会告诉这个惰性的 watcher,

computed watcher 通过 this.dep.subs.length 判断有没有订阅者,

有的话, 会从新计算, 而后比照新旧值, 如果变动了, 会从新渲染。(Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 从新渲染,实质上是一种优化。)

没有的话, 仅仅把 this.dirty = true。(当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。)

正文完
 0