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);
    };
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容