编写VUE 双向数据绑定

使用页面: 

<!DOCTYPE html>
<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>Document</title>
  <script src="Observer.js"></script>
  <script src="MyVue.js"></script>
</head>

<body>
  <div id="mvvm-app">
    <input type="text" v-model="word">
    <p>{{message}}</p>
    <p>{{word}}</p>
    <button v-on:click="sayHi">change model</button>
  </div>
  <script type="text/javascript">
    new MyVue({
      el: '#mvvm-app',
      data: {
        message: "hello,MyVue",
        word: "my is input"
      },
      methods: {
        sayHi() {
         
          //获取1-10之间的随机数
          var num = Math.floor(Math.random() * 10 + 1);
            
          this.message = num;
        }
      },
      mounted() {

      }
    })
  </script>
 
</body>

</html>

Observer.js 

由:

Observer 监听器

Dep 通知器

Watcher 观察器处理刷新

/**
 * 
 * @param {} obj 类型判断
 */
let getType = (obj) => {
    return Object.prototype.toString.call(obj).match(/object\s*(\w*)/)[1]
}
function Watcher(vm, exp, fn) {
    this.fn = fn;
    this.exp = exp;
    this.vm = vm;
    this.getter = this.parseGetter(exp);
    this.value = this.get();
    this.update = function () {
        var value = this.get();
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.fn.call(this.vm, value, oldVal);
        }
    }
}

Watcher.prototype.get = function (exp) {
    Dep.target = this;
    var value = this.getter.call(this.vm, this.vm);
    Dep.target = null;
    return value;
}

Watcher.prototype.parseGetter = function (exp) {
    if (/[^\w.$]/.test(exp)) return;

    var exps = exp.split('.');

    return function (obj) {
        for (var i = 0, len = exps.length; i < len; i++) {
            if (!obj) return;
            obj = obj[exps[i]];
        }
        return obj;
    }
}

function Dep() {
    this.subs = [];

    this.addSub = function (watcher) {
        this.subs.push(watcher);
    }

    this.notify = function () {
        this.subs.forEach(function (watcher) {
            watcher.update();
        });
    }
}
function Observer(obj, key, value) {
    var dep = new Dep();
    if (Object.prototype.toString.call(value) == '[object Object]') {
        Object.keys(value).forEach(function (key) {
            new Observer(value, key, value[key])
        })
    };

    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function () {
            if (Dep.target) {
                dep.addSub(Dep.target);
            };
            return value;
        },
        set: function (newVal) {
            value = newVal;
            dep.notify();
        }
    })
}
/**
 * 
 * @param {} el 解析模板 
 */
function Compile(el, vm) {
    this.$vm = vm;
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    if (this.$el) {
        this.$fragment = this.node2Fragment(this.$el);
        this.init();
        this.$el.appendChild(this.$fragment);
    }
}

/**
 * 判断是否是元素
 */
Compile.prototype.isElementNode = function (el) {
    return el.nodeType === 1 ? true : false;
}

Compile.prototype.node2Fragment = function ($el) {
    /**
     * 创建虚拟document 片段
     */
    var fragment = document.createDocumentFragment(), child;
    while (child = $el.firstChild) {
        fragment.appendChild(child);
    }

    return fragment;
}

Compile.prototype.init = function () {
    this.compileElement(this.$fragment);
}

Compile.prototype.compileElement = function ($el) {

    /**
     * 获取#demo 的所有子元素
     */
    var childNodes = $el.childNodes

    new Array().slice.call(childNodes).forEach((node) => {
        //返回节点及其后代的文本内容
        var text = node.textContent;
        var reg = /\{\{(.*)\}\}/;
        /**
         * 判断是否为元素节点
         */
        if (node.nodeType === 1) {
            this.compile(node)
        }

        //文本节点
        if (node.nodeType === 3 && reg.test(text)) {

            compileUtil.text(node, this.$vm, RegExp.$1);
        }

        /**
         * 是否存在子节点
         */
        if (node.childNodes && node.childNodes.length) {
            this.compileElement(node);
        }
    })
}

