深入响应式原理
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data
选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty
把这些 property 全部转为 getter/setter。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
检测变化
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
对于对象
Vue 无法检测 property 的添加或移除,对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式 property。
var vm = new Vue({
el: "#app",
data: {
message: '',
obj: {
name: 'sera',
age: 23
}
}
})
-
可以使用
Vue.set(object, propertyName, value)
方法Vue.set(vm.obj, 'job', 'coding');
-
使用
vm.$set
实例方法,这也是全局Vue.set
方法的别名在任意函数中:
this.$set(this.obj,"hobby", "reading")
为已有对象赋值多个新 property,比如使用
Object.assign()
vm.obj = Object.assign({},vm.obj,{hight:170,weight:'45kg'})
对于数组
Vue 不能检测以下数组的s变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
方法:
-
Vue.set(vm.items, indexOfItem, newValue)
Vue.set(vm.items, 2, 'cc')
-
vm.$set(vm.items, indexOfItem, newValue)
vm.$set(vm.items, 4, 'ee')
vm.items.splice(indexOfItem, 1, newValue)
vm.items.splice(newLength)(针对第二类问题)
this.$set()的源码:
import { set } from '../observer/index'
...
Vue.prototype.$set = set
...
Vue.set()和this.$set()这两个api的实现原理基本一模一样,都是使用了set函数。set函数是从 ../observer/index 文件中导出的,区别在于Vue.set()是将set函数绑定在Vue构造函数上,this.$set()是将set函数绑定在Vue原型上。
异步更新队列
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。
nextTick
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
// 修改数据
vm.msg = 'Hello'
// DOM 还没有更新
Vue.nextTick(function () {
// DOM 更新了
})
// 作为一个 Promise 使用
Vue.nextTick()
.then(function () {
// DOM 更新了
})
//可以使用async
async update() {
this.msg = '已更新';
console.log(this.$el.textContent); //'未更新'
// this.$nextTick(function(){
// console.log(this.$el.textContent);//'已更新'
// })
await this.$nextTick();
console.log(this.$el.textContent);//'已更新'
}
textContent &innerText
上面用到了textContent,查了一下和innerText的区别:
console.log('innerText--' + document.getElementById('test').innerText);
let text = document.getElementById('test').textContent
console.log('TextContent--'+text);
-
innerText
会保留块级元素的换行特性,以换行符形式呈现。在HTML中,如果white-space
不是pre
或pre-wrap
则会表现为空格。<div id="test"><b style="display: block;">some</b>text</div>
-
display:none
元素是无法使用innerText
获取的,但是textContent
却可以,无论元素隐藏与否。<div id="test"><b style="display: none;">some</b>text</div> ...
由于
innerText
属性值的获取会考虑CSS样式,因此读取innerText
的值将触发回流以确保计算出的样式是最新的。如果你要在一个DOM元素中改变文字内容,推荐使用
textContent
,而不是innerHTML
,性能会更高一点。