一、Vue2.0响应式原理
下面是vue官方文档的内容,根据我个人的阅读思维习惯,做了些许的排版调整,便于以后自己时常回头梳理消化。
另外,推荐反复看这个文章,反复看!剖析Vue实现原理 - 如何实现双向绑定mvvm
1、如何追踪变化
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 为这些 property 设置 getter/setter函数。
这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
2、检测变化的注意事项
- 由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上初始存在,才能让 Vue 将它转换为响应式的。
- 对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。
3、不能检测数组和对象的变化
(1)对于对象
Vue 不能检测对象新增的key变化,例如:vm.someObject.b = 2
可以使用 Vue.set(object, propertyName, value) 方法或者使用 vm.$set 实例方法,向对象添加响应式 property。
Vue.set(vm.someObject, 'b', 2)
this.$set(this.someObject,'b',2)
- 有时你可能需要为已有对象赋值多个新 property,比如使用
Object.assign()或_.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
(2)对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
其实Object.defineProperty是可以检测到通过索引改变数组的操作的,那Vue2.0为什么没有实现呢?主要是考虑性能问题,避免数组中元素过多,进行大量遍历。具体分析文章参考:vue为什么不能检测数组的变化 - 当你修改数组的长度时,例如:
vm.items.length = newLength
为了解决第1个问题,有以下两种方式:
set方法
Vue.set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, indexOfItem, newValue)
splice方法
vm.items.splice(indexOfItem, 1, newValue)
为了解决第2类问题,你可以使用 splice:
vm.items.splice(newLength)
4、异步更新队列——$nextTick()
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,在更新 DOM 时是异步执行的.简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)或者vm.$nextTick(callback) 实例方法,这样回调函数callback将在 DOM 更新完成后被调用。
另外,可以阅读这个文章,学习一下 Vue.nextTick 的原理和用途
// html
<div id="example">{{message}}</div>
// js
// 1、使用Vue.nextTick方法
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
// 2、使用vm.$nextTick实例方法
// 在组件内使用 vm.$nextTick() 实例方法特别方便,因为它不需要全局 Vue,并且回调函数中的 this 将自动绑定到当前的 Vue 实例上:
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: '未更新'
}
},
methods: {
updateMessage: function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
this.$nextTick(function () {
console.log(this.$el.textContent) // => '已更新'
})
}
}
})
二、唯一key值的作用
1、key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点。
为什么 Vue 中不要用 index 作为 key?(diff 算法详解)
为什么使用v-for时必须添加唯一的key?
当我们在使用v-for时,需要给单元加上key
如果不用key,Vue会采用“就地更新”策略:最小化element的移动,并且会尝试尽最大程度在同适当的地方对相同类型的element,做patch或者reuse。
如果使用了key,Vue会根据keys的顺序记录element,曾经拥有了key的element如果不再出现的话,会被直接remove或者destoryed
2、不建议用index作为v-for循环的key
对于删除、翻转、插入等会改变原数组元素顺序的场景,index作为key的话,会导致更新dom的开销很大
3、不能用random()随机数作为v-for循环的key
明明是相同的节点,却因为key是2次随机数被判定为不同的节点,节点会经历销毁、重新创建的过程,开销简直就是毁灭性的
三、vue中的attrs、listeners、inheritAttrs
1、inheritAttrs属性和attrs实例方法的参考文章# Vue 2.4.0新增inheritAttrs,attrs详解
2、attrs与listeners实例方法的参考文章# attrs与listeners的详解
四、组件上使用.sync和v-model的实现原理
1、.sync修饰符
在子组件通过以update:myPropName 的模式触发事件,在父组件监听这个事件并更新一个本地的数据 property。举例:
//子组件 包含名为title的prop,我们可以用以下方法表达对其赋新值的意图:
this.$emit('update:title', myNewTitle)
//父组件
<father
v-bind:title="myTitle"
v-on:update:title="myTitle = $event"
></father>
为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:
<father v-bind:title.sync="myTitle"></father>
2、v-model(本质上只是语法糖)
(1)表单 <input>、<textarea> 及 <select> 元素上使用v-model
v-model在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
valueproperty 和input事件; - checkbox 和 radio 使用
checkedproperty 和change事件; - select 字段将
value作为 prop 并将change作为事件。
`<input v-model = 'something'>`
相当于
<input v-bind:value="something" v-on:input="something=$event.target.value">
(2)自定义组件上使用v-model
在组件上,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,也即:
第1步:父组件通过v-bind将本地数据绑定给子组件的一个名为value的prop
第2步:子组件的value值发生变化,则通过emit抛出一个名为input的事件
第3步:父组件通过捕获input事件,将本地数据的值更新为最新的value的值
<custom-input v-model="searchText"></custom-input>
// 相当于
<custom-input
v-bind:value="searchText"
v-on:input="searchText = $event"
></custom-input>
//custom-input组件的代码需这样编写
Vue.component('custom-input', {
props: ['value'],
template: `
<input
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
`
})
但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突: