前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue 绑定简单分析实现

Vue 绑定简单分析实现

作者头像
deep_sadness
发布2018-08-30 10:54:22
5360
发布2018-08-30 10:54:22
举报
文章被收录于专栏:Flutter入门Flutter入门

双向绑定示意图.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);
    };
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.05.25 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分析
    • 数据监听的基本要素
      • 1. 事件的触发
        • 2. 事件的分发场所
          • 3. 事件的响应
          • 代码逻辑
            • 事件的触发
              • 事件分发的场所
                • 构造对象。
                • 创建的时机
              • 事件的触发
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档