目录
十、既然函数是引用类型,为什么 vue 的 data 还是可以用函数
一、说说Vue中的diff算法
是什么?
- diff算法是一种通过同层的树节点进行比较的高效算法
- 有2个特点:①比较只会在同层级进行,不会跨层级比较,②在diff比较过程中,循环从两边向中间比较
- 应用场景:在vue中,作用于虚拟dom渲染成真实dom的新旧VNode节点比较
比较方式?
整体策略:深度优先,同层比较
①比较只会在同层级进行,不会跨层级比较,
②在diff比较过程中,循环从两边向中间比较
比较过程:
原理分析:
当数据发生改变时,set方法会调用Dep.notify通知所有订阅者watcher,订阅者会调用patch给真实的dom打补丁,更新相应的视图。
源码位置在src/core/vdom/patch.js。
通过isSameVnode
进行判断,相同则调用patchVnode
方法。
patchVnode
做了以下操作:
- 找到对应的真实
dom
,称为el
- 如果都有都有文本节点且不相等,将
el
文本节点设置为Vnode
的文本节点 - 如果
oldVnode
有子节点而VNode
没有,则删除el
子节点 - 如果
oldVnode
没有子节点而VNode
有,则将VNode
的子节点真实化后添加到el
- 如果两者都有子节点,则执行
updateChildren
函数比较子节点
updateChildren
主要做了以下操作:
- 设置新旧
VNode
的头尾指针 - 新旧头尾指针进行比较,循环向中间靠拢,根据情况调用
patchVnode
进行patch
重复流程、调用createElem
创建一个新节点,从哈希表寻找key
一致的VNode
节点再分情况操作
二、Vue模板是如何编译的
new Vue({
render: h => h(App)
})
调用 render 就会得到传入的模板(.vue
文件)对应的虚拟 DOM,那么这个 render 是哪来的呢?它是怎么把 .vue
文件转成浏览器可识别的代码的呢?render 函数是怎么来的有两种方式:
- 第一种就是经过模板编译生成 render 函数
- 第二种是我们自己在组件里定义了 render 函数,这种会跳过模板编译的过程
三、v-model 作用
v-model
本质上不过是语法糖,可以用 v-model 指令在表单及元素上创建双向数据绑定。
- 它会根据控件类型自动选取正确的方法来更新元素
- 它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
v-model
会忽略所有表单元素的value
、checked
、selected
特性的初始值,而总是将 Vue 实例的数据作为数据来源,因此我们应该通过 JavaScript 在组件的data
选项中声明初始值
v-model
在内部为不同的输入元素使用不同的属性并抛出不同的事件:
-
text 和 textarea 元素使用
value
属性和input
事件; -
checkbox 和 radio 使用
checked
属性和change
事件; -
select 字段将
value
作为 prop 并将change
作为事件。
四、v-model 实现原理
v-model只不过是一个语法糖而已,真正的实现靠的还是
- v-bind:绑定响应式数据
- 触发oninput 事件并传递数据
五、Vue2.0 双向绑定的缺陷
Vue2.0的数据响应是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty () 来劫持各个属性的setter、getter,但是它并不算是实现数据的响应式的完美方案,某些情况下需要对其进行修补或者hack这也是它的缺陷,主要表现在两个方面:
-
vue 实例创建后,无法检测到对象属性的新增或删除,只能追踪到数据是否被修改:当创建一个Vue实例时,将遍历所有DOM对象,并为每个数据属性添加了get和set。get和set 允许Vue观察数据的更改并触发更新。但是,如果你在Vue实例化后添加(或删除)一个属性,这个属性不会被vue处理,改变get和set。
-
不能监听数组的变化:vue在实现数组的响应式时,它使用了一些hack,把无法监听数组的情况通过重写数组的部分方法来实现响应式,这也只限制在数组的push/pop/shift/unshift/splice/sort/reverse七个方法,其他数组方法及数组的使用则无法检测到。
vue实现数组响应式的方法:通过重写数组的Array.prototype对应的方法,具体来说就是重新指定要操作数组的prototype,并重新该prototype中对应上面的7个数组方法
const methods = ['pop','shift','unshift','sort','reverse','splice', 'push'];
// 复制Array.prototype,并将其prototype指向Array.prototype
let proto = Object.create(Array.prototype);
methods.forEach(method => {
proto[method] = function () { // 重写proto中的数组方法
Array.prototype[method].call(this, ...arguments);
viewRender() // 视图更新
function observe(obj) {
if (Array.isArray(obj)) { // 数组实现响应式
obj.__proto__ = proto; // 改变传入数组的prototype
return;
}
if (typeof obj === 'object') {
... // 对象的响应式实现
}
}
}
})
不能检查数组的解决办法
对于数组
1、this.$set(array, index, data)
//这是个深度的修改,某些情况下可能导致你不希望的结果,因此最好还是慎用
this.dataArr = this.originArr
this.$set(this.dataArr, 0, {data: '修改第一个元素'})
console.log(this.dataArr)
console.log(this.originArr) //同样的 源数组也会被修改 在某些情况下会导致你不希望的结果
2、splice
//因为splice会被监听有响应式,而splice又可以做到增删改。
3、利用临时变量进行中转
let tempArr = [...this.targetArr]
tempArr[0] = {data: 'test'}
this.targetArr = tempArr
对于对象
1、this.$set(obj, key ,value) - 可实现增、改
2、watch时添加deep:true深度监听,只能监听到属性值的变化,新增、删除属性无法监听
this.$watch('blog', this.getCatalog, {
deep: true
// immediate: true // 是否第一次触发
});
3、watch时直接监听某个key
watch: {
'obj.name'(curVal, oldVal) {
// TODO
}
}
六、Vue3.0 实现数据双向绑定的方法
vue3.0 实现数据双向绑定是通过Proxy。Proxy是 ES6 中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。 Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等), 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
七、Vue3.0实现数据双向绑定相比Vue2.0的优势
- 可以劫持整个对象,并返回一个新对象
- 有13种劫持操作
八、组件通信的方式
有8种:
-
props和$emit
这是最最常用的父子组件通信方式,父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的
// 子组件 标签v-model="事件名称" 事件名称(){ this.$emit("自定义事件名称", this.要传的数据) } // 父组件 标签 @自定义事件名称="父组件的事件名称" 父组件的事件名称(){ 事件具体操作 }
-
attrs和listeners
第一种方式处理父子组件之间的数据传输有一个问题:如果多层嵌套,父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?
如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。从Vue 2.4开始,提供了attrs和listeners来解决这个问题,能够让组件A之间传递消息给组件C。
-
v-model
父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input',val)自动修改v-model绑定的值
// 父组件 <div :key="val"></div> //子组件 用props接收 props: ["key"] 或者用对象写法 props:{key: String} //子组件视图更新 {{key}}
-
provide和inject
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
--------------------------------------------以上处理父子组件------------------------------------------------- -
中央事件总线bus
中央事件总线主要用来解决兄弟组件通信的问题。这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.emit触发事件,bus.on监听触发的事件。
// 兄弟1 引入兄弟2 bus.$emit("自定义事件名称", 传递的数据) // 兄弟2 引入兄弟1 mounted(){ bus.$on("自定义事件名称", 事件具体操作) }
-
parent和children
-
boradcast和dispatch
vue1.0中提供了这种方式,但vue2.0中没有,但很多开源软件都自己封装了这种方式,比如min ui、element ui和iview等。 比如如下代码,一般都作为一个mixins去使用, broadcast是向特定的父组件,触发事件,dispatch是向特定的子组件触发事件,本质上这种方式还是on和on和emit的封装,但在一些基础组件中却很实用
-
vuex处理组件之间的数据交互
如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的
九、vue keep-alive
是什么?
keep-alive
可以实现组件缓存,是Vue.js的一个内置组件。
作用:
- 它能够把不活动的组件实例保存在内存中,而不是直接将其销毁
- 它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中
使用方法:
-
常用的两个属性
include/exclude
,允许组件有条件的进行缓存。 -
两个生命周期
activated/deactivated
,用来得知当前组件是否处于活跃状态。 -
keep-alive的中还运用了
LRU(Least Recently Used)
算法。
原理:
Vue 的缓存机制并不是直接存储 DOM 结构,而是将 DOM 节点抽象成了一个个 VNode节点,所以,keep- alive的缓存也是基于VNode节点的而不是直接存储DOM结构。其实就是将需要缓存的VNode节点保存在this.cache中/在render时,如果VNode的name符合在缓存条件(可以用include以及exclude控制),则会从this.cache中取出之前缓存的VNode实例进行渲染。
十、既然函数是引用类型,为什么 vue 的 data 还是可以用函数
JavaScript只有函数构成作用域(注意理解作用域,只有函数{}构成作用域,对象的{}以及if(){}都不构成作用域),data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响。
十一、vue 中 $nextTick 作用与原理
作用:
是为了可以获取更新后的DOM 。由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick(),就是在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
原理:
在下次 DOM 更新循环结束之后执行延迟回调。nextTick主要使用了宏任务和微任务。根据执行环境分别尝试采用
- Promise
- MutationObserver
- setImmediate
- 如果以上都不行则采用setTimeout
定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。
十二、vue的特性
- 表单操作
- 自定义指令
- 计算属性
- 过滤器
- 侦听器
- 生命周期
十三、v-if与v-show的区别
区别一:展示形式不同
区别二:使用场景不同
v-show和v-if都是用来显示隐藏元素,v-if还有一个v-else配合使用,两者达到的效果都一样,但是v-if更消耗性能的,因为v-if在显示隐藏过程中有DOM的添加和删除,v-show就简单多了,只是操作css。
v-show不管条件是真还是假,第一次渲染的时候都会编译出来,也就是标签都会添加到DOM中。之后切换的时候,通过display: none;样式来显示隐藏元素。可以说只是改变css的样式,几乎不会影响什么性能。
v-if在首次渲染的时候,如果条件为假,什么也不操作,页面当作没有这些元素。当条件为真的时候,开始局部编译,动态的向DOM元素里面添加元素。当条件从真变为假的时候,开始局部编译,卸载这些元素,也就是删除。
十四、Vue 列表为什么加 key
vue中列表循环需加:key="唯一标识" ,唯一标识且最好是静态的,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件, key的作用主要是为了高效的更新虚拟DOM
十五、为什么选择用vue做页面展示
-
MVVM 框架:
Vue 正是使用了这种 MVVM 的框架形式,并且通过声明式渲染和响应式数据绑定的方式来帮助我们完全避免了对 DOM 的操作。
-
单页面应用程序
Vue 配合生态圈中的 Vue-Router 就可以非常方便的开发复杂的单页应用
-
轻量化与易学习
Vue 的生产版本只有 30.90KB 的大小,几乎不会对我们的网页加载速度产生影响。同时因为 Vue 只专注于视图层,单独的 Vue 就像一个库一样,所以使我们的学习成本变得非常低
-
渐进式与兼容性
Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。Vue 只做界面,而把其他的一切都交给了它的周边生态(axios(Vue 官方推荐)、Loadsh.js、Velocity.js 等)来做处理,这就要求 Vue 必须要对其他的框架拥有最大程度的兼容性
-
视图组件化
Vue 允许通过组件来去拼装一个页面,每个组件都是一个可复用的 Vue 实例,组件里面可以包含自己的数据,视图和代码逻辑。方便复用
-
虚拟 DOM(Virtual DOM)
Vue 之所以可以完全避免对 DOM 的操作,就是因为 Vue 采用了虚拟 DOM 的方式,不但避免了我们对 DOM 的复杂操作,并且大大的加快了我们应用的运行速度。
-
社区支持
得益于 Vue 的本土化身份(Vue 的作者为国人尤雨溪),再加上 Vue 本身的强大,所以涌现出了特别多的国内社区,这种情况在其他的框架身上是没有出现过的,这使得我们在学习或者使用 Vue 的时候,可以获得更多的帮助
-
未来的 Vue 走向
Vue 是由国人尤雨溪在 Google 工作的时候,为了方便自己的工作而开发出来的一个库,而在 Vue 被使用的过程中,突然发现越来越多的人喜欢上了它。所以尤雨溪就进入了一个边工作、边维护的状态,在这种情况下 Vue 依然迅速的发展。