资讯专栏INFORMATION COLUMN

VUE的数据双向绑定

魏明 / 926人阅读

摘要:写在前面的东西自从在上开源以来就受到各方的极大关注,并在短暂的时间里立即火了起来,现在已成为最流行的前端框架之一我也使用有一段时间了,对的双向绑定有一定的理解,在这和大家分享我的愚见,有错误的地方望大家给予指正。

写在前面的东西

Vue.js自从在github上开源以来就受到各方的极大关注,并在短暂的时间里立即火了起来,现在已成为最流行的前端框架之一;我也使用vue有一段时间了,对vue的双向绑定有一定的理解,在这和大家分享我的愚见,有错误的地方望大家给予指正。

1、概述
 让我们先来看一下官网的这张数据绑定的说明图:

原理图告诉我们,a对象下面的b属性定义了getter、setter对属性进行劫持,当属性值改变是就会notify通知watch对象,而watch对象则会notify到view上对应的位置进行更新(这个地方还没讲清下面再讲),然后我们就看到了视图的更新了,反过来当在视图(如input)输入数据时,也会触发订阅者watch,更新最新的数据到data里面(图中的a.b),这样model数据就能实时响应view上的数据变化了,这样一个过程就是数据的双向绑定了。

看到这里就会第一个疑问:那么setter、getter是怎样实现的劫持的呢?答案就是vue运用了es5中Object.defineProperty()这个方法,所以要想理解双向绑定就得先知道Object.defineProperty是怎么一回事了;

2.Object.defineProperty

它是es5一个方法,可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象,对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个拥有可写或不可写值的属性。存取描述符是由一对 getter-setter 函数功能来描述的属性。描述符必须是两种形式之一;不能同时是两者
属性描述符包括:configurable(可配置性相当于属性的总开关,只有为true时才能设置,而且不可逆)、Writable(是否可写,为false时将不能够修改属性的值)、Enumerable(是否可枚举,为false时for..in以及Object.keys()将不能枚举出该属性)、get(一个给属性提供 getter 的方法)、set(一个给属性提供 setter 的方法)

var o = {name:"vue"};
Object.defineProperty(o, "age",{ value : 3,
                               writable : true,//可以修改属性a的值
                               enumerable : true,//能够在for..in或者Object.keys()中枚举
                               configurable : true//可以配置
                               });

Object.keys(o)//["name","age"]
o.age = 4;
console.log(o.age) //4

var bValue;
Object.defineProperty(o, "b", {
                               get : function(){ 
                                         return bValue; 
                                     },
                               set : function(newValue){ 
                                       console.log("haha..")
                                       bValue = newValue; 
                                     },
                               enumerable : true,//默认值是false 及不能被枚举
                               configurable : true//默认也是false
                               });
 o.b = "something";
//haha..

上面分别给出了对象属性描述符的数据描述符和存取描述的例子,注意一点是这两种不能同时拥有,也就是valuewritable不能和getset同时具备。在这里只是很粗浅的说了一下Object.defineProperty这个方法,要了解更多可以点击这里

3.实现observer

我们在上面一部分讲到了es5的Object.defineProperty()这个方法,vue正式通过它来实现对一个对象属性的劫持的,在创建实例的时候vue会对option中的data对象进行一次数据格式化或者说初始化,给每个data的属性都设置上get/set进行对象劫持,代码如下:

function Observer(data){
    this.data = data;
    if(Array.isArray(data)){
        protoAugment(data,arrayMethods); //arrayMethods实现对Array.prototype原型方法的拷贝;
        this.observeArray(data);
    }else{
        this.walk(data);
    }
    
}

Observer.prototype = {
    walk:function walk(data){
        var _this = this;
        Object.keys(data).forEach(function(key){
            _this.convert(key,data[key]);
        })
    },
    convert:function convert(key,val){
        this.defineReactive(this.data,key,val);
    },
    defineReactive:function defineReactive(data,key,val){
        var ochildOb = observer(val);
        var _this = this;
        Object.defineProperty(data,key,{
            configurable:false,
            enumerable:true,
            get:function(){
                console.log(`i get the ${key}-->${val}`)
                return val;
            },
            set:function(newVal){
                if(newVal == val)return;
                console.log(`haha.. ${key} changed oldVal-->${val} newVal-->${newVal}`);
                val = newVal;
                observer(newVal);//在这里对新设置的属性再一次进行get/set     
            }            
        })
    },
    observeArray:function observeArray(items){
        for (var i = 0, l = items.length; i < l; i++) {
            observer(items[i]);
         }
    }
}
function observer(data){
    if(!data || typeof data !=="object")return;
    return new Observer(data);
}
//让我们来试一下
var obj = {name:"jasonCloud"};
var ob = observer(obj);
obj.name = "wu";
//haha.. name changed oldVal-->jasonCloud newVal-->wu
obj.name;
//i get the name-->wu

到这一步我们只实现了对属性的set/get监听,但并没实现变化后notify,那该怎样去实现呢?在VUE里面使用了订阅器Dep,让其维持一个订阅数组,但有订阅者时就通知相应的订阅者notify。

let _id = 0;
/*
  Dep构造器用于维持$watcher检测队列;
*/
function Dep(){
    this.id = _id++;
    this.subs = [];
}

Dep.prototype = {
    constructor:Dep,
    addSub:function(sub){
        this.subs.push(sub);
    },
    notify:function(){
        this.subs.forEach(function(sub){
            if(typeof sub.update == "function")
            sub.update();
        })
    },
    removeSub:function(sub){
        var index = this.subs.indexOf(sub);
        if(index >-1)
        this.subs.splice(index,1);
    },
    depend:function(){
        Dep.target.addDep(this);
    }
}

Dep.target = null; //定义Dep的一个属性,当watcher时Dep.targert=watcher实例对象

在这里构造器Dep,维持内部一个数组subs,当有订阅时就addSub进去,通知订阅者更新时就会调用notify方法通知到订阅者;我们现在合并一下这两段代码

function Observer(data){
    //省略的代码..
    this.dep = new Dep();
    //省略的代码..
    
}

Observer.prototype = {

    //省略的代码..
    
    defineReactive:function defineReactive(data,key,val){

        //省略的代码..

        var dep = new Dep();

        Object.defineProperty(data,key,{
            configurable:false,
            enumerable:true,
            get:function(){
                if(Dep.target){
                    dep.depend();
                    //省略的代码..
                }
                return val;
            },
            set:function(newVal){
                //省略的代码..
                dep.notify();         
            }            
        })
    },
    observeArray:function observeArray(items){
        for (var i = 0, l = items.length; i < l; i++) {
            observer(items[i]);
         }
    }
}

function observer(data){
    if(!data || typeof data !=="object")return;
    return new Observer(data);
}

上面代码中有一个protoAugment方法,在vue中是实现对数组一些方法的重写,但他并不是直接在Array.prototype.[xxx]直接进行重写这样会影响到所有的数组中的方法,显然是不明智的,vue很巧妙的进行了处理,使其并不会影响到所有的Array上的方法,代码可以点击这里

到这里我们实现了数据的劫持,并定义了一个订阅器来存放订阅者,那么谁是订阅者呢?那就是Watcher,下面让我们看看怎样实现watcher

4.实现一个Watcher

watcher是实现view视图指令及数据和model层数据联系的管道,当在执行编译时候,他会把对应的属性创建一个Watcher对象让他和数据层model建立起联系。但数据发生变化是会触发update方法更新到视图上view中,反过来亦然。

function Watcher(vm,expOrFn,cb){
    this.vm = vm;
    this.cb = cb;
    this.expOrFn = expOrFn;
    this.depIds = {};
    var value = this.get(),valuetemp;
    if(typeof value === "object" && value !== null){
        if(Array.isArray(value)){
            valuetemp = [];
            for(var i = 0,len = value.length;i

到现在还差一步就是将我们在容器中写的指令和{{}}让他和我们的model建立起连续并转化成,我们平时熟悉的html文档,这个过程也就是编译;编译简单的实现就是将我们定义的容器里面所有的子节点都获取到,然后通过对应的规则进行转换编译,为了提高性能,先创建一个文档碎片createDocumentFragment(),然后操作都在碎片中进行,等操作成功后一次性appendChild进去;

function Compile(el,vm){
    this.$vm = vm;
    this.$el = this.isElementNode(el) ? el : document.querySelector(el);
    if(this.$el){
        this.$fragment = this.nodeToFragment(this.$el);
        this.init();
        this.$el.appendChild(this.$fragment);
        this.$vm.$option["mount"] && this.$vm.$option["mount"].call(this.$vm);
    }
}
5.实现一个简易版的vue

到目前为止我们可以实现一个简单的数据双向绑定了,接下来要做的就是对这一套流程进行整合了,不多说上码

function Wue(option){
    this.$option = option;
    var data = this._data = this.$option.data;
    var _this = this;

    //数据代理实现数据从vm.xx == vm.$data.xx;
    Object.keys(data).forEach(function(val){
        _this._proxy(val)
    });

    observer(data)
    this.$compile = new Compile(this.$option.el , this);
}

Wue.prototype = {
    $watch:function(expOrFn,cb){
        return new Watcher(this,expOrFn,cb);
    },
    _proxy:function(key){
        var _this = this;
        Object.defineProperty(_this,key,{
            configurable: false,
            enumerable: true,
            get:function(){
                return _this._data[key];
            },
            set:function(newVal){
                _this._data[key] = newVal;
            }
        })
    }
}

在这里定义了一个Wue构造函数,当实例化的时候他会对option的data属性进行格式化(劫持),然后再进行编译,让数据和视图建立起联系;在这里用_proxy进行数据代理是为了当访问数据时可以直接vm.xx而不需要vm._data.xx;

源码放在这里

后话

在这里只是很初步的实现了一些vue的功能,而且还很残缺,比如对象的深层绑定,以及计算属性都还没有加入,作为后续部分吧,最后得膜拜一下尤神,太牛叉了!

参考资料:
1.https://segmentfault.com/a/11...
2.https://segmentfault.com/a/11...
3.https://github.com/youngwind/...

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81924.html

相关文章

  • Vue 双向数据绑定原理分析

    摘要:关于双向数据绑定当我们在前端开发中采用的模式时,,指的是模型,也就是数据,,指的是视图,也就是页面展现的部分。参考沉思录一数据绑定双向数据绑定实现数据与视图的绑定与同步,最终体现在对数据的读写处理过程中,也就是定义的数据函数中。 关于双向数据绑定 当我们在前端开发中采用MV*的模式时,M - model,指的是模型,也就是数据,V - view,指的是视图,也就是页面展现的部分。通常,...

    nanfeiyan 评论0 收藏0
  • 基于Object.defineProperty实现双向数据绑定

    摘要:双向数据绑定可算是前端领域经久不衰的热词,不管是前端开发还是面试都会有所涉及。因此,中的挺身而出,拯救了中对数组数据处理的不足。有兴趣的朋友请期待笔者的下一篇博客,讨论下用实现双向数据绑定。 双向数据绑定可算是前端领域经久不衰的热词,不管是前端开发还是面试都会有所涉及。而且不同的框架也想尽一切办法去实现这一特性,比如:Knockout / Backbone --- 发布-订阅模式Ang...

    fredshare 评论0 收藏0
  • vue.js源码 - 剖析observer,dep,watch三者关系 如何具体实现数据双向绑定

    摘要:双向数据绑定的核心和基础是其内部真正参与数据双向绑定流程的主要有和基于和发布者订阅者模式,最终实现数据的双向绑定。在这里把双向数据绑定分为两个流程收集依赖流程依赖收集会经过以上流程,最终数组中存放列表,数组中存放列表。 Vue双向数据绑定的核心和基础api是Object.defineProperty,其内部真正参与数据双向绑定流程的主要有Obderver、Dep和Watcher,基于d...

    mo0n1andin 评论0 收藏0
  • 前端MVVM模式及其在Vue和React中体现

    摘要:在模式中一般把层算在层中,只有在理想的双向绑定模式下,才会完全的消失。层将通过特定的展示出来,并在控件上绑定视图交互事件,一般由框架自动生成在浏览器中。三大框架的异同三大框架都是数据驱动型的框架及是双向数据绑定是单向数据绑定。 MVVM相关概念 1) MVVM典型特点是有四个概念:Model、View、ViewModel、绑定器。MVVM可以是单向绑定也可以是双向绑定甚至是不绑...

    沈建明 评论0 收藏0
  • 160行代码仿Vue实现极简双向绑定[详细注释]

    摘要:兼容性更详细的可以看一下实现思路系列的双向绑定,关键步骤实现数据监听器,用重写数据的,值更新就在中通知订阅者更新数据。 showImg(https://segmentfault.com/img/remote/1460000015375220?w=640&h=426); 前言 现在的前端面试不管你用的什么框架,总会问你这个框架的双向绑定机制,有的甚至要求你现场实现一个双向绑定出来,那对于...

    endiat 评论0 收藏0
  • Vue双向绑定原理,教你一步一步实现双向绑定

    摘要:储存订阅器因为属性被监听,这一步会执行监听器里的方法这一步我们把也给弄了出来,到这一步我们已经实现了一个简单的双向绑定了,我们可以尝试把两者结合起来看下效果。总结本文主要是对双向绑定原理的学习与实现。 当今前端天下以 Angular、React、vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋。 所以我们要时刻保持好奇心,拥抱变化,...

    Labradors 评论0 收藏0

发表评论

0条评论

魏明

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<