2023前端面试总结-Vue篇(2023.3.11更新)

回答面试题的套路

  1. 先说这个点的明确定义,或者是特性;

  2. 再说具体的应用场景;

  3. 说说自己的看法、观点;

  4. 可以稍微举一反三,说说同类特性,或者类似的框架,更好的方案。

一、常考-基础点

1. 对SPA单页面的理解,优缺点是什么?
2. new Vue() 发生了什么?
3. Vue.use 是干什么的?原理是什么?
4. 请说一下响应式数据的原理?
5. Vue 如何检测数组变化?
6. Vue.set 方法是如何实现的?
7. Vue 中模板编译原理?
8. Proxy 与 Object.defineProperty优劣对比
9. Vue3.x 响应式数据原理?

二、常考-生命周期

1. Vue 的生命周期方法有哪些?一般在哪一步发起请求及原因?
2. 生命周期钩子是如何实现的?
3. Vue 的父组件和子组件生命周期钩子执行顺序?

三、常考-组件通信

1. Vue 中的组件的 data 为什么是一个函数?
2. Vue 组件通信有哪几种方式?
3. 组件中写 name 选项有哪些好处及作用?
4. keep-alive 平时在哪里使用?原理是?
5. Vue.minxin 的使用场景和原理?

四、常考-路由

1. Vue-router 有几种钩子函数?具体是什么及执行流程是怎样的?
2. Vue-router 两种模式的区别?

五、常考-属性作用与对比

1. nextTick 在哪里使用?原理是什么?
2. Vue 为什么需要虚拟 DOM?虚拟 DOM 的优劣如何?
3. Vue 中 key 的作用及原理,说说你对它的理解?
4. Vue 中 diff 的原理?
5. v-if 和 v-for 的优先级
6. v-if 和 v-show 的区别
7. computed 和 watch 的区别和运用的场景?
8. 如何理解自定义指令?
9. V-model的原理是什么?

六、常考-性能优化

1. Vue性能优化

正文从这里开始~

一、常考-基础点


1. 对SPA单页面的理解,优缺点是什么?


核心答案:

SPA(single-page application) 仅在 Web 页面初始化时加载相应的 HTML、JavaScript、和CSS,一旦页面加载完成,SPA不会再因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现页面 UI 的变换,从而实现与用户交互,避免页面的重新加载。

优点:

  1. 用户体验好,页面切换速度快,因为改动内容不用重新加载整个页面,页面切换效果可以很酷炫;
  2. 实现前后端分离,架构清晰,职责分明;
  3. SPA 对服务器压力较小。

缺点:

  1. 首次加载速度慢,因为需要首次在加载页面时将 JavaScript、CSS 统一加载;
  2. 不利于 SEO:因为所有内容都在一个页面中动态替换显示,所以在 SEO 上有着天然的弱势。

2. new Vue() 发生了什么?


  1. 结论:new Vue() 是创建 Vue 实例,内部执行了根实例的初始化过程。
  2. 主要包括以下步骤:
    • 合并选项 options;
    • $children, $refs, $createElement, $slots 等实例属性方法的初始化;
    • 注册用户自定义的事件;
    • 数据响应式处理;
    • 生命周期钩子调用(beforeCreate created);
    • 如果传入参数存在 el 属性,就去挂载对应节点。
  3. 总结:new Vue() 创建根实例,并准备好数据和方法,未来执行挂载时,此过程还是递归的应用于它的子组件上,最终形成一个有紧密关系的组件数。

3. Vue.use 是干什么的?原理是什么?


Vue.use 是用来注册插件的,我们可以在插件中注册全局组件、指令、原型方法等。

  1. 检查插件是否已注册,若已注册,直接跳出;
  2. 处理入参,将第一个参数之后的参数归集,并在首部插入 this 上下文;
  3. 执行注册方法,调用插件定义的 install 方法,传入处理后的参数,若没有 install 方法并且插件本身为 function 则直接进行注册;
  4. 缓存插件。

