发表于: 2017-07-13 22:25:39

1 1091


如何理解ANGULAR的脏检查?

小课堂【武汉第170期】

分享人:庄引

目录

1.背景介绍

2.知识剖析

3.常见问题

4.解决方案

5.编码实战

6.扩展思考

7.参考文献

8.更多讨论

1.背景介绍

关于Angular脏检查,Angular 会定时的进行周期性数据检查, 将前台和后台数据进行比较, 所以非常损耗性能。这是大错而特错的。如果在 前端面试的时候你这么胡说一通, 没有深入了解就信口开河,最后被拒也是理所当然。 首先纠正误区, Angular并不是周期性触发脏检查。 只有当UI事件,ajax请求或者 timeout 延迟事件, 才会触发脏检查。 为什么叫脏检查? 对脏数据的检查就是脏检查,比较UI和后台的数据是否一致

$WATCH 对象

Angular 每一个绑定到UI的数据,就会有一个 $watch 对象。 这个对象包含三个参数

watch = {
   name:'',      //当前的watch 对象 观测的数据名
   getNewValue:function($scope){ //得到新值
   ...
       return newValue;
   },
   listener:function(newValue,oldValue){  // 当数据发生改变时需要执行的操作
   ...
   }
}

每当我们将数据绑定到 UI 上,AAngular 就会向你的 watchList 上插入一个 $watch。 比如:

<span>{{user}}</span>    
<span>{{password}}</span>

这就会插入两个$watch 对象。之后,开始脏检查。只有先理解了Angular的双向数据绑定,才能透彻理解脏检查 。 Angular实现了双向数据绑定。即界面的操作能实时反馈到数据,数据的更改也能在界面呈现。 界面到数据的更改,是由 UI 事件,ajax请求,或者timeout 等回调操作, 而数据到界面的呈现则是由脏检查来做 。

2.知识剖析

双向数据绑定

看下面的例子

    <div ng-app="myApp" ng-controller="myCtrl">

        <span ng-bind="counter"></span>

        <button ng-click="counter=counter+1">increase</button>

    </div>

    var app = angular.module('myApp', []);

    app.controller('myCtrl', function ($scope) {

        $scope.counter = 0;

    });

毫无疑问,我每点击一次button,counter就会+1,因为点击事件,将couter+1,而后触发了脏检查,又将新值2 返回给了界面. 这就是一个简单的双向数据绑定的流程. 但是就只有这么简单吗?

看下面的代码

    'use strict';

    var app = angular.module('app', []);

    app.directive('myclick', function () {

        return function (scope, element, attr) {

            element.on('click', function () {

                scope.data++;

                console.log(scope.data)

            })

        }

    })

    app.controller('appController', function ($scope) {

        $scope.data = 0;

    });

    <div ng-app="app">

        <div ng-controller="appController">

            <span>{{data}}</span>

            <button myclick="">increase</button>

        </div>

    </div>

点击后,毫无反应 ?

当在 console.log(scope.data) 之后添加 scope.$digest(); ? 也可以使用$apply () 也可以(后面会讲解 )

为什们呢? 假设没有AngularJS,要让我们自己实现这个类似的功能,该怎么做呢?

<button ng-click="increase">increase</button>

<button ng-click="decrease">decrease</button>

<span ng-bind="data"></span>

<script src="app.js"></script>

    window.onload = function() {

        'use strict';

        var scope = {

            increase: function() {

                this.data++;

            },

            decrease: function decrease() {

                this.data--;

            },

            data: 0

        }

        function bind() {

            var list = document.querySelectorAll('[ng-click]');

            for (var i = 0, l = list.length; i < l; i++) {

                list[i].onclick = (function(index) {

                    return function() {

                        var func = this.getAttribute('ng-click');

                        scope[func](scope);

                        apply();

                    }

                })(i);

            }

        }

        // apply

        function apply() {

            var list = document.querySelectorAll('[ng-bind]');

            for (var i = 0, l = list.length; i < l; i++) {

                var bindData = list[i].getAttribute('ng-bind');

                list[i].innerHTML = scope[bindData];

            }

        }

        bind();

        apply();

    }

