美文网首页
Vue 绑定简单分析实现

Vue 绑定简单分析实现

作者: deep_sadness | 来源:发表于2018-05-25 00:11 被阅读59次
    双向绑定示意图.png

    使用js es6 中 Object.defineProperty为我们自己定义的VM创建示例。同时这个方法通过提供了set.get方法的触发我们的监听事件。

    分析

    数据监听的基本要素

    通过这样的思路来理解这里的模型。

      1. 事件的触发
      1. 事件的分发场所
      1. 事件的响应

    1. 事件的触发

    通过Object.defineProperty方法定义的属性。在getset方法内进行事件的触发。将事件分发给监听者watcher(就是后面所说的事件分发的场所)。
    这里通过Object.defineProperty定义属性的方式来进行,对Array的方法,就没办法监听到。vue对list的一些方法进行了hook,来触发。

    const aryMethods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
    const arrayAugmentations = [];
    
    aryMethods.forEach((method)=> {
    
        // 这里是原生Array的原型方法
        let original = Array.prototype[method];
    
       // 将push, pop等封装好的方法定义在对象arrayAugmentations的属性上
       // 注意:是属性而非原型属性
        arrayAugmentations[method] = function () {
            //触发事件,分发给watcher
    
            // 调用对应的原生方法并返回结果
            return original.apply(this, arguments);
        };
    
    });
    
    

    同样,事件的触发器只要能hook到就可以做到。后续的Proxy

    2. 事件的分发场所

    事件分发的场所。需要凑齐两个因素。

    • 分发的事件
    • 事件的处理者(真正的监听对象)

    事件的处理者,在这里监听到到我们需要的事件。
    所以初始化的时候,需要对事件监听的对象和监听的事件,都入场。

    3. 事件的响应

    事件的监听对象,对事件,进行自己的响应。

    代码逻辑

    针对上面的思路。来理一下代码结构

    事件的触发

    通过defineProperty中的set方法,进行事件的触发

        //接下来需要实现_obserse函数,对data进行处理,重写data的set和get函数
        myVue.prototype._obverse=function (obj) {
            // body...
            var value;
            for (key in obj) {  //遍历对象。给每个对象添加监听事件
                if (obj.hasOwnProperty(key)) {  //如果有这个属性
                    //为这个属性添加这个指令监听者
                    this._binding[key]={
                        _directives:[]
                    }
    
                    value=obj[key]
                    if (typeof value === 'object') {    //如果这个值还是对象,则继续处理
                        this._obverse(value)
                    }
    
                    var binding =this._binding[key]
                    //定义Property,一直以来说的关键!!!
                    Object.defineProperty(this.$data,key,{
                        enumerable:true,
                        configurable:true,
                        //定义get方法。
                        get:function () {
                            console.log(`获取${value}`)
                            return value;
                        },
                        set:function (newVal) {
                            // body...
                            console.log(`更新${newVal}`)
                            if (value!==newVal) {
                                value=newVal;
                                //如果更新了数据了,就分发给监听者,同时改变数据
                                binding._directives.forEach(function(item){
                                    item.update()
                                })
                            }
                        }
    
                    })
                }
            }
        }
    

    事件分发的场所

    构造对象。
    //写一个指令集Watcher,用来绑定更新函数,实现对Dom元素的更新
    function Watcher(name,el,vm,exp,attr) {
            //指令的名称。
            this.name = name;   
            //指令对应的DOM元素
            this.el = el;
            //执行所属于的myVue实例
            this.vm=vm;
            //指令对应的值,
            this.exp=exp;
            //绑定的属性值。
            this.attr=attr;
                  
                    //这里的update其实是事件响应的方法。最后再来看
            this.update();
            // body...
    }
    
    

    看到我们需要响应的事件。其实是 vm.$data中对应的exp的变化。这些是变化,会当作事件传递到这里。
    而另外一方面,真实监听这些事件的,就是dom得元素。可以通过this.el和attr来的到。

    创建的时机

    因为是通过v-bind这样的指令进行绑定的。所以创建的时机,就是在遍历dom tree的时候。

        //3.创建指令的编译器
        myVue.prototype._compile = function(root) { 
            //root为id为app的Element元素。也就是我们的根元素
            var _this = this
            var nodes = root.children
            //取出每个子节点。如果有子节点。则继续递归。并进行处理
            for (var i = 0; i < nodes.length; i++) {
                var node = nodes[i]
                this._compile(node)
            
    
                //如果有v-on,则进行点击事件的监听
                if (node.hasAttribute('v-on')) {
                    node.onclick=(function () {
                    //这里使用i。就马上调用,就不会出现问题。
                    var attrVal = nodes[i].getAttribute('v-on')
                    //bind是使用data的作用域与method函数的作用域保持一致
                    return _this.$methods[attrVal].bind(_this.$data)
                    })();
                }
    
                //继续,解析其他的
                //v-model只能绑定 input
                if (node.hasAttribute('v-model') && (node.tagName=='INPUT' || node.tagName=='TEXTAREA')) {
    
                    node.addEventListener('input',(function (key) {
                        var attrVal=node.getAttribute('v-model')
                        //如果是v-model就为属性值添加一个watcher
                        _this._binding[attrVal]._directives.push(new Watcher(
                            'input',
                            node,
                            _this,
                            attrVal,
                            'value'
                        ))
    
                        return function () {
                        //将data内的值,给成这里写的值。实现双向绑定
                        _this.$data[attrVal] =nodes[key].value
                        }
                    })(i))
    
                }
    
                //继续v-bind
                if (node.hasAttribute('v-bind')) {
                var attrVal = node.getAttribute('v-bind')
                _this._binding[attrVal]._directives.push(new Watcher(
                    'text',
                    node,
                    _this,
                    attrVal,
                    'innerHTML'
                    ))
                }
            }
        };
    

    可以看到,创建时,将我们的两个要素的组成部分,都传入了。

    事件的触发

    事件的触发,其实就是我们的update方法。来完成。
    属性的变化

        Watcher.prototype.update = function() {
            // body...
            this.el[this.attr]=this.vm.$data[this.exp]
        };
    

    方法的调用
    之前在绑定时,已经添加了我们的监听事件了。

        if (node.hasAttribute('v-on')) {
                    node.onclick=(function () {
                    //这里使用i。就马上调用,就不会出现问题。
                    var attrVal = nodes[i].getAttribute('v-on')
                    //bind是使用data的作用域与method函数的作用域保持一致
                    return _this.$methods[attrVal].bind(_this.$data)
                    })();
                }
    

    最后集成到一起。

    //自定义myVue函数
        function myVue(options){
            //添加一个init属性
            this._init(options)
        }
        //第一个版本。简单的init函数
        myVue.prototype._init = function(options) {
            //为上面使用时传入的结构体。包括el.data.methods
            this.$options=options;//options
            //el是#app,this.$el是id为app的element元素 
            this.$el=document.querySelector(options.el);
            this.$data=options.data;
            this.$methods=options.methods;
    
            //_binding 保持这model和view之间的映射关系,也就算我们之前定义的
            //wathcer.当model改变时,我们会触发其中的指令类更新,保证view也能实时更新
            this._binding ={}
    
            //需要调用自己的observable方法来监听data
            this._obverse(this.$data)
            this._compile(this.$el);
        };
    

    相关文章

      网友评论

          本文标题:Vue 绑定简单分析实现

          本文链接:https://www.haomeiwen.com/subject/bunsjftx.html