学习vue的深入响应式原理

深入响应式原理

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.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变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如: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不是prepre-wrap则会表现为空格。

        <div id="test"><b style="display: block;">some</b>text</div>
    
image.png
  • display:none元素是无法使用innerText获取的,但是textContent却可以,无论元素隐藏与否。

     <div id="test"><b style="display: none;">some</b>text</div>
     ...
    
image.png
  • 由于innerText属性值的获取会考虑CSS样式,因此读取innerText的值将触发回流以确保计算出的样式是最新的。

  • 如果你要在一个DOM元素中改变文字内容,推荐使用textContent,而不是innerHTML,性能会更高一点。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。