MVVM实现原理(数据变更的实现)

来源:互联网 发布:mac os支持office 2016 编辑:程序博客网 时间:2024/06/10 19:37

手动触发绑定

手动触发指令绑定是比较直接的实现方式,主要思路是通过在数据对象上定义get()方法和set()方法,调用时手动触发get()或set()函数来获取、修改数据,改变数据后会主动触发get()和set()函数中View层的重新渲染功能。根据View来驱动ViewModel变化的场景主要应用于<input>、<select>、<textarea> 等元素,当用户输入内容变化时,通过监听DOM的change,select、keyup等事件来触发操作改变ViewModel的数据。

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title></head><body><input q-value="value" type="text" id="input"><span q-text="value" id="el"></span><script>let elems=[document.getElementById('input'),document.getElementById('el')];let data={value:'hello'};let directive={    text:function (text) {        this.innerHTML=text;    },    value:function (value) {        this.setAttribute('value',value);    }}function scan() {    for(let elem of elems){        elem.directive=[];        for(let attr of elem.attributes){            if(attr.nodeName.indexOf('q-')>=0){                directive[attr.nodeName.slice(2)].call(elem,data[attr.nodeValue]);                elem.directive.push(attr.nodeName.slice(2));            }        }    }}function ViewModelSet(key,value) {    data[key]=value;    scan();}scan();setTimeout(function () {    ViewModelSet('value','helloouvenzhang');},1000);if(document.addEventListener){    elems[0].addEventListener('keyup',function (e) {        ViewModelSet('value',e.target.value);    },false);}</script></body></html>

前端数据对象挟持

其基本思路是使用Object.defineProperty和Object.defineProperties对ViewModel数据对象进行属性get()和set()的监听,当有数据读取和赋值操作时则扫描元素节点,运行对应节点的Directive指令。

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title></head><body><input q-value="value" type="text" id="input"><div q-text="value" id="el"></div><script>let elems=[document.getElementById('el'),document.getElementById('input')];let data={value:'hello'};let directive={    text:function (text) {        this.innerHTML=text;    },    value:function (value) {        this.setAttribute('value',value);    }};let bValue;scan();defineGetAndSet(data,'value');if(document.addEventListener){    elems[1].addEventListener('keyup',function (e) {        data.value=e.target.value;    },false);} else {    elems[1].attachEvent('onkeyup',function (e) {        data.value=e.srcElement.value;    },false);}setTimeout(function () {    data.value='helloouvenzhang';},2000);function scan() {    for(let elem of elems){        elem.directive=[];        for(let attr of elem.attributes){            if(attr.nodeName.indexOf('q-')>=0){                directive[attr.nodeName.slice(2)].call(elem,data[attr.nodeValue]);                elem.directive.push(attr.nodeName.slice(2));            }        }    }}function defineGetAndSet(obj,propName) {    Object.defineProperty(obj,propName,{        get:function () {            return bValue;        },        set:function (newValue) {            bValue=newValue;            scan();        },        enumerable:true,        configurable:true    })}</script></body></html>

ES6 Proxy

Proxy特性可以用于在已有的对象基础上重新定义一个对象,并重新定义对象原型上的方法,包括get()和set()方法。

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Document</title></head><body><input q-value="value" type="text" id="input"><span q-text="value" id="el"></span><script>    let elems=[document.getElementById('el'),document.getElementById('input')];    let directive={        text:function (text) {            this.innerHTML=text;        },        value:function (value) {            this.setAttribute('value',value);        }    };    let data = new Proxy({},{        get:function (target,key,receiver) {            return target.value;        },        set:function (target,key,value,receiver) {            target.value=value;            scan();            return target.value;        }    });    data['value']='hello';    scan();    if(document.addEventListener){        elems[1].addEventListener('keyup',function (e) {            data.value=e.target.value;        },false);    } else {        elems[1].attachEvent('onkeyup',function (e) {            data.value=e.srcElement.value;        },false);    }    setTimeout(function () {        data.value='hello ouvenzhang';    },1000);    function scan() {        for(let elem of elems){            elem.direction=[];            for(let attr of elem.attributes){                if(attr.nodeName.indexOf('q-')>=0){                    directive[attr.nodeName.slice(2)].call(elem,data[attr.nodeValue]);                    elem.directive.push(attr.nodeName.slice(2));                }            }        }    }</script></body></html>

vue.js实现原理

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Two-way data-binding</title></head><body><div id="app">    <input type="text" v-model="text">    {{ text }}</div><script>    function observe (obj, vm) {        Object.keys(obj).forEach(function (key) {            defineReactive(vm, key, obj[key]);        });    }    function defineReactive (obj, key, val) {        var dep = new Dep();        Object.defineProperty(obj, key, {            get: function () {                if (Dep.target) dep.addSub(Dep.target);                return val            },            set: function (newVal) {                if (newVal === val) return                val = newVal;                dep.notify();            }        });    }    function nodeToFragment (node, vm) {        var flag = document.createDocumentFragment();        var child;        while (child = node.firstChild) {            compile(child, vm);            flag.appendChild(child);        }        return flag;    }    function compile (node, vm) {        var reg = /\{\{(.*)\}\}/;        // 节点类型为元素        if (node.nodeType === 1) {            var attr = node.attributes;            // 解析属性            for (var i = 0; i < attr.length; i++) {                if (attr[i].nodeName == 'v-model') {                    var name = attr[i].nodeValue; // 获取v-model绑定的属性名                    node.addEventListener('input', function (e) {                        // 给相应的data属性赋值,进而触发该属性的set方法                        vm[name] = e.target.value;                    });                    node.value = vm[name]; // 将data的值赋给该node                    node.removeAttribute('v-model');                }            }            new Watcher(vm, node, name, 'input');        }        // 节点类型为text        if (node.nodeType === 3) {            if (reg.test(node.nodeValue)) {                var name = RegExp.$1; // 获取匹配到的字符串                name = name.trim();                new Watcher(vm, node, name, 'text');            }        }    }    function Watcher (vm, node, name, nodeType) {//        this为watcher函数        Dep.target = this;//        console.log(this);        this.name = name;        this.node = node;        this.vm = vm;        this.nodeType = nodeType;        this.update();        Dep.target = null;    }    Watcher.prototype = {        update: function () {            this.get();            if (this.nodeType == 'text') {                this.node.nodeValue = this.value;            }            if (this.nodeType == 'input') {                this.node.value = this.value;            }        },        // 获取daa中的属性值        get: function () {            this.value = this.vm[this.name]; // 触发相应属性的get        }    }    function Dep () {        this.subs = []    }    Dep.prototype = {        addSub: function(sub) {            this.subs.push(sub);        },        notify: function() {            this.subs.forEach(function(sub) {                sub.update();            });        }    };    function Vue (options) {        this.data = options.data;        var data = this.data;        observe(data, this);        var id = options.el;        var dom = nodeToFragment(document.getElementById(id), this);        // 编译完成后,将dom返回到app中        document.getElementById(id).appendChild(dom);    }    var vm = new Vue({        el: 'app',        data: {            text: 'hello world'        }    });</script></body></html>
原创粉丝点击