Angular沉思录数据绑定

Posted 前端初学者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Angular沉思录数据绑定相关的知识,希望对你有一定的参考价值。

每天一篇好文章系列18年第76期 

编者按

Angular沉思录

接触AngularJS已经两年多了,时常问自己一些问题,如果是我实现它,会在哪些方面选择跟它相同的道路,哪些方面不同。为此,记录了一些思考,给自己回顾,也供他人参考。

初步大致有以下几个方面:

  • 数据双向绑定

  • 视图模型的继承关系

  • 模块和依赖注入的设计

  • 待定

数据的双向绑定

Angular实现了双向绑定机制。所谓的双向绑定,无非是从界面的操作能实时反映到数据,数据的变更能实时展现到界面。

一个最简单的示例就是这样:

<div ng-controller="CounterCtrl">
    <span ng-bind="counter"></span>
    <button ng-click="counter=counter+1">increase</button>
</div>
function CounterCtrl($scope) {    $scope.counter = 1;
}

这个例子很简单,毫无特别之处,每当点击一次按钮,界面上的数字就增加一。

绑定数据是怎样生效的

初学AngularJS的人可能会踩到这样的坑,假设有一个指令:

var app = angular.module("test", []);app.directive("myclick", function() {    return function (scope, element, attr) {        element.on("click", function() {            scope.counter++;
        });
    };
});app.controller("CounterCtrl", function($scope) {    $scope.counter = 0;
});
<body ng-app="test">
    <div ng-controller="CounterCtrl">
        <button myclick>increase</button>
        <span ng-bind="counter"></span>
    </div>
</body>

这个时候,点击按钮,界面上的数字并不会增加。很多人会感到迷惑,因为他查看调试器,发现数据确实已经增加了,Angular不是双向绑定吗,为什么数据变化了,界面没有跟着刷新?

试试在scope.counter++;这句之后加一句scope.digest();再看看是不是好了?

为什么要这么做呢,什么情况下要这么做呢?我们发现第一个例子中并没有digest,而且,如果你写了digest,它还会抛出异常,说正在做其他的digest,这是怎么回事?

我们先想想,假如没有AngularJS,我们想要自己实现这么个功能,应该怎样?

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>two-way binding</title>
    </head>
    <body onload="init()">
        <button ng-click="inc">
            increase 1
        </button>
        <button ng-click="inc2">
            increase 2
        </button>
        <span style="color:red" ng-bind="counter"></span>
        <span style="color:blue" ng-bind="counter"></span>
        <span style="color:green" ng-bind="counter"></span>

        <script type="text/javascript">            /* 数据模型区开始 */            var counter = 0;            function inc() {                counter++;            }            function inc2() {                counter+=2;            }            /* 数据模型区结束 */            /* 绑定关系区开始 */            function init() {                bind();            }            function bind() {                var list = document.querySelectorAll("[ng-click]");                for (var i=0; i<list.length; i++) {                    list[i].onclick = (function(index) {                        return function() {                            window[list[index].getAttribute("ng-click")]();                            apply();                        };                    })(i);                }            }            function apply() {                var list = document.querySelectorAll("[ng-bind='counter']");                for (var i=0; i<list.length; i++) {                    list[i].innerHTML = counter;                }            }            /* 绑定关系区结束 */        </script>
    </body>
</html>

可以看到,在这么一个简单的例子中,我们做了一些双向绑定的事情。从两个按钮的点击到数据的变更,这个很好理解,但我们没有直接使用DOM的onclick方法,而是搞了一个ng-click,然后在bind里面把这个ng-click对应的函数拿出来,绑定到onclick的事件处理函数中。为什么要这样呢?因为数据虽然变更了,但是还没有往界面上填充,我们需要在此做一些附加操作。

从另外一个方面看,当数据变更的时候,需要把这个变更应用到界面上,也就是那三个span里。但由于Angular使用的是脏检测,意味着当改变数据之后,你自己要做一些事情来触发脏检测,然后再应用到这个数据对应的DOM元素上。问题就在于,怎样触发脏检测?什么时候触发?

我们知道,一些基于setter的框架,它可以在给数据设值的时候,对DOM元素上的绑定变量作重新赋值。脏检测的机制没有这个阶段,它没有任何途径在数据变更之后立即得到通知,所以只能在每个事件入口中手动调用apply(),把数据的变更应用到界面上。在真正的Angular实现中,这里先进行脏检测,确定数据有变化了,然后才对界面设值。

所以,我们在ng-click里面封装真正的click,最重要的作用是为了在之后追加一次apply(),把数据的变更应用到界面上去。

那么,为什么在ng-click里面调用$digest的话,会报错呢?因为Angular的设计,同一时间只允许一个$digest运行,而ng-click这种内置指令已经触发了$digest,当前的还没有走完,所以就出错了。

其实还有$digest和$apply的相关,因篇幅的关系这里就不一一列举了,有兴趣的朋友可以滑动到屏幕底部左下角点击“阅读原文”,查看更多。



点评

文章深入浅出的讲解了angular数据绑定的原理,如何使用angular进行数据绑定,讲解了数据绑定是如何实现的。接下来讲了一下脏检测机制。$apply和$digest的使用和区别。在学习angularJS的同学可以看一下,对了解angularJS运行机制有一定帮助。



以上是关于Angular沉思录数据绑定的主要内容,如果未能解决你的问题,请参考以下文章

实用代码片段将json数据绑定到html元素 (转)

angular 数据双向绑定的终极奥义

元编程沉思录

json 可视代码工作室Angular with Firebase片段

Angular8 ngFor子数据绑定不呈现

typescript Angular 2测试片段。代码库https://developers.livechatinc.com/blog/category/programming/angular-2/