Angular JS数据的双向绑定

接触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中,有$apply和$digest两个函数,我们刚才是通过$digest来让这个数据应用到界面上。但这个时候,也可以不用$digest,而是使用$apply,效果是一样的,那么,它们的差异是什么呢?

最直接的差异是,$apply可以带参数,它可以接受一个函数,然后在应用数据之后,调用这个函数。所以,一般在集成非Angular框架的代码时,可以把代码写在这个里面调用。

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

app.directive("myclick", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.counter  ;
      scope.$apply(function() {
        scope.counter  ;
      });
    });
  };
});

app.controller("CounterCtrl", function($scope) {
  $scope.counter = 0;
});

除此之外,还有别的区别吗?

在简单的数据模型中,这两者没有本质差别,但是当有层次结构的时候,就不一样了。考虑到有两层作用域,我们可以在父作用域上调用这两个函数,也可以在子作用域上调用,这个时候就能看到差别了。

对于$digest来说,在父作用域和子作用域上调用是有差别的,但是,对于$apply来说,这两者一样。我们来构造一个特殊的示例:

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

app.directive("increasea", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.a  ;
      scope.$digest();
    });
  };
});

app.directive("increaseb", function() {
  return function (scope, element, attr) {
    element.on("click", function() {
      scope.b  ;
      scope.$digest();  //这个换成$apply即可
    });
  };
});

app.controller("OuterCtrl", ["$scope", function($scope) {
  $scope.a = 1;

  $scope.$watch("a", function(newVal) {
    console.log("a:"   newVal);
  });

  $scope.$on("test", function(evt) {
    $scope.a  ;
  });
}]);

app.controller("InnerCtrl", ["$scope", function($scope) {
  $scope.b = 2;

  $scope.$watch("b", function(newVal) {
    console.log("b:"   newVal);
    $scope.$emit("test", newVal);
  });
}]);
<div ng-app="test">
  <div ng-controller="OuterCtrl">
    <div ng-controller="InnerCtrl">
      <button increaseb>increase b</button>
      <span ng-bind="b"></span>
    </div>
    <button increasea>increase a</button>
    <span ng-bind="a"></span>
  </div>
</div> 


这时候,我们就能看出差别了,在increase b按钮上点击,这时候,a跟b的值其实都已经变化了,但是界面上的a没有更新,直到点击一次increase a,这时候刚才对a的累加才会一次更新上来。怎么解决这个问题呢?只需在increaseb这个指令的实现中,把$digest换成$apply即可。

当调用$digest的时候,只触发当前作用域和它的子作用域上的监控,但是当调用$apply的时候,会触发作用域树上的所有监控。

因此,从性能上讲,如果能确定自己作的这个数据变更所造成的影响范围,应当尽量调用$digest,只有当无法精确知道数据变更造成的影响范围时,才去用$apply,很暴力地遍历整个作用域树,调用其中所有的监控。

从另外一个角度,我们也可以看到,为什么调用外部框架的时候,是推荐放在$apply中,因为只有这个地方才是对所有数据变更都应用的地方,如果用$digest,有可能临时丢失数据变更。

脏检测的利弊

很多人对Angular的脏检测机制感到不屑,推崇基于setter,getter的观测机制,在我看来,这只是同一个事情的不同实现方式,并没有谁完全胜过谁,两者是各有优劣的。

大家都知道,在循环中批量添加DOM元素的时候,会推荐使用DocumentFragment,为什么呢,因为如果每次都对DOM产生变更,它都要修改DOM树的结构,性能影响大,如果我们能先在文档碎片中把DOM结构创建好,然后整体添加到主文档中,这个DOM树的变更就会一次完成,性能会提高很多。

同理,在Angular框架里,考虑到这样的场景:

function TestCtrl($scope) {
  $scope.numOfCheckedItems = 0;

  var list = [];

  for (var i=0; i<10000; i  ) {
    list.push({
      index: i,
      checked: false
    });
  }

  $scope.list = list;

  $scope.toggleChecked = function(flag) {
    for (var i=0; i<list.length; i  ) {
      list[i].checked = flag;
      $scope.numOfCheckedItems  ;
    }
  };
}

如果界面上某个文本绑定这个numOfCheckedItems,会怎样?在脏检测的机制下,这个过程毫无压力,一次做完所有数据变更,然后整体应用到界面上。这时候,基于setter的机制就惨了,除非它也是像Angular这样把批量操作延时到一次更新,否则性能会更低。

所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在,在不同的业务场景中,避开最容易造成性能瓶颈的用法。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

Angular JS数据的双向绑定详解及实例的更多相关文章

  1. html5 拖拽及用 js 实现拖拽功能的示例代码

    这篇文章主要介绍了html5 拖拽及用 js 实现拖拽,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  2. amaze ui 的使用详细教程

    这篇文章主要介绍了amaze ui 的使用详细教程,本文通过多种方法给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. swift皮筋弹动发射飞机ios源码

    这是一个款采用swift实现的皮筋弹动发射飞机游戏源码,游戏源码比较详细,大家可以研究学习一下吧。

  4. Swift与Js通过WebView交互

    开发环境:Swfit2.3XCode8.2基础概念jscontext,jscontext是代表JS的执行环境,通过-evaluateScript:方法就可以执行一JS代码JSValue,JSValue封装了JS与ObjC中的对应的类型,以及调用JS的API等JSExport,JSExport是一个协议,遵守此协议,就可以定义我们自己的协议,在协议中声明的API都会在JS中暴露出来,才能调用Swif

  5. JSCore swift

    如果双方相互引用,会造成循环引用,而导致内存泄露。以上是Jscore的基本使用,比较简单

  6. Swift WKWebView的js调用swift

    最近项目需求,需要用到JavaScriptCore和WebKit,但是网上的资源有限,而且比较杂,都是一个博客复制另外一个博客,都没有去实际敲代码验证,下面给大家分享一下我的学习过程。

  7. Swift WKWebView的swift调用js

    不多说,直接上代码:在html里面要添加的的代码,显示swift传过去的参数:这样就实现了swift给js传参数和调用!

  8. 在 Swift 專案中使用 Javascript:編寫一個將 Markdown 轉為 HTML 的編輯器

    你有強烈的好奇心,希望在你的iOS專案中使用JavaScript。jscontext中的所有值都是JSValue對象,JSValue類用於表示任意類型的JavaScript值。因此,我們既需要寫Swift代碼也要寫JavaScript代碼。此外,我們還會在JavaScript中按照這個類的定義來創建一個對象并對其屬性進行賦值。從Swift中呼叫JavaScript就如介紹中所言,JavaScriptCore中最主要的角色就是jscontext類。一個jscontext對象是位於JavaScript環境和本

  9. swift - WKWebView JS 交互

    本文介绍WKWebView怎么与js交互,至于怎么用WKWebView这里就不介绍了HTML代码APP调JS代码结果JS给APP传参数首先注册你需要监听的js方法名2.继承WKScriptMessageHandler并重写userContentController方法,在该方法里接收JS传来的参数3.结果

  10. swift 开发UIWebView跟JS的交互

    前言作为小白的我,才开始入门IOS,选择了swift来进行入门学习,学习做着公司一个简单的小小项目,该项目需要进行跟H5进行交互,然后我就开始研究了UIWebView的使用,其实基本原理跟Android的一样,因为我是Android开发的,所以就顺水推舟了。))//这里设置你需要加载的地址}overridefuncdidReceiveMemoryWarning(){super.didReceiveMemoryWarning()//disposeofanyresourcesthatcanberecreate

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部