Vue基本原理 (三)

3、响应式原理
核心思想:Object.defineProperty(obj, key, {set, get})

    function defineReact(obj, key, value){
        Object.defineProperty(obj, key, {
            set: function(newValue){
                console.log(`触发setter`);
                value = newValue;
                console.log(value);
            },
            get: function(){
                console.log(`触发getter`);
                return value;
            }
        });
    }

  这里是针对data数据的属性的响应式定义,但是如何去实现vue实例vm绑定data每个属性,通过以下方法:

   function observe(obj, vm){
        Object.keys(obj).forEach((key) => {
            defineReact(vm, key, obj[key]);
        })
    }

  vue的构造函数:

    function Vue(options){
        this.data = options.data;
        let id = options.el;
 
        observe(this.data, this); // 将每个data属相绑定到Vue的实例上this
    }

  

通过以上我们可以实现Vue实例绑定data属性。

如何去实现Vue,通常我们实例化Vue是这样的:

var vm = new Vue({
        el: 'container',
        data: {
            msg: 'Hello world!',
            inpText: 'Input text'
        }
    });
    
    console.log(vm.msg); // Hello world!
    console.log(vm.inpText); // Input text

 实现以上效果,我们必须在vue内部初始化虚拟Dom

function Vue(options){
        this.data = options.data;
        let id = options.el;
 
        observe(this.data, this); // 将每个data属相绑定到Vue的实例上this
        
        //------------------------添加以下代码
        let container = document.getElementById(id);
        let fragment = virtualDom(container, this); // 这里通过vm对象初始化
        container.appendChild(fragment);
        
    }

  这是我们再对Vue进行实例化,则可以看到以下页面:

 

 

 至此我们实现了dom的初始化,下一步我们在v-model元素添加监听事件,这样就可以通过view层的操作来修改vm对应的属性值。在compile编译的时候,可以准确的找到v-model属相元素,因此我们把监听事件添加到compile内部。

function compile(node, data){
        let reg = /\{\{(.*)\}\}/g;
        if(node.nodeType === 1){ // 标签
            let attr = node.attributes;
            for(let i = 0, len = attr.length; i < len; i++){
                // console.log(attr[i].nodeName, attr[i].nodeValue);
                if(attr[i].nodeName === 'v-model'){
                    let name = attr[i].nodeValue;
                    node.value = data[name];
 
                    // ------------------------添加监听事件
                    node.addEventListener('keyup', function(e){
                        data[name] = e.target.value;
                    }, false);
                    // -----------------------------------
                }
            }
            if(node.hasChildNodes()){
                node.childNodes.forEach((item) => {
                    compile(item, data);
                });
            }
        }
        if(node.nodeType === 3){ // 文本节点
            if(reg.test(node.nodeValue)){
                let name = RegExp.$1;
                name = name.trim();
                node.nodeValue = data[name];
            }
        }
    }

  这一步我们操作页面输入框,可以看到以下效果,证明监听事件添加有效。

 

var subscribe_1 = {
        update: function(){
            console.log('This is subscribe_1');
        }
    };
    var subscribe_2 = {
        update: function(){
            console.log('This is subscribe_2');
        }
    };
    var subscribe_3 = {
        update: function(){
            console.log('This is subscribe_3');
        }
    };

  

 到这里我们已经实现了MVVM的,即Model -> vm -> View || View -> vm -> Model 中间桥梁就是vm实例对象。

4、观察者模式原理

观察者模式也称为发布者-订阅者模式,这样说应该会更容易理解,更加形象。
订阅者:

var subscribe_1 = {
        update: function(){
            console.log('This is subscribe_1');
        }
    };
    var subscribe_2 = {
        update: function(){
            console.log('This is subscribe_2');
        }
    };
    var subscribe_3 = {
        update: function(){
            console.log('This is subscribe_3');
        }
    };

  三个订阅者都有update方法。

发布者:

function Publisher(){
        this.subs = [subscribe_1, subscribe_2, subscribe_3]; // 添加订阅者
    }
    Publisher.prototype = {
        constructor: Publisher,
        notify: function(){
            this.subs.forEach(function(sub){
                sub.update();
            })
        }
    };

  发布者通过notify方法对订阅者广播,订阅者通过update来接受信息。
实例化publisher:

 var publisher = new Publisher();
    publisher.notify();

 

  

 

 

 这里我们可以做一个中间件来处理发布者-订阅者模式:

 var publisher = new Publisher();
    var middleware = {
        publish: function(){
            publisher.notify();
        }
    };
    middleware.publish();

  

5、观察者模式嵌入
到这一步,我们已经实现了:
1、修改v-model属性元素 -> 触发修改vm的属性值 -> 触发set
2、发布者添加订阅 -> notify分发订阅 -> 订阅者update数据 
接下来我们要实现:更新视图,同时把订阅——发布者模式嵌入。

发布者:

function Publisher(){
        this.subs = []; // 订阅者容器
    }
    Publisher.prototype = {
        constructor: Publisher,
        add: function(sub){
            this.subs.push(sub); // 添加订阅者
        },
        notify: function(){
            this.subs.forEach(function(sub){
                sub.update(); // 发布订阅
            });
        }
    };

  订阅者:
考虑到要把订阅者绑定data的每个属性,来观察属性的变化,参数:name参数可以有compile中获取的name传参。由于传入的node节点类型分为两种,我们可以分为两订阅者来处理,同时也可以对node节点类型进行判断,通过switch分别处理。

function Subscriber(node, vm, name){
        this.node = node;
        this.vm = vm;
        this.name = name;
    }
    Subscriber.prototype = {
        constructor: Subscriber,
        update: function(){
            let vm = this.vm;
            let node = this.node;
            let name = this.name;
            switch(this.node.nodeType){
                case 1:
                    node.value = vm[name];
                    break;
                case 3:
                    node.nodeValue = vm[name];
                    break;
                default:
                    break;
            }
        }
    };

  我们要把订阅者添加到compile进行虚拟dom的初始化,替换掉原来的赋值:

 function compile(node, data){
        let reg = /\{\{(.*)\}\}/g;
        if(node.nodeType === 1){ // 标签
            let attr = node.attributes;
            for(let i = 0, len = attr.length; i < len; i++){
                // console.log(attr[i].nodeName, attr[i].nodeValue);
                if(attr[i].nodeName === 'v-model'){
                    let name = attr[i].nodeValue;
                    // --------------------这里被替换掉
                    // node.value = data[name]; 
                    new Subscriber(node, data, name);
 
                    // ------------------------添加监听事件
                    node.addEventListener('keyup', function(e){
                        data[name] = e.target.value;
                    }, false);
                }
            }
            if(node.hasChildNodes()){
                node.childNodes.forEach((item) => {
                    compile(item, data);
                });
            }
        }
        if(node.nodeType === 3){ // 文本节点
            if(reg.test(node.nodeValue)){
                let name = RegExp.$1;
                name = name.trim();
                // ---------------------这里被替换掉
                // node.nodeValue = data[name];
                new Subscriber(node, data, name);
            }
        }
    }

  既然是对虚拟dom编译初始化,Subscriber要初始化,即Subscriber.update,因此要对Subscriber作进一步的处理:

function Subscriber(node, vm, name){
        this.node = node;
        this.vm = vm;
        this.name = name;
        
        this.update();
    }
    Subscriber.prototype = {
        constructor: Subscriber,
        update: function(){
            let vm = this.vm;
            let node = this.node;
            let name = this.name;
            switch(this.node.nodeType){
                case 1:
                    node.value = vm[name];
                    break;
                case 3:
                    node.nodeValue = vm[name];
                    break;
                default:
                    break;
            }
        }
    };

  发布者添加到defineReact,来观察数据的变化:

function defineReact(data, key, value){
        let publisher = new Publisher();
        Object.defineProperty(data, key, {
            set: function(newValue){
                console.log(`触发setter`);
                value = newValue;
                console.log(value);
                publisher.notify(); // 发布订阅
            },
            get: function(){
                console.log(`触发getter`);
                if(Publisher.global){ //这里为什么来添加判断条件,主要是让publisher.add只执行一次,初始化虚拟dom编译的时候来执行
                    publisher.add(Publisher.global); // 添加订阅者
                }
                return value;
            }
        });
    }

  

这一步将订阅者添加到发布者容器内,对订阅者改造:

function Subscriber(node, vm, name){
        Publisher.global = this;
        this.node = node;
        this.vm = vm;
        this.name = name;
        
        this.update();
        Publisher.global = null;
    }
    Subscriber.prototype = {
        constructor: Subscriber,
        update: function(){
            let vm = this.vm;
            let node = this.node;
            let name = this.name;
            switch(this.node.nodeType){
                case 1:
                    node.value = vm[name];
                    break;
                case 3:
                    node.nodeValue = vm[name];
                    break;
                default:
                    break;
            }
        }
    };

                                         点击这里=》             效果图+总结

posted @ 2022-05-12 11:19  南风轻语、  阅读(44)  评论(0)    收藏  举报