angularjs脏检查

angularjs实现了双向绑定,与vue的defineProperty不同,它的原理在于它的脏检查机制,以下做了一些总结;

angular.js介绍

  • AngularJs是mvvm框架,它的组件是vm组件,scope是vm组件的数据集合
  • AngularJs通过directive来声明vm的行为,它实现为一个watcher,监听scope的属性的变化,把最新的属性更新UI
  • AngularJs的双向绑定:如:一个是将$scope属性值绑定到HTML结构中,当$scope属性值发生变化的时候界面也发生变化;另一个是,当用户在界面上进行操作,例如点击、输入、选择时,自动触发$scope属性的变化(界面也可能跟着变)
  • 监听scope的属性变更:脏检查(dirty check )

脏检查

  • angular根本不监听数据的变动,而是在恰当的时机($watch)从$rootScope开始遍历所有$scope,
  • 检查它们上面的属性值是否有变化,如果有变化,就用一个变量dirty记录为true,再次进行遍历($digest),
  • 如此往复,直到某一个遍历完成时,这些$scope的属性值都没有变化时,结束遍历。
  • 由于使用了一个dirty变量作为记录,因此被称为脏检查机制。

简而言之: 当作用域创建时,angular会去解析模板,将绑定值和事件调用找出来并用$watch绑定;

$scope.$watch(string|function, listener, objectEquality, prettyPrintExpression)
// string: 验证值或者function执行后return的string
// listener: 如果验证值不同,则执行该监听函数
// objectEquality:执行深检查
  • 完成绑定后,就会自动检测这些属性的变化,执行$watch, 那么对应的信息被绑定到angular内部的一个$$watchers中,
  • 它是一个队列(数组),而当$digest被触发时,angular就会去遍历这个数组,
  • 并且用一个dirty变量记录$$watchers里面记录的那些$scope属性是否有变化

接下来的流程:

  1. 判断dirty是否为true,如果为false,则不进行$digest递归。(dirty默认为true)
  2. 遍历$$watchers,取出对应的属性值的老值和新值根据objectEquality进行新老值的对比。
  3. 如果两个值不同,则继续往下执行。如果两个值相同,则设置dirty为false。
  4. 检查完所有的watcher之后,如果dirty还为true, 设置dirty为true用新值代替老值;
  5. 这样,在下一轮递归的时候,老值就是这一轮的新值再次调用$digest(简单说就是执行两次递归遍历检查新旧值变化)
  6. 将变化后的$scope重新渲染到界面

$apply触发$digest

  • 一般不调用$digest, 调用$apply,它内部会触发$digest递归遍历
  • angular的内部指令封装了$apply,比如ng-click,所以一般不用手动调用apply

手动调用apply

  • 部分时候需要手动触发
function($timeout) {
  // 当我们通过on('click')的方式触发某些更新的时候,可以这样做
  $timeout(() => { // 内置语法糖 $http, $timeout已经包含了apply
    $scope.name = 'lily'
  })
  // 也可以这样做
  $element.on('click', () => {
    $scope.name = 'david'
    $scope.$apply()
  })
}
注意:在递归过程中,绝对不能手动调用$apply,比如在ng-click的函数中,比如在$watch的回调函数中。

最后,实现一个简易脏检查机制

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>angularjs脏检查实现</title>
</head>
<style type="text/css">
  button {
    height: 60px;
    width: 100px;
  }

  p {
    margin-left: 20px;
  }
</style>

<body>
  <div>
    <button type="button" ng-click="increase">增加</button>
    <button type="button" ng-click="decrease">减少</button>
    数量:<span ng-bind="data">0</span>
  </div>
  <br>
</body>
<script>
  window.onload = function () {
    /**
     * 声明构造函数
     */
    function Scope() {
      this.$$watchList = []; // angular内部会声明一个数组(包含监听的对象),在digest执行时去遍历
    }

    /**
     * 属性赋值给$scope
     * 类似angular解析controller的模板,把模板中的属性解析出来,属性赋值给$scope
     */
    Scope.prototype.getNewValue = function () {
      return $scope[this.name];
    }

    /**
     * 实现监听
     */
    Scope.prototype.$watch = function (name, listener) {
      let watch = {
        name: name,
        getNewValue: this.getNewValue,
        listener: listener || function () { }
      };
      // 当作用域创建时,angular会去解析模板,$watch用来绑定监听值和监听函数
      this.$$watchList.push(watch);
    }

    /**
    * 检查dirty,循环更新scope上的最新值
    */
    Scope.prototype.$digest = function () {
      console.log('$digest');
      let dirty = true; // 默认dirty变量为true
      let checkTimes = 0;
      while (dirty) {
        dirty = this.$valScope();
        checkTimes++;
        if (checkTimes > 10 && dirty) {
          throw new Error("循环过多");
        }
      }
    }

    /**
     * 验证值是否有变化
     */
    Scope.prototype.$valScope = function () {
      let dirty;
      let list = this.$$watchList;
      for (let i = 0; i < list.length; i++) {
        let watch = list[i];
        let newValue = watch.getNewValue();
        let oldValue = watch.last || undefined;
        if (newValue !== oldValue) {
          watch.listener(newValue, oldValue);
          dirty = true; // 如果新旧值不同,则继续遍历
        } else {
          dirty = false;
        }
        watch.last = newValue;
      }
      return dirty;
    }

    /**
     * 刷新scope
     */
    Scope.prototype.$apply = function (params) {
      let list = document.querySelectorAll('[ng-bind]');
      console.log('list', list)
      for (let i = 0, l = list.length; i < l; i++) {
        let bindData = list[i].getAttribute('ng-bind');
        console.log('bindData', bindData)
        console.log('list[i]', list[i])
        list[i].innerHTML = $scope[bindData];
      }
    }

    let $scope = new Scope(); // 实例化,声明$scope对象集合
    $scope.data = 0;
    $scope.increase = function () {
      this.data++;
    };
    $scope.decrease = function () {
      this.data--;
    };

    $scope.$watch('data', function(newValue, oldValue) { // 监听
        console.log("new: " + newValue + "=========" + "old: " + oldValue);
    });

    // 手动为button按钮添加onclick事件,并为通过闭包为其绑定了全局scope对象,绑定apply方法
    // 类似angular内部实现
    function startBind() {
      let list = document.querySelectorAll('[ng-click]');
      for (let i = 0, l = list.length; i < l; i++) {
        list[i].onclick = (function (index) {
          return function () {
            let func = this.getAttribute('ng-click');
            $scope[func]($scope);
            $scope.$digest();
            $scope.$apply()
          }
        })(i)
      }
    }

    // 初始化
    startBind();
  }