4. 请说一下响应式数据的原理?


核心答案:

根据数据类型来做不同处理,对数组和对象类型进行劫持并绑定依赖,当数据变化时触发依赖更新视图。

  1. 对象内部通过defineReactive方法,使用 Object.defineProperty() 监听数据属性的 get 来进行数据依赖收集,再通过 set 来完成数据更新的派发;

  2. 数组则通过重写数组方法来实现的。扩展它的 7 个变更⽅法,通过监听这些方法可以做到依赖收集和派发更新;( push/pop/shift/unshift/splice/reverse/sort )

这里在回答时可以带出一些相关知识点 (比如多层对象是通过递归来实现劫持,顺带提出vue3中是使用 proxy来实现响应式数据)

补充回答:

内部依赖收集是怎么做到的,每个属性都拥有自己的dep属性,存放他所依赖的 watcher,当属性变化后会通知自己对应的 watcher去更新。

响应式流程:

  1. defineReactive 把数据定义成响应式的;

  2. 给属性增加一个 dep,用来收集对应的那些watcher;

  3. 等数据变化进行更新

dep.depend() // get 取值:进行依赖收集

dep.notify() // set 设置时:通知视图更新

这里可以引出性能优化相关的内容:1)对象层级过深,性能就会差。2)不需要响应数据的内容不要放在data中。3)object.freeze() 可以冻结数据。

源码地址:src/core/observer/index.js 158


5. Vue 如何检测数组变化?


核心答案:

数组考虑性能原因没有用 defineProperty 对数组的每一项进行拦截,而是选择重写数组方法,当数组调用到这7个方法时,执行 ob.dep.notify() 进行派发通知 Watcher 更新;

  • 重写数组方法:push, pop, shift, unshift, splice, reverse, sort

补充回答:

在 Vue 中修改数组长度和通过下标修改数组值时是无法监听到的,需要通过以上7中变异方法才会触发数组对应的 Watcher 进行更新,数组中如果是对象数据类型也会递归进行劫持。

说明:如果想要改索引更新数组怎么办?

可以通过 Vue.set() 来进行数据更新,内部核心用的是 splice() 方法:

// 取出数组原型
const arrayProto = Array.prototype;
// 拷贝原型
export const arrayMethods = Object.create(arrayProto);
// 重写数组方法
def(arrayMethods, method, function mutator (...args) {});
// 调用方法时通知视图
ob.dep.notify();

源码地址:src/core/observer/array.js


6. Vue.set 方法是如何实现的?


核心答案:

为什么 $set 能触发更新,因为我们给对象增加了 dep 属性,当通过 $set 给数组或对象添加不存在的属性时,就会触发依赖的 Watcher 去更新,当修改数组索引时我们调用重写的 splice 方法去更新数组。

补充回答:
官网定义 Vue.set(obj, key, val)

  1. 如果是数组,会调用重写的splice方法(这样可以更新视图)
    代码:target.splice(key, 1, val)
  2. 如果 target 本身不是响应式,那么久不需要定义成响应式
  3. 如果是对象,就会调用 defineReactive(ob.value, key, val) 将属性定义成响应式的
  4. 通知视图更新 op.dep.notify()

源码地址:src/core/observer/index.js


7. Vue 中模板编译原理


核心答案:

如何将template转换成render函数(这里要注意的是我们在开发时尽量不要使用template,因为将template转化成render方法需要在运行时进行编译操作会有性能损耗,同时引用带有complier包的vue体积也会变大) 默认.vue文件中的 template处理是通过vue-loader 来进行处理的并不是通过运行时的编译。

  1. 将 template 模板转换成 ast 语法树 - parserHTML

  2. 对静态语法做静态标记 - markUp

  3. 重新生成代码 - codeGen

补充回答:

模板引擎的实现原理就是new Function + with来进行实现的。

