美文网首页
实现双向数据绑定

实现双向数据绑定

作者: overflow_hidden | 来源:发表于2017-04-09 11:45 被阅读191次

    MVVM框架主要包含3个部分:modelviewviewmodel

    1. Model:指的是数据部分,对应到前端就是javascript对象
    2. View:指的是视图部分,对应前端就是dom
    3. Viewmodel:就是连接视图与数据的中间件
    1.双向数据绑定的实现方式

    简单的来说,就是框架的控制器层(这里的控制器层是一个泛指,可以理解为控制view行为和联系model层的中间件)和UI展示层(view层)建立一个双向的数据通道。当这两层中的任何一方发生变化时,另一层将会自动作出相应的变化。

    vue-MVVM

    一般来说要实现这种双向数据绑定,在前端我目前了解的有三种形式:

    • 基于脏检查 angular,regular(网易开发)
    • 观察机制
    • 封装属性访问器

    2.基于脏检查

    目前angular,regular的实现都是基于脏检查。当发生某些特定的事情的时候,框架会调用相关的digest方法。内部逻辑就是遍历所有的watcher,对监控的属性做对比。如果值发生了变化,则执行相应的handler

    2.1基于regular详细介绍一下:
    watcher对象看起来是这样的:
    {
      get: function(context){...}  //获得表达式当前求值,此函数在解析时,已经生成
      set: function(){} // 有些表达式可以生成set函数,用于处理赋  值,这个一般用于双向绑定的场景
      once: false // 此监听器是否只生效一次
      last: undefined// 上一次表达式的求值结果
      fn: function(newvalue, oldvalue){} // 即你传入$watch的第二个参数,当值改变时,会调用此函数
      // ...
    }
    

    当系统进入脏检查阶段,遍历所有的$watch绑定的watcher,然后对比watcher.get()watcher.last,如果不同则运行对应的watcher.fn(newvalue, oldvalue)。然后再进入下一个watcher的检查。

    何时进行脏检查?

    在Regular中,digest阶段是由$update
    方法触发的。
    具体源码在regular/src/helper/watcher.js里面

    由于regularjs是基于脏检查,所以当不是由regularjs本身控制的操作(如事件、指令)引起的数据操作,可能需要你手动的去同步data与view的数据. $update方法即帮助将你的data同步到view层.

    //手动同步
    var component = new Regular();
    component.data.name = 'leeluolee'
    // you need call $update to Synchronize data and view 
    component.$update();  
    
    //自动进入
    <div on-click={blog.title='Hello'}>{blog.title}</div>
    
    regular-源码片段,基于select的r-model指令

    总结:但是很显然,脏检查是低效的,它的效率基本上取决于你绑定的观察者数量,在Regular中,你可以通过[@(Expression)

    ](https://regularjs.github.io/reference?syntax-zh#bind-once)元素来控制你的观察者数量。

    3.观察者机制

    使用ES7中的 Object.observe 方法对对象(或者其属性)进行监控观察,一旦其发生变化时,将会执行相应的handler。这是目前监控属性数据变更最完美的一种方法,语言(浏览器)原生支持,没有什么比这个更好了。唯一的遗憾就是目前支持广度还不行,有待全面推广。

    4.封装属性访问器(存取器get,set)

    vue.js和avalon.js实现数据双向绑定的原理就是属性访问器。
    它使用了ES5中的定义标准属性的Object.defineProperty 方法。

    Object.defineProperty(obj, prop, descriptor)
    //obj 待修改的对象
    //prop 待修改的属性名称
    //descriptor 待修改属性的相关描述,要求传入一个对象。
    //@{param} descriptor
    1.configurable ,属性是否可配置。可配置的含义包括:是否可以删除属性( delete ),是否可以修改属性的 writable 、     enumerable 、 configurable 属性。
    2.enumerable ,属性是否可枚举。可枚举的含义包括:是否可以通过 for...in 遍历到,是否可以通过 Object.keys() 方法获取属性名称。
    3.writable ,属性是否可重写。可重写的含义包括:是否可以对属性进行重新赋值。
    4.value ,属性的默认值。
    5.set ,属性的重写器。一旦属性被重新赋值,此方法被自动调用。
    6.get ,属性的读取器。一旦属性被访问读取,此方法被自动调用。
    

    Object.defineProperty使用示例:

    var o = {};
    Object.defineProperty(o, 'name', { value: 'erik'});
    console.log(Object.getOwnPropertyDescriptor(o, 'name'));
       // {
            value: "erik", 
            writable: false, 
            enumerable:false,
            configurable: false
         }
    
    Object.defineProperty(o, 'age', {
        value: 23,
        configurable: true,
        writable: true
    });
    console.log(o.age); // 23
    o.age = 18;
    console.log(o.age); // 18. 因为age属性是可重写的
    console.log(Object.keys(o)); // []. name和age属性都不是可枚举的
    Object.defineProperty(o, 'sex', {
        value: '女',
        writable: false
    });
    o.sex = '男'; // 这里的赋值其实是不起作用的
    console.log(o.sex); // '女';
    delete o.sex; // false, 属性删除的动作也是无效的
    

    注意:

    • Object.defineProperty() 方法设置属性时,属性不能同时声明访问器属性( set 和 get )和 writable 或者 value 属性。 意思就是,某个属性设置了 writable 或者 value 属性,那么这个属性就不能声明 get 和 set 了,反之亦然。
    • 因为 Object.defineProperty() 在声明一个属性时,不允许同一个属性出现两种以上存取访问控制。
    4.1基于vue使用封装属性访问器

    首先,vuejs在实例化的过程中,会对遍历传给实例化对象选项中的data 选项,遍历其所有属性并使用 Object.defineProperty 把这些属性全部转为 getter/setter。

    同时每一个实例对象都有一个watcher实例对象,他会在模板编译的过程中,用getter去访问data的属性,watcher此时就会把用到的data属性记为依赖,这样就建立了视图与数据之间的联系。当之后我们渲染视图的数据依赖发生改变(即数据的setter被调用)的时候,watcher会对比前后两个的数值是否发生变化,然后确定是否通知视图进行重新渲染。这样就实现了所谓的双向数据绑定。

    defineProperty 效果

    相关文章

      网友评论

          本文标题:实现双向数据绑定

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