</script>

</html>

原文地址:https://www.cnblogs.com/mapleChain/p/12225754.html

时间: 2024-10-28 21:08:24

angularjs脏检查的相关文章

angularjs脏检查机制

很久没有写点东西了,从今天起,在博客园对自己过往的工作,学习和生活做一些梳理,总结和温故.今天窗外的阳光不那么任性,天空白云点点,蝉鸣依旧嘹亮,安静地坐在阳台上,吹着风,敲击着键盘.于我而言,这就是幸福生活. 转入正题,对angularjs脏检查机制做了个回顾和总结如下: 参考文章:  http://teropa.info/build-your-own-angular/ http://www.cnblogs.com/likeFlyingFish/p/6183630.html http://www

脏检查

首先解释一下什么是脏检查:在angular中,数据绑定到html中后,这个数据改变了,需要检查刷新,这就触发了脏检查. Angular中的数据是双向绑定的,那么我们怎么知道数据是否是变化了呢? 1.首先能通过固定的接口才能改变变量的值,比如说只能通过set()设置变量的值,set被调用的时候比较一下就知道了.这种方法的缺点是写法比较繁琐. 2.脏检查,将原对象复制一份,在某个时间,比较现在对象与原对象的值,如果不一样就表明发生了变化,这个策略要保留两份变量,而且要遍历对象,比较每个属性,这样会有

$apply方法(触发脏检查机制)

$swatch监听方法 <!DOCTYPE html> <html><head lang="en"> <meta charset="UTF-8"> <title></title> <script src="../angular-1.5.5/angular.min.js"></script> </head> <body ng-app=&

Session脏检查及清理缓存机制

今天看hibernate书,看到"理解Session的缓存"这一张脏检查及清理缓存的机制后,突然明白程序中session并非一定要执行update()方法.没有执行update()方法,对象仍会与数据库进行数据交互. Session脏检查及清理缓存机制: Session缓存中对象的属性每次改变时,Session不会立即清理缓存及执行Update SQL,而是会在特定时间清理缓存(执行相应的sql),提交事务. 清理缓存时,会相应的改变对象的状态. Java对象在内存中有四个状态:临时状

脏检查 和 缓存清理机制

Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照.当Session清理缓存时,会先进行脏检查,即比较Customer对象的当前属性与它的快照,来判断Customer对象的属性是否发生了变化,如果发生了变化,就称这个对象是“脏对象”,Session会根据脏对象的最新属性来执行相关的SQL语句,从而同步更新数据库. 缓存清理机制 当Session缓存中对象的属性每次发生了变化,Sessi

8.29 脏检查笔记

个人理解图 脏检查 Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照.当Session清理缓存时,会先进行脏检查,即比较Customer对象的当前属性与它的快照,来判断Customer对象的属性是否发生了变化,如果发生了变化,就称这个对象是“脏对象”,Session会根据脏对象的最新属性来执行相关的SQL语句,从而同步更新数据库. 缓存清理机制 当Session缓存中对象的属性每次发

脏检查及刷新缓存机制

当事务提交时,Hibernate会对Session中持久状态的对象进行检测,判断对象的数据是否发生了改变,这种判断称为脏检查 Session session; Transaction tx; @After public void afterTest(){ tx.commit(); HibernateUtil.closeSession(); } @Before public void initDate(){ session=HibernateUtil.getSession(); tx = sess

关于Hibernate脏检查与快照

脏检查 Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会 为Customer对象的值类型的属性复制一份快照.当Session清理缓存时,会先进行脏检查,即比较Customer对象的当前属性与它的快照,来 判断Customer对象的属性是否发生了变化,如果发生了变化,就称这个对象是“脏对象”,Session会根据脏对象的最新属性来执行相关的SQL语 句,从而同步更新数据库. 缓存清理机制 当Session缓存中对象的属性每次发生了变

Hibernate 中三种状态及脏检查

java对象的三种状态 持久态: Student stu=new Student() Session.save(stu); Session以及数据库都有 游离态: stu.setId(1); Session.close(); Session没有 数据库中有 瞬时态: Student stu=new Student() 体现:在Session中以及DB都没有 三种状态图: 使用new关键字构建对象,该对象的状态是瞬时状态. 1 瞬时状态转为持久状态 使用Session对象的save()或saveO