Vue 2.0双方向バインド原理の実現

29208 ワード

Object.definePropertyメソッド
vue.jsはデータハイジャックとパブリッシャー-サブスクライバモードを組み合わせた方式を採用し、Object.defineProperty()は、各属性のsetter,getterをハイジャックし、データが変動したときに購読者にメッセージを発行し、対応するリスニングコールバックをトリガーします.
具体的な実装プロセスのコードは次のとおりです.
  • 定義コンストラクタ
    function Vue(option){
        this.$el = document.querySelector(option.el);   //      
        this.$data = option.data;
        this.$methods = option.methods;
        this.deps = {};     //            (      ):{msg: [   1,    2,    3], info: [   1,    2]}
        this.observer(this.$data);  //     
        this.compile(this.$el);     //       
    }
    
  • 定義命令解析器
    Vue.prototype.compile = function (el) {
        let nodes = el.children; //          
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            if (node.children.length) {
                this.compile(node) //       
            }
            if (node.hasAttribute('l-model')) { //      l-model  
                let attrVal = node.getAttribute('l-model'); //     
                node.addEventListener('input', (() => {
                    this.deps[attrVal].push(new Watcher(node, "value", this, attrVal)); //       
                    let thisNode = node;
                    return () => {
                        this.$data[attrVal] = thisNode.value //        
                    }
                })())
            }
            if (node.hasAttribute('l-html')) {
                let attrVal = node.getAttribute('l-html'); //     
                this.deps[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)); //       
            }
            if (node.innerHTML.match(/{{([^\{|\}]+)}}/)) {
                let attrVal = node.innerHTML.replace(/[{{|}}]/g, '');   //         
                this.deps[attrVal].push(new Watcher(node, "innerHTML", this, attrVal)); //       
            }
            if (node.hasAttribute('l-on:click')) {
                let attrVal = node.getAttribute('l-on:click'); //          
                node.addEventListener('click', this.$methods[attrVal].bind(this.$data)); // this  this.$data
            }
        }
    }
    
  • 観察者
    Vue.prototype.observer = function(data){
        for(var key in data){
            (function(that){
                let val = data[key];    //         
                that.deps[key] = [];    //          {msg: [   ], info: []}
                var watchers = that.deps[key];
                Object.defineProperty(data, key, {  //    
                    get: function(){
                        return val;
                    },
                    set: function(newVal){  //   (       )
                        if(val !== newVal){
                            val = newVal;
                        }
                        //      
                        watchers.forEach(watcher=>{
                            watcher.update()
                        })
                    }
                })
            })(this)
        }
    }
    
    
  • を定義する.
  • 定義購読者
    function Watcher(el, attr, vm, attrVal) {
        this.el = el;
        this.attr = attr;
        this.vm = vm;
        this.val = attrVal;
        this.update(); //    
    }
    
  • ビュー
    Watcher.prototype.update = function () {
        this.el[this.attr] = this.vm.$data[this.val]
    }
    
  • を更新する.
    以上のコードは1つのVueに定義.jsファイルでは、双方向バインドを使用する必要がある場所に導入すればよい.使用してみます.
    
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Documenttitle>
        <script src="./vue.js">script>
    head>
    <body>
        
        <div id="app">
            <input type="text" l-model="msg" >
            <p l-html="msg">p>
            <input type="text" l-model="info" >
            <p l-html="info">p>
            <button l-on:click="clickMe">  button>
            <p>{{msg}}p>
        div>
    
        <script>
            var vm = new Vue({
                el: "#app",
                data: {
                    msg: "    ",
                    info: "    ,     "
                },
                methods: {
                    clickMe(){
                        this.msg = "     ";
                    }
                }
            })
        script>
    body>
    html>