• Vue——响应式原理


    Vue的MVVM思想中,主要是靠VM 视图-模型完成响应,充当数据与视图之间的桥梁,数据更新响应视图、视图文本数据更新响应数据。

    • 数据劫持
    • 发布订阅

      数据劫持指的是vue利用ES5的Object.defineProperty属性对data选项中的数据进行getter和setter设置;
      发布订阅指 的是vue通过自定义事件将data的变化反应到视图上去,vue通过observe观察者对象反应数据的变化,然后通知vue生成新的vdom,进而渲染视图。

    一、如何实现Object.defineProperty

      Vue通过设定对象属性的getter/setter 方法来监听数据的变化。通过getter进行依赖收集,而每个setter方法就是一个观察者,在数据变更的时候通知订阅者更新视图。

    function observe(value, cb) {
        Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
    }
    
    function defineReactive (obj, key, val, cb) {
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                /*....依赖收集等....*/
                return val
            },
            set:newVal=> {
                val = newVal;
                cb();/*订阅者收到消息的回调*/
            }
        })
    }
    
    class Vue {
        constructor(options) {
            this._data = options.data;
            observe(this._data, options.render) //循环遍历每一个data的数据、进行依赖收集和观察
        }
    }
    
    let app = new Vue({
        el: '#app',
        data: {
            text: 'text',
            text2: 'text2'
        },
        render(){
            console.log("render");
        }
    })

     二、依赖收集(为什么不是二维数组?)

    先看下这段代码:

    new Vue({
        template: 
            `<div>
                <span>text1:</span> {{text1}}
                <span>text2:</span> {{text2}}
            <div>`,
        data: {
            text1: 'text1',
            text2: 'text2',
            text3: 'text3'
        }
    });

    按照响应式原理,数据text3被修改的时候也会触发setter导致出现渲染,这显然是不正确的。(因为没有数据依赖)。

    1.先说说Dep,订阅者列表

      当对data上的对象进行修改值的时候会触发它的setter,那么取值的时候自然就会触发getter事件,所以我们只要在最开始进行一次render,那么所有被渲染所依赖的data中的数据就会被getter收集到Dep的subs中去。在对data中的数据进行修改的时候setter只会触发Dep的subs的函数。

    定义一个依赖收集类Dep。(subs:二维数组,键值对,每个键名都有对应的一张订阅者表。)

    class Dep {
        constructor () {
            this.subs = [];
        }
    
        addSub (sub: Watcher) {
            this.subs.push(sub)
        }
    
        removeSub (sub: Watcher) {
            remove(this.subs, sub)
        }
        notify () {
            const subs = this.subs.slice()
            for (let i = 0, l = subs.length; i < l; i++) {
                subs[i].update()
            }
        }
    }
    function remove (arr, item) {
        if (arr.length) {
            const index = arr.indexOf(item)
            if (index > -1) {
                return arr.splice(index, 1)
            }
        }
    }

    2.订阅者 watcher

      当依赖收集的时候会addSub到sub中,在修改data中数据的时候会触发dep对象的notify,通知所有Watcher对象去修改对应视图。

    class Watcher {
        constructor (vm, expOrFn, cb, options) {
            this.cb = cb;
            this.vm = vm;
    /*在这里将观察者本身赋值给全局的target,只有被target标记过的才会进行依赖收集*/ Dep.target = this; /*Github:https://github.com/answershuto*/ /*触发渲染操作进行依赖收集*/ this.cb.call(this.vm); } update () { this.cb.call(this.vm); } }

    3.开始收集依赖

    class Vue {
        constructor(options) {
            this._data = options.data;
            observer(this._data, options.render);
            let watcher = new Watcher(this, );
        }
    }
    
    function defineReactive (obj, key, val, cb) {
        /*在闭包内存储一个Dep对象*/
        const dep = new Dep();
    
        Object.defineProperty(obj, key, {
            enumerable: true,
            configurable: true,
            get: ()=>{
                if (Dep.target) {
                    /*Watcher对象存在全局的Dep.target中*/
                    dep.addSub(Dep.target);
                }
            },
            set:newVal=> {
                /*只有之前addSub中的函数才会触发*/
                dep.notify();
            }
        })
    }
    Dep.target = null;

      将观察者Watcher实例赋值给全局的Dep.target,然后触发render操作只有被Dep.target标记过的才会进行依赖收集。有Dep.target的对象会将Watcher的实例push到subs中,在对象被修改触发setter操作的时候dep会调用subs中的Watcher实例的update方法进行渲染。

    三、总结

      首先说一下template解析成AST以及render function的过程:主要是通过正则解析模板成为AST树,然后会将AST树编译成render function,其中运用来缓存等优化方法都是值得一读的。

      接着从Data开始看,当数据Data发生变化时,会触发setter,setter会触发闭包中的Dep通知所有对该数据进行观察的观察者对象Watcher,Watcher会调用_update来更新视图,_update的第一个参数是一个render函数,然后返回一个VNode节点。

      新的VNode节点会与之前的VNode节点进行一个patch的过程,比较得出最小单位的修改。最后将这些修改渲染到真实DOM上。

  • 相关阅读:
    JS中数组去除重复的方法
    ember.js里的实用方法
    Ember入门指南——教程目录
    如何解决问题?
    Web前端开发工程师基本要求
    (转)轻松学习JavaScript三:JavaScript与HTML的结合
    (转)JavaScript二:JavaScript语言的基本语法要求
    HTML的checkbox和radio的美化
    C#串口通信—向串口发送数据,同步接收返回数据
    C#生成验证码
  • 原文地址:https://www.cnblogs.com/jiox/p/14540312.html
Copyright © 2020-2023  润新知