专栏首页Flutter入门Vue 绑定简单分析实现

Vue 绑定简单分析实现

双向绑定示意图.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);
    };

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • WAV文件格式解析及处理

    RIFF全称为资源互换文件格式(Resources Interchange File Format),是Windows下大部分多媒体文件遵循的一种文件结构。RI...

    deep_sadness
  • Kotlin中的延迟属性(lazy properties)

    lazy() 是接受一个lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托。也就是说: 第一次调用get() 会执行...

    deep_sadness
  • Android OpenGL ES(五)-结合相机

    上文中我们已经实现了在纹理上添加滤镜的效果。这编文章就是将OpenGl和相机结合到一起。

    deep_sadness
  • 面试题:你能写一个Vue的双向数据绑定吗?

    Vue的双向数据绑定的原理相信大家也都十分了解了,主要是通过 Object对象的defineProperty属性,重写data的set和get函数来实现的,这里...

    :::::::
  • 基础篇章:关于 React Native 之 Modal 组件的讲解

    (友情提示:RN学习,从最基础的开始,大家不要嫌弃太基础,会的同学请自行略过,希望不要耽误已经会的同学的宝贵时间) Modal是模态视图,它的作用是可以用来覆盖...

    非著名程序员
  • 浅谈前端响应式设计(一)

    现实世界有很多是以响应式的方式运作的,例如我们会在收到他人的提问,然后做出响应,给出相应的回答。在开发过程中我也应用了大量的响应式设计,积累了一些经验,希望能抛...

    有赞coder
  • vue项目使用 富文本 封装

    我又来了,今天给大家分享一个富文本框的封装,写后台管理也离不开富文本框,我就做了封装,供大家参考,

    前端小白@阿强
  • 【干货】Cocos Creator制作一个微信小游戏(下)

    | 导语 微信小游戏都火成这样了,为什么不尝试一下? 我们的目标是使用Cocos Creator从零开始制作一个小游戏,并放到微信上玩。 上文链接:Cocos...

    腾讯NEXT学位
  • 干货 | 用uni-app制作迷你PS小程序

    ? ? 该文章主要讲解最近基于 uni-app 框架编写的集图文拖拽等多方位编辑、油墨电子签名、开放式海报于一体的小程序的制作思路和实现代码。 1、完整源码链...

    腾讯NEXT学位

扫码关注云+社区

领取腾讯云代金券