/**
 * 处理元素节点解析指令
 */
Compile.prototype.compile = function (node) {
    var nodeAttrs = node.attributes
    new Array().slice.call(nodeAttrs).forEach((attr) => {
        //获得自定义属性名
        var attrName = attr.name;
        /**
         * 如果存在v- 表示指令
         */
        if (attrName.indexOf("v-") != -1) {
            var exp = attr.value;
            //列:v-on:click 去除v- 得到 on:click
            var dir = attrName.substring(2);
            //解析事件指令
            if (dir.indexOf('on') === 0) {
                compileUtil.eventHandler(node, this.$vm, exp, dir);
            }else{
                //判断指令是否 存在
                compileUtil[dir] && compileUtil[dir](node, this.$vm, exp);
            }
            
        }
    })
}


var compileUtil = {
    //注入文本
    text: function (node, vm, exp) {
        this.bind(node, vm, exp, 'text');
    },
    //绑定文本表单
    model:function(node, vm, exp){
        
        this.bind(node, vm, exp, 'model');
        var val = this._getVMVal(vm, exp);
        node.addEventListener('input', (e)=> {
            this._setVMVal(vm, exp,e.target.value);
        });
    },
    bind: function (node, vm, exp, dir) {
        var updaterFn = updater[dir + 'Updater'];

        updaterFn && updaterFn(node, this._getVMVal(vm, exp));

        new Watcher(vm, exp, (value, oldValue) => {
            updaterFn && updaterFn(node, value, oldValue);
        });
    },
    //注入事件
    eventHandler: function (node, vm, exp, dir) {
        var eventType = dir.split(':')[1],
            fn = vm.$options.methods && vm.$options.methods[exp];

        if (eventType && fn) {
            node.addEventListener(eventType, fn.bind(vm), false);
        }
    },
    _getVMVal: function (vm, exp) {
        var val = vm;
        exp = exp.split('.');
        //匹配this.$data 里面对应的值
        exp.forEach(function (k) {
            val = val[k];
        });
        return val;
    },
    _setVMVal: function (vm, exp,value) {
        vm[exp] = value
    }
}

var updater = {
    textUpdater: function (node, value) {
        node.textContent = typeof value == 'undefined' ? '' : value;
    },
    modelUpdater: function (node, value) {
        node.value = typeof value == 'undefined' ? '' : value;
    },
};

MyVue.js 实例

function MyVue(options) {
    if (getType(options) != 'Object') {
        throw ("参数必须为对象")
    }
    let $this = this;
    /**
     * 得到需要监听的Object 对象
     */
    this.$data = getType(options.data) != 'Object' ? {} : options.data;
    this.$options = options;

    this.proxyKeys();

    /**
     * 监听器,监听this.$data 的属性
     */
    Object.keys(this.$data).forEach((key) =>{
        Observer(this.$data,key,this.$data[key])
    });
    
    this.$compile = new Compile(options.el || document.body, this)

    /**
     * 执行mounted 函数方法,并且把作用域执行MyVue 实例
     */
    options.mounted.call(this)
}

/**
     * proxyKeys 代理属性将this.$data 里面的对象属性都绑定到MyVue 属性上
     * 列:this.$data.xxx 拥有可以this.xxx 实例直接获取对应属于他的属性
     */
MyVue.prototype.proxyKeys = function () {
    /**
     * 获取对象的所有可遍历属性
     */
    let keys = Object.keys(this.$data);
    /**
     * 为MyVue 添加this.xx 实例属性
     */
    keys.forEach((key) => {
        Object.defineProperty(this, key, {
            enumerable: true,
            configurable: false,
            set(newVal) {
                this.$data[key] = newVal
            },
            get() {
                return this.$data[key];
            }
        })
    })
}

猜你喜欢

转载自blog.csdn.net/weixiaoderensheng/article/details/83084413