可以看到我们没有直接使用DOM的onclick方法,而是搞了一个ng-click, 然后在bind里面把这个ng-click对应的函数拿出来,绑定到onclick的事件处理函数中。 为什么要这样呢?因为数据虽然变更了,但是还没有往界面上填充,我们需要在此做一些附加操作。 另外,由于双向绑定机制,在DOM操作中,虽然更新了数据的值,但是并没有立即反映到界面上, 而是通过 apply() 来反映到界面上,从而完成职责的分离,可以认为是单一职责模式了。 在真正的Angular中,ng-click 封装了click,然后调用一次 apply 函数,把数据呈现到界面上 在Angular 的apply函数中,这里先进行脏检测, 看 oldValue 和 newVlue 是否相等,如果不相等,那么讲newValue 反馈到界面上, 通过如果通过 $watch 注册了 listener事件,那么就会调用该事件。


3.常见问题

1.ANGULAR 脏检查(DIRTY CHECK)更新机制

2.ANGULAR 脏检查的优缺点

4.解决方案

 Angular 视图模板里, 读取数据有一个叫 $scope 的上下文, 可以就认为它是一个存储数据的JS对象, 里面放了各种视图模板需要访问的数据/函数。$scope 里面的值可以自由更改 (比如说在 Angular 的控制器(Controller)里面)。 比如说有这样一个非常简单的 Angular 应用 (模板中引用了 $scope 里的 title 数据, 同时绑定了 onclick 事件修改 title 数据):



    <script>

        var testApp = angular.module('testApp', []);

        testApp.controller('TestController', function ($scope) {

            $scope.title = 'test title';

            $scope.onDIVClick = function () {

                $scope.title = 'another title';

            };

        });

    </script>

    <div ng-app="testApp" ng-controller="TestController" ng-click="onDIVClick()">

        {{title}}  <!-- 显示为: test title -->

    </div>

然后点击 div 触发事件, 就会调用 $scope.onDIVClick, 修改了 title 数据, 然后视图就更新了 (内容变为了 another title)。 这一步DOM更新是怎么做到的? 仅仅只是更新了一个JavaScript对象的属性值, 但ES5版本下的JavaScript中并没有监听对象属性变更的方法, 没办法通过事件触发更新; 修改的属性名是任意的, 更不可能提前设置对象的 setter 嵌入数据更新操作。 答案是, Angular 不监听数据更新, 数据发生任何改变时 Angular 都不理睬, 它只是找了一个恰当的时机, 遍历所有的DOM更新方法, 从被修改过任意次的 $scope 数据中尝试更新DO (这里这个"恰当的时机"就是click事件处理结束时)。

* 简单理解,一次脏检查就是调用一次 $apply() 或者 $digest(),将数据中最新的数据呈现在界面上。

* 每次 UI 事件变更,ajax 还有 timeout 都会触发 $apply()。

2.ANGULAR 脏检查的优缺点

不断触发脏检查是不是一种好的方式? 有很多人认为,这样对性能的损耗很大, 不如 setter 和 getter 的观察者模式。 如果界面上某个文本绑定在循环中的变量,会怎样?在脏检测的机制下, 这个过程毫无压力,在后台完成所有数据变更,然后整体应用到界面上。 这时候,基于setter的机制就惨了,除非它也是像Angular这样把批量操作延时到一次更新,否则性能会更低。 所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在, 在不同的业务场景中,避开最容易造成性能瓶颈的用法。

5.编码实战

6.扩展思考

触发ANGULAR的脏检查机制(DIRTY-CHECKING)?

angular 的脏检查机制(dirty-checking), 常见的误解认为angular是定时轮询去检查model是否变更。 angular并不存在定时脏检测。 ngular对常用的dom事件,xhr事件等做了封装, 在里面触发进入angular的digest流程。 在digest流程里面, 会从rootscope开始遍历, 检查所有的watcher。 因此,angular只有在指定事件触发后,才进入$digest cycle:

DOM事件,譬如用户输入文本,点击按钮等。(ng-click)

XHR响应事件 ($http)

浏览器Location变更事件 ($location)

Timer事件($timeout, $interval)

执行$digest()或$apply()

ANGULAR、REACT、VUE 数据更新

不同框架的具体实现思想差别还是挺大的。 像 React, 并不引入任何数据观察机制, 而是利用虚拟节点树 (Virtual DOM), 做到每次都重绘整个界面 (同时保证最小的DOM差异更新); Vue 大致是通过JS setter, 将某个数据的更新绑定到对应的DOM上 (于是区别于 Angular, Vue 并不会每次遍历所有的 watcher)。 而 Angular 的实现核心: 脏检查(Dirty Check)。

7.参考文献

参考一:Angular 脏检查机制研究|Blog - Yunfei

参考一:AngularJS的数据双向绑定是怎么实现的?

8.更多讨论

脏检查有哪些坑? 怎么避免踩坑?

脏检查的内部实现?


返回列表 返回列表
评论

    分享到