vue-loader中处理template属性主要靠的是 vue-template-compiler

vue-loader

// template => ast => codegen => with+function 实现生成render方法 
let {ast, render } = VueTemplateCompiler.compile(`<div>{{aaa}}</div>`)
console.log(ast, render)
 
// 模板引擎的实现原理 with + new Function
console.log(new Function(render).tostring())
// render方法执行完毕后生成的是虚拟 dom
// with(this){return _c('div',[_s(aaa)])}
// 代码生成

源码设置:

const ast = parse(template.trim(), options) // 将代码解析成ast语法树
  if (options.optimize !== false) {
    optimize(ast, options) // 优化代码 标记静态点 标记树
  }
  const code = generate(ast, options) // 生成代码 

源码地址:src/compiler/index.js


8. Proxy 与 Object.defineProperty优劣对比


核心答案:

Proxy 的优势如下:

1)Proxy 可以直接监听对象而非属性;

2)Proxy 可以直接监听数组的变化;

3)Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的;

4)Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;

5)Proxy 作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利;

Object.defineProperty 的优势如下:

兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题,而且无法用 polyfill 磨平,因此 Vue 的作者才声明需要等到下个大版本( 3.0 )才能用 Proxy 重写。


9. Vue3.x 响应式数据原理?


核心答案:

Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?

判断当前Reflect.get的返回值是否为Object,如果是则再通过reactive方法做代理, 这样就实现了深度观测。

监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?

我们可以判断key是否为当前被代理对象target自身属性,也可以判断旧值与新值是否相等,只有满足以上两个条件之一时,才有可能执行trigger。

二、常考-生命周期


1. Vue 的生命周期方法有哪些?一般在哪一步发起请求及原因?


核心答案:
总共分为8个阶段:创建前后,载入前后,更新前后,销毁前后。

1. 创建前后

  1. beforeCreate:在此阶段,Vue 实例还没有挂载元素 el 和数据对象 data 都为 undefined,还未初始化;
    说明:在此阶段 data、methods、computed、watch 上的数据和方法都不能访问。

  2. created:在此阶段进行了 Vue 实例进行了 data 的初始化,可以访问 data 了,但元素还没有挂载,不能访问 el;
    说明:在此阶段可以进行一些初始化数据的获取,但还不能操作 DOM,如果想要操作 DOM,需要在 vm.$nextTick 方法中去访问。

2. 挂载前后

  1. beforeMount:此时 $el 和 data 都已经初始化了,进行了 DOM 渲染的初始化操作;
    说明:在此阶段可以对数据进行更改,但不会触发 updated。
  2. mounted:Vue 实例挂载完成,DOM 渲染完成,
    说明:在此阶段,真实 DOM 加载完成,数据完成双向绑定,可以访问到 DOM 节点,可以使用 $refs 对节点进行操作。

3. 更新前后

  1. beforeUpdate:响应式数据更新时调用,发生在虚拟 DOM 打补丁之前,适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器;
    说明:可以在当前阶段进行数据更改,不会造成重渲染。
  2. updated:虚拟 DOM 渲染完成之后触发,避免在此钩子函数中操作数据,防止死循环;

4. 销毁前后

  1. beforeDestroy:实例销毁前调用,此时实例还可以调用,this 能获取到实例,常用于销毁定时器、解绑事件等善后收尾工作;
  2. destroyed:当前阶段组件已被销毁,数据绑定被卸除,监听被移除,子组件实例也都被销毁;

补充回答:

第一次页面加载时会触发:beforeCreate, created, beforeMount, mounted。

  1. created 实例已经创建完成,因为它是最早触发的原因可以进行一些数据,资源的请求。(服务器渲染支持created方法)

  2. mounted 实例已经挂载完成,可以进行一些DOM操作。(接口请求)

源码地址:src/core/instance/lifecycle.js


2. 生命周期钩子是如何实现的?


核心答案:

Vue 生命周期钩子函数本质上就是回调函数,当创建组件实例的过程中会调用对应的钩子函数。

补充回答:

内部主要是使用callHook方法来调用对应的方法。核心是一个发布订阅模式,将钩子订阅好(内部采用数组的方式存储),在对应的阶段进行发布。

源码地址:src/core/util/options.js core/instance/lifecycle.js


3. Vue 的父组件和子组件生命周期钩子执行顺序?


核心答案:

第一次页面加载时,会触发beforeCreate、created、beforeMount、mounted这几个钩子函数。

渲染过程 :

父组件挂载完成一定是等所有子组件挂载完之后,才算是父组件挂载完成,所以子组件的 mounted 在父组件 mounted 之前

  • 首次加载过程:
    • 父 beforeCreate —> 父 created —> 父 beforeMount —> 子beforeCreate —> 子 created —> 子 beforeMount —> 子 mounted —> 父 mounted
  • 子组件更新过程:
    • 不影响父组件:子 beforeUpdate —> 子 updated
    • 影响父组件:父beforeUpdate —> 子 beforeUpdate —> 子 updated —> 父 updated
  • 父组件更新过程:
    • 不影响子组件:父 beforeUpdate —> 父updated
    • 影响子组件: 父beforeUpdate —> 子 beforeUpdate —> 子 updated —> 父 updated
  • 销毁过程:
    • 父 beforeDestroy —> 子 beforeDestroy —> 子 destroyed —> 父 destroyed

重要:父组件等待子组件完成后,才会执行自己对应完成的钩子。

三、常考-组件通信


1. Vue 中的组件的 data 为什么是一个函数?


组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

源码地址:src/core/util/options


2. Vue 组件通信有哪几种方式?


  1. props / $emit 适用于 父子组件通信;

  2. ref 与 $parent / $children 适用于 父子组件通信;

  3. EventBus($emit / $on) 适用于 父子组件、隔代组件、兄弟组件;

  4. $attrs / $listeners 适用于 隔代组件;

  5. provide / inject 适用于 隔代组件;

    祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。

  6. Vuex 适用于 父子组件、隔代组件、兄弟组件


3. 组件中写 name 选项有哪些好处及作用?


核心答案:

  1. 可以通过名字找到对应的组件 ( 递归组件 )

  2. 可以通过name属性实现缓存功能 (keep-alive)

  3. 可以通过name来识别组件 (跨级组件通信时非常重要)

Vue.extend = function () {
    if(name) {
        Sub.options.componentd[name] = Sub
    }
}

源码地址:src/core/vdom/create-element.js


4. keep-alive 平时在哪里使用?原理是?


keep-alive 用于缓存组件,当数组满了的时候,采用最久未使用算法。

常用的两个属性 include / exclude,允许组件有条件的缓存。

采用 keep-alive 的组件,激活或失活时会走 activated / deactivated 生命周期钩子函数。

export default {
    name: 'keep-alive',
    abstract: true,
    props: {
        include: patternTypes, // 要缓存的有哪些
        exclude: patternTypes, // 要排除的有哪些
        max: [String, Number] //最大缓存数量
    },
    ...
    render() {
        ...
        if (cache[key]) { // 通过key 找到缓存,获取实例
            vnode.componentInstance = cache[key].componentInstance
            // 将队列中旧的 key 删除,重新添加到队列的尾部
            remove(keys, key)
            keys.push(key)
        } else {
            cache[key] = vnode
            keys.push(key)
            // 删除最早缓存的
            if (this.max && keys.length > parseInt(this.max)) {
                pruneCacheEntry(cache, keys[0], keys, this._vnode)
            }
        }

        vnode.data.keepAlive = true
    }
}

5. Vue.minxin 的使用场景和原理?


核心答案:

Vue.minxin 的作用是抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时,会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并,如果混入的数据和本身组件中的数据冲突,会采用“就近原则”以组件的数据为准。

补充回答:

minxin 中有很多缺陷:命名冲突、依赖问题、数据来源问题等,这里强调一下 minxin 的数据是不共享的。

源码地址:src/core/util/options.js

四、常考-路由


1. Vue-router 有几种钩子函数?具体是什么及执行流程是怎样的?


核心答案:

路由钩子的执行流程,钩子函数的种类:全局守卫、组件守卫、路由守卫。

钩子函数种类:

  • 全局守卫:
    beforeEach、afterEach、beforeResolve
  • 组件守卫:
    beforeRouteEnter、beforeRouteLeave、beforeRouteUpdate
  • 路由守卫:
    beforeEnter

执行流程:

  1. 导航被触发;
  2. 在即将失活的组件里调用离开守卫 beforeRouteLeave();
  3. 调用全局守卫 beforeEach;
  4. 在重用的组件里调用 beforeRouteUpdate 守卫;
  5. 在路由匹配规则里调用 beforeEnter;
  6. 解析异步路由组件;
  7. 在被激活的组件里调用 beforeRouteEnter;
  8. 调用全局的 beforeResolve 守卫 (2.5+);
  9. 导航被确认;
  10. 调用全局守卫 afterEach;
  11. 触发 DOM 更新;
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

注意各个守卫中 this 是否已赋值为当前组件实例;


2. Vue-router 两种模式的区别?


核心答案:

Vue-router 有3种路由模式:hash、history、abstract

hash 模式: hash + onhashchange 事件

特点:hash 虽然在 URL 中,但是不在 HTTP 请求中;用来知道浏览器工作,对服务端安全无用,hash 不会重新加载页面;通过监听 hash(#) 的变化来执行 js 代码实现页面改变。

核心代码:

window.addEventListener("hashchange", function() {});

history 模式:history api + popState

HTML5 推出的 history api,由 pushState 记录操作历史,监听 popState 事件来监听到状态变更。
因为 只要刷新 这个url(www.ff.ff/jjkj/fdfd/fdf/fd)就会请求服务器,然而服务器上根本没有这个资源,所以就会报404,解决方案就 配置一下服务器端。

说明:
1)hash:使用 URL hash 值来作路由,兼容性好,支持所有浏览器;
2)history:依赖 HTML5 history api 和服务器配置;
3)abstract:主要用于 node.js 环境。

五、常考-属性作用与对比


1. nextTick 在哪里使用?原理是什么?


核心答案:

nextTick 的回调是在下次 DOM 更新循环结束之后的延迟回调,在修改后使用这个方法,可以获取更新之后的 DOM。nextTick 内部使用了微任务和宏任务来实现延迟回调的(Promise、setImmediate、MessageChannel、setTimeout — Vue-2.5.17-beta.0)。

补充回答:

vue多次更新数据,最终会进行批处理更新。内部调用的就是nextTick实现了延迟更新,用户自定义的nextTick中的回调会被延迟到更新完成后调用,从而可以获取更新后的DOM。


2. Vue 为什么需要虚拟 DOM?虚拟 DOM 的优劣如何?


核心答案:

Virtual DOM 就是用 JS 对象来描述真实的 DOM,是对真实 DOM 的抽象,由于直接操作 DOM 性能低但是操作 JS 性能高,可以将 DOM 操作转化为 JS 对象操作,最终通过 diff 算法比对差异来更新真实 DOM(减少了对真实 DOM 的操作)。同时虚拟 DOM 不依赖平台环境从而也可以实现跨平台。

补充回答:

虚拟 DOM 的实现就是普通对象包含 tag、data、children 等属性真实节点的描述。(本质上就是在 JS 和 DOM 之间的一个缓存)

Vue2 的 Virtual DOM 借鉴了开源库 snabbdom 的实现。

Virtual DOM 映射到真实 DOM 需要经历 VNode 的 create、diff、patch 等阶段。


3. Vue 中 key 的作用及原理,说说你对它的理解?


核心答案:

例如:v-for=“(item, itemIndex) in tabs” :key=“itemIndex”

key的作用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程中通过key可以精准判断两个节点是否是同一个,从而避免频繁更新不同元素,使得整个patch过程更加高效,减少DOM操作量,提高性能。

补充回答:

1、若不设置key还可能在列表更新时引发一些隐蔽的bug

2、vue中在使用相同标签名元素的过渡切换时,也会使用到key属性,其目的也是为了让vue可以区分它们,否则vue只会替换其内部属性而不会触发过渡效果。

源码地址:src\core\vdom\patch.js - updateChildren()


4. Vue 中 diff 的原理?


核心答案:

vue的diff算法是平级比较,不考虑跨级比较的情况。内部采用深度递归的方式 + 双指针的方式进行比较。

补充回答:

  1. 先比较是否是相同节点

  2. 相同节点比较属性,并复用老节点

  3. 比较儿子节点,考虑老节点和新节点儿子的情况

  4. 优化比较:头头、尾尾、头尾、尾头

  5. 比对查找进行复用

Vue2 与 Vue3.x 的diff算法:

Vue2的核心Diff算法采用了双端比较的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。

Vue3.x借鉴了ivi算法和 inferno算法,该算法中还运用了动态规划的思想求解最长递归子序列。(实际的实现可以结合Vue3.x源码看。)

源码地址:src/core/vdom/patch.js


5. v-if 和 v-for 的优先级


核心答案:

1、v-for优先于v-if被解析

2、如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能

3、要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环

4、如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项

源码地址:compiler/codegen/index.js


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


核心答案:

v-if 是真正的条件渲染,直到条件第一次变为真时,才会开始渲染。

v-show 不管初始条件是什么会渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。

注意:v-if 适用于不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。


7. computed 和 watch 的区别和运用的场景?


核心答案:

computed: 计算属性。依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;

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

运用场景:

1)当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;

2)当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。


8. 如何理解自定义指令?


核心答案:

指令的实现原理,可以从编译原理 =>代码生成=> 指令钩子实现进行概述

1、在生成 ast 语法树时,遇到指令会给当前元素添加directives属性

2、通过 genDirectives 生成指令代码

3、在patch前将指令的钩子提取到 cbs中,在patch过程中调用对应的钩子。

4、当执行指令对应钩子函数时,调用对应指令定义的方法


9. V-model的原理是什么?


核心答案:

v-model本质就是一个语法糖,可以看成是value + input方法的语法糖。可以通过model属性的prop和event属性来进行自定义。原生的v-model,会根据标签的不同生成不同的事件和属性。

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

1)text 和 textarea 元素使用 value 属性和 input 事件;

2)checkbox 和 radio 使用 checked 属性和 change 事件;

3)select 字段将 value 作为 prop 并将 change 作为事件。


六、常考-性能优化


1. Vue性能优化


1、你都做过哪些Vue的性能优化?( 统计后的结果 )

1)编码阶段

  • 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher;

  • 如果需要使用v-for给每项元素绑定事件时使用事件代理;

  • SPA 页面采用keep-alive缓存组件;

  • 在更多的情况下,使用v-if替代v-show;

  • key保证唯一;

  • 使用路由懒加载、异步组件;

  • 防抖、节流;

  • 第三方模块按需导入;

  • 长列表滚动到可视区域动态加载;

  • 图片懒加载;

2)用户体验:

  • 骨架屏;

  • PWA;

  • 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

3)SEO 优化

  • 预渲染;

  • 服务端渲染 SSR;

4)打包优化

  • 压缩代码;

  • Tree Shaking/Scope Hoisting;

  • 使用 cdn 加载第三方模块;

  • 多线程打包 happypack;

  • splitChunks 抽离公共文件;

  • sourceMap 优化;

说明:优化是个大工程,会涉及很多方面。

参考链接:
从源码角度-解读Vue常考面试题

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlwaysWM丶

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值