• Angular2的双向数据绑定


    什么是双向绑定

    如图:

     
    双向绑定.jpg

    双向绑定机制维护了页面(View)与数据(Data)的一致性。如今,MVVM已经是前段流行框架必不可少的一部分。

    Angular2中的双向绑定

    双向绑定,也是Angular2的核心概念之一,Angular2的双向绑定是这样的:

    • data=>view:数据绑定,模板语法是 []
    • view=>data:事件绑定,模板语法是 ()
    • Angular其实并没有一个双向绑定的实现,他的双向绑定就是数据绑定+事件绑定,模板语法是 [()] 。

    Angular2官方给的例子:

    <!--value是数据绑定,input是事件绑定-->
    <input [value]="currentHero.name"   
           (input)="currentHero.name=$event.target.value"
           >
    <!--等价-->
    <input [(ngModel)]="currentHero.name">
    

    上面是input空间的双向绑定语法,很清楚的说明了双向绑定与两个单向绑定的关系。这里没有使用ngModule语法,ngModule语法内部实现与这个差不多。

    事件绑定

    1. 用户操作出发DOM事件通知
    2. Angular监听到了通知,然后执行模板语法,上面的例子就是将input控件的输入值赋给了currentHero.name

    数据绑定

    由于js语言并没有属性变化通知的机制,所以angular也不知道谁发生了变化,在什么时候变了。Angular的变化机制是:

     
    image.png

    上面的例子中input的数据绑定过程如下:

    1. 代码修改了currentHero.name的值。
    2. 触发整个组件树的变化检查。
    3. input显示了修改后的值。
    数据何时变化

    主要入下集中情况可能改变数据:

    • 用户输入操作,比如点击,提交等。
    • 请求服务端数据。
    • 定时事件,比如setTimeoutsetInterval

    这几点有个共同点,就是他们都是异步的。也就是说,所有的异步操作是可能导致数据变化的根源因素。

    如何通知变化

    在Angularjs中是由代码$scope.$apply()或者$scope.$digest触发,而Angular2接入了ZoneJS,由它监听了Angular所有的异步事件。ZoneJS重写了所有的异步API(所谓的猴子补丁,MonkeyPath)。ZoneJS会通知Angular可能有数据发生变化,需要检测更新。

    变化检测原理 -- 脏检查

    所谓脏检查就是存储所有变量的值,每当可能有变量发生变化需要检查时,就将所有变量的旧值跟新值进行比较,不相等就说明检测到变化,需要更新对应的视图。

    AngularJS与Angular2变化检测的区别

    Angularjs的变化检测机制也是脏检查,而Angular2的变化检测性能比Angularjs提升了很多。

    Angular2

    Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树。Angular的变化检测可以分组件进行,每个组件都有对应的变化检测器ChangeDetector。可想而知,这些变化检测器也会构成一棵树。

    另外,Angular的数据流是自顶而下的,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测,尽管检查了负组件之后,自组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError(关于这个问题的答案,可以在参考资料中找到)。而在生产环境中,脏检查只会执行一次。

    Angularjs

    相比之下,Angularjs采用的是双向数据流,错综复杂的数据流使得他不得不多次检查,使得数据最终趋向稳定。理论上,数据永远不可能稳定,Angularjs的策略是,脏检查超过10次就认定程序有问题。

     
    angular2-change-detection-moscowjs-31-9-638.jpg

    变化检测优化

    优化策略

    有2个思路:

    1. OnPush策略:我知道我没变,别查我。
    2. 手动控制刷新:我变了,只查我。

    变化检测策略 OnPush

    Angular还让开发者拥有制定变化策略的能力。

    export enum ChangeDetectionStrategy { 
      OnPush, // 表示变化检测对象的状态为`CheckOnce` 
      Default, // 表示变化检测对象的状态为`CheckAlways`
    }
    

    ChangeDetectionStrategy可以看到,Angular有两种变化检测策略。Default是Angular默认的变化检测策略,也就是脏检查(只要有值发生变化,就全部检查)。开发者可以根据场景来设置更加高效的变化检测方式:OnPushOnPush策略,就是只有当输入数据的引用发生变化或者有事件触发时,组件进行变化检测。

    @Component({
      template: `
        <h2>{{vData.name}}</h2>
        <span>{{vData.email}}</span>
      `,
      // 设置该组件的变化检测策略为onPush
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    class VCardCmp {
      @Input() vData;
    }
    

    比如上面这个例子,当vData的属性值发生变化的时候,这个组件不会发生变化检测,只有当vData重新赋值的时候才会。一般,只接受输入的木偶子组件(dumb components)比较适合采用onPush策略。

    那什么时候只要对象的属性值发生变化,整个对象的引用就变了呢?不可变对象(Immutable Object)。当组件中的输入对象是不变量时,可采用onPush变化检测策略,减少变化检测的频率。换个角度来说,为了更加智能地执行变化检测,可以在只接受输入的子组件中采用onPush策略。

    手动控制变化检测

    Angular不仅可以让开发者设置变化检测策略,还可以让开发者获取变化检测对象引用ChangeDetectorRef,手动去操作变化检测。变化检测对象引用给开发者提供的方法有以下几种:

    • markForCheck():将检查组件的所有父组件所有子组件,即使设置了变化检测策略为onPush
    • detach():将变化检测对象脱离检测对象树,不再进行变化检查;结合detectChanges可实现局部变化检测。(采用onPush策略之后的组件detach()无效)
    • detectChanges():将检测该组件及其子组件,结合detach可实现局部检测。
    • checkNoChanges(): 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成。
    • reattach():将脱离的变化检测对象重新链接到变化检测树上。

    那么,如果是Observable的话,它会订阅所有的变量变化,只要在订阅回调函数中手动触发变化检测即可实现最小成本的检测(仍采用onPush变化检测策略)。举个例子:

    @Component({
      template: '{{counter}}',
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    class CartBadgeCmp {
    
      @Input() addItemStream:Observable<any>;
      counter = 0;
    
      constructor(private cd: ChangeDetectorRef) {}
    
      ngOnInit() {
        this.addItemStream.subscribe(() => {
          this.counter++;        // 数据模型发生变化
          this.cd.markForCheck(); // 手动触发检测
        })
      }
    }
    

    另外,当数据模型变化太过频繁,我们可自定义变化检测的时机。举个例子:

    @Component({
      template: `{{counter}}
      <input type="check" (click)="toggle()">`, 
    })
    class CartBadgeCmp { 
      counter = 0;
      detectEnabled = false;
     
      constructor(private cd: ChangeDetectorRef) {}
    
      ngOnInit() {
        // 每10毫秒增加1
        setInterval(()=>{this.counter++}, 10);
      }
      
      toggle(){
          if( this.detectEnabled ){
              this.cd.reattach();  // 链接上变化检测树
          }
          else{
              this.cd.detach(); // 脱离变化检测树
          }
      }
    }
    

    总结

    Angular与Angularjs都采用变化检测机制,前者优于后者主要体现在:

    • 单项数据流动
    • 以组件为单位维度独立进行检测
    • 生产环境只进行一次检查
    • 可自定义的变化检测策略:DefaultonPush
    • 可自定义的变化检测操作:markForcheck()detectChanges()detach()reattach()checkNoChanges()
    • 代码实现上的优化,据说采用了VM friendly的代码。




    链接:https://www.jianshu.com/p/cee44e8831c9

  • 相关阅读:
    for...else...
    js 代码运行中插入交互
    八皇后问题(python)
    多层for循环转递归/迭代
    迭代器/可迭代对象/生成器
    js,python,go 协程对比
    Promise精简总结
    PyQt常用组件用法总结
    PyQt5 接入 web 登录页
    C++求整型数组的和
  • 原文地址:https://www.cnblogs.com/ckAng/p/11057712.html
Copyright © 2020-2023  润新知