Vue2面试题笔记(更新中...)__Vue.js
发布于 3 年前 作者 banyungong 1409 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green

贡献主题:https://github.com/xitu/juejin-markdown-themes

theme: smartblue highlight: atom-one-dark

前言

  • vue2 面试题(针对一些常问的)

1.说一下computed 和 watch 区别是什么

vue2核心原理(简易)-computed笔记

vue2核心原理(简易)-watch笔记

# 主要考察
1. computed和watch都是基于Watcher来实现的
2. 分别是计算属性watcher和用户watcher。computed属性是具备缓存的, 依赖值不发生变化, 
   对齐取值时计算属性方法不会重新执行(可以模板渲染, 取值的过程中不支持异步方法)
   watch是监控值发生变化, 当值发生变化时调用对应的函数

# 补充回答
1. computed不会立即重新计算生成新的值,而是先标记为脏数据,
   当下次computed被获取时候,才会进行重新计算并返回
2. watch则是立即执行, 将老值保存在watcher上, 当数据更新时重新计算新值, 将新值与老值
   传递回答函数中

2.说一下响应式数据的理解

Vue2核心原理(简易) - 响应式原理(数据劫持)

# 主要考察:数组和对象是如何劫持到的
1. 对象内部通过defineReactive方法, 使用的是Object.defineProperty将属性进行劫持(只会劫持已经存在的属性, 后续增加的不劫持)
2. 对数组是通过重新写数组的方法实现的(push, pop, unshift, shfit, sort, reverse, splice)
* 可以顺便带出一些相关知识 如多层对象是通过递归实现的, 顺便提一嘴Vue3中是用proxy来实现数据响应的

# 主要考察答出了 也可进行补充回答
1. 内部依赖收集怎么回事 每个属性都拥有自己的dep属性 存放它所依赖的watcher 当属性变化后会通知watcher去更新
2. 每个对象本身也有个dep属性 为了$set

# 这里可以引出性能优化相关内容
1. 对象层级过深 性能就会差
2. 不需要响应的内容不要放到data中
3. Object.freeze() 进行数据冻结(vue将不能对数据进行getter和setter)

3.说一下Vue如何检测数组变化的

Vue2核心原理(简易) - 响应式原理(数据劫持)

# 主要考察
1. 数组考虑性能原因没有用Object.defineProperty进行拦截,
2. 选择重新写数组的七个方法(push, pop, unshift, shfit, sort, reverse, splice)

# 补充回答
1. 所以在Vue中修改数组的索引和长度是无法监控到的, 需要通过以上的7个方法, 去触发数组对应的watcher, 实现更新
2. 如果数组中是对象 会进行递归劫持

# 引发问题
1. 想要通过索引更改数据, 可以通过`Vue.$set()`处理(内部用的splice方法)

4.说一下模板编译原理

Vue2核心原理(简易) - 模板编译(笔记)

# 主要考察
1. 如何将template转换成render函数(这里要说明的是我们在开发时尽量不要使用template, 因为将template转换成render方法, 需要在运行时进行编译操作会有性能消耗
同时引用带有compiler包vue的体积也会变大, 默认的.vue文件中的template处理是通过vue-loader进行处理, 并不是通过运行时编译)
	* 1.将template模板转换成ast语法树 - parserHTML方法
	* 2.对静态语法做静态标记 - markUp方法
   	* 3.重新生成代码 - codeGen方法

# 补充回答
1. 模板引擎的实现原理就是new Function + with
2. vue-loader中处理template属性主要考的是vue-template-compiler模块
	* const VueTemplateCompiler = require('vue-template-compiler')
	* const { render } = VueTemplateCompiler.compile('<div id="hello">{{msg}}</div>')

5.说一下生命周期钩子是如何实现的

vue2核心原理(简易) - 生命周期初次实现 + Vue.mixin笔记

# 主要考察
1. 生命周期钩子 就是回掉函数, 在创建组件实例的过程中去调用对应的钩子方法

# 补充回答
1. 内部主要使用callHook方法去调用对应的方法(核心是个发布订阅模式), 将钩子订阅好(内部采用数组的方式存储), 然后在对应得阶段进行发布

6.说一下Vue.mixin()的使用场景和原理

vue2核心原理(简易) - 生命周期初次实现 + Vue.mixin笔记

# 主要考察(原理)
1. Vue.mixin(options), 将Vue本身的options, 和后期用户传入的options, 递归混合
2. 针对不同类型的options(options.key不同), 采用不同的策略(列队存储, 递归继承等)
3. 这些策略放在一个存储空间 strats = {}
4. 如果strats中存在对应的key策略, 就直接执行该策略, 然后返回即可
   如果不存在, 就父组件(Vue.options对应的key)与子组件(options)进行合并 都有的情况下, 子覆盖父
5. 最后将父子options对应的key都执行了一遍 实现了混合策略

# 主要考察(场景)
1. 父子组件的生命周期就是采用Vue.mixin()方法将每次混入的钩子函数按照顺序都放在一个队列当中, 当触发条件时 依次执行
2. 父子组件的混合(Vue.componet), 根据父对象构建一个新的对象(Object.create(parent.options.componet)), 新对象就继承了父对象的proto, 
   然后将新对象依次赋予options[key] = childOptins[key], 这样如果自身没有值 就可以在父组件找到对应的值

7.说一下nextTick使用场景和原理

vue2核心原理(简易)-异步更新(Vue.nextTick)笔记

# 主要考察(场景原理)
1. nextTick中的回调是下次DOM更新循环结束之后执行的延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM
2.原理就是异步方法(如promise mutationObserver,setimmediate,setTimeout)经常与事件一起来问(宏任务和微任务)

# 补充回答
1. vue多次更新数据 最终会进行批处理更新 内部调用的就是nextTick实现了延迟更新,用户自定义的nextTick中的回调会被延迟到更新完成后调用, 从而实现更新后的DOM

8.说一下为什么需要虚拟DOM

vue2核心原理(简易) - diff算法解析笔记

# 主要考察(场景原理)
1. Virtual DOM就是用js对象来描述真实DOM, 是对真实DOM的抽象,由于直接DOM的抽象, 由于直接操作DOM性能低但是js层的操作效率高,可以将DOM操作转化成对象操作
2. 最终通过diff算法比对差异进行更新DOM, 减少了对真实DOM的操作, 虚拟DOM不依赖真实平台环境,虚拟DOM不依赖真实平台环境从而也可以实现跨平台

# 补充回答
1. 虚拟DOM的实现就是普通对象包含tag, attrs, children等属性对真实节点的描述,本质上就是在JS和DOM之间的缓存

9.说一下diff的原理

vue2核心原理(简易) - diff算法解析笔记

# 主要考察
1. Vue中的diff算法是平级比较, 不考虑跨级比较的情况, 内部采用深度递归的方式 + 双指针的方式进行比较

* 首先比较是相同的节点 不是直接替换掉
* 相同的节点比较属性 并复用老节点
* 比较儿子节点 考虑老节点和新节点儿子情况
* 优化比较: 头头比较 尾尾比较 头尾比较 尾头比较
* 比对查找进行复用

# 补充回答
1. Vue3中采用最长递增子序列实现diff算法

10.说一下既然Vue通过数据劫持可以精准探测数据变化 为什么还需要虚拟DOM进行diff检测差异

vue2核心原理(简易) - diff算法解析笔记

# 主要考察
1. 响应式数据变化 Vue确实可以在数据发生变化时 响应式系统可以立刻得知, 但如果给每个属性都添加watcher用于更新的话,
   会产生大量的watcher从而降低性能,而且力度过细也会导致不精准的问题 所以Vue采用组件级的watcher配合diff来检测差异
2. 可以往diff算法说说

11.说一下Vue.$set方法怎么实现的

vue2核心原理(简易) - $set和$del笔记

# 主要考察(为什么$set可以触发更新操作)
1. 其实在数据劫持的时候 对象和数组本身都添加了一个dep属性, 当给对象新增一个不存在的属性的时候, 触发对象收集的watcher去更新
2. 修改数组索引时候 调用的其实是数组本身的splice方法去更新数组(重新写的数组的七个方法)

12.说一下生命周期方法有哪些, 一般在那一步发起请求及原因

# 主要考察(组件的生命周期中各个钩子函数做了什么事情)
1. beforeCreate 在实例初始化之后, 数据观测(data observer) 和 event/watcher事件配置之前被调用
2. created 实例创建完成之后被调用 在这一步 实例已完成以下配置: 数据观测(data observer) 属性和方法的运算 watcher/event事件回调 还没有挂载$el
3. beforeMount 在挂载开始之前被调用: 相关的render函数首次被调用
4. mounted el 被新创建的vm.$el替换, 并挂载并挂载到实例上去之后调用该钩子
5. beforeUpdate 数据更新时调用, 发生在虚拟dom重新渲染和打补丁之前
6. updated 由于数据更改导致的虚拟dom重新渲染和打补丁 在这之后调用该钩子
7. beforeDestroy 实例销毁之前调用 在这一步 实例任然完全可用
8. destroyed Vue实例销毁后调用 调用后 vue实例指示的所有东西都会解除绑定, 所有的事件监听器会被移除, 所有的子实例也会被销毁, 该钩子在服务器端渲染期间 不会被调用

# 补充回答(钩子的作用)
1. created 实例已经创建完成 以为它是最早触发的原因 可以进行一些数据, 资源的请求 (vue ssr 支持created方法)
2. mounted 实例已经挂载完成 可以进行一些dom操作
3. beforeUpdate 可以进一步更改状态 不会触发重渲染过程
4. updated 可以执行依赖dom的操作 然而在大多数情况下 应该避免在此期间更改状态 因为可能会导致更新无限循环, 该钩子在服务器端渲染期间 不会被调用
5. destroyed 可以执行一些优化操作 清空定时器 解除绑定事件

* 在哪里发请求都可以 具体看做什么事

13.说一下vue组件间传值方式 以及之间的区别

# 主要考察(为什么$set可以触发更新操作)
1. props和$emit 父组件向子组件传递数据时通过props传递的 
   子组件传递的数据给父组件是通过$emit触发事件
2. $parent, $children 获取当前组件的父组件和当前组件的子组件
3. $attrs和$listeners A->B->C Vue2.4开始提供了$attrs 和 $listeners来解决这个问题
4. 父组件中通过provide来提供变量 然后在子组件中通过inject来注入变量
5. $refs 获取实例
6. eventBus 平级组件数据传统 这种情况下可以使用中央事件总线的方式
7. vuex状态管理

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: 小桂summer 原文链接:https://juejin.im/post/6917144916455849991

回到顶部