Angular JS(Angular.JS)는 웹 페이지를 개발하는 데 사용되는 프레임워크, 템플릿, 데이터 바인딩 및 풍부한 UI 구성 요소 집합입니다. 전체 개발 프로세스를 지원하고 수동 DOM 조작 없이 웹 애플리케이션의 아키텍처를 제공합니다. AngularJS는 크기가 60K에 불과하고 주류 브라우저와 호환되며 jQuery와도 잘 작동합니다. 양방향 데이터 바인딩은 MVC의 원리를 완벽하게 보여주는 AngularJS의 가장 멋지고 실용적인 기능일 수 있습니다.
AngularJS의 작동 원리는 다음과 같습니다. HTML 템플릿은 브라우저에 의해 DOM으로 구문 분석됩니다. DOM 구조는 AngularJS 컴파일러 입력이 됩니다. AngularJS는 DOM 템플릿을 탐색하여 해당 NG 명령어를 생성합니다. 모든 명령어는 뷰(즉, HTML의 ng-model)에 대한 데이터 바인딩 설정을 담당합니다. 따라서 NG 프레임워크는 html의
:
<body ng-app="ngApp"> <div ng-controller="ngCtl"> <label ng-model="myLabel"></label> <input type="text" ng-model="myInput" /> <button ng-model="myButton" ng-click="btnClicked"></button> </div> </body>
js에서 로드된 후에만 작동하기 시작합니다. :
// angular app var app = angular.module("ngApp", [], function(){ console.log("ng-app : ngApp"); }); // angular controller app.controller("ngCtl", [ '$scope', function($scope){ console.log("ng-controller : ngCtl"); $scope.myLabel = "text for label"; $scope.myInput = "text for input"; $scope.btnClicked = function() { console.log("Label is " + $scope.myLabel); } }]);
위와 같이 먼저 html로 각도 앱을 정의하고 각도 컨트롤러를 지정하면 컨트롤러가 범위에 해당합니다($scope 접두사를 사용하여 지정할 수 있음). 그런 다음 ngCtl 범위 내 HTML 태그의 값이나 작업을 $scope를 통해 js의 속성 및 메서드에 바인딩할 수 있습니다.
이런 방식으로 두 가지 -NG의 데이터 바인딩이 실현됩니다. 즉, HTML에 표시되는 뷰는 AngularJS의 데이터와 일치합니다. 하나가 수정되면 해당 다른 쪽도 그에 따라 변경됩니다.
이 방법은 정말 매우 좋습니다. 사용하기 편리합니다. js의 각도 컨트롤러 범위에 바인딩된 해당 속성과 메서드에만 관심이 있습니다. 많은 복잡한 DOM을 결합합니다.
이 아이디어는 실제로 jQuery의 DOM 쿼리 및 작업과 완전히 다릅니다. 따라서 많은 사람들은 AngularJS를 사용할 때 jQuery를 혼합하지 말라고 제안합니다. 물론 두 가지 모두 장점과 단점이 있으며 선택에 따라 다릅니다. .
NG의 앱은 각 앱에 여러 개의 컨트롤러를 정의할 수 있으며 각 컨트롤러는 자체 범위를 가지며 서로 간섭하지 않습니다. 데이터 바인딩은 어떻게 적용되나요?
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();를 추가하고 작동하는지 확인해 보세요.
왜 이렇게 해야 하나요? 어떤 상황에서 해야 하나요? 첫 번째 예에서는 다이제스트가 없다는 것을 발견했으며, 다이제스트를 작성하면 다른 다이제스트가 만들어지고 있다는 예외도 발생합니다.
먼저 생각해보자. 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을 생성한 후 이에 해당하는 함수를 꺼냈습니다. -바인드에서 클릭합니다. onclick 이벤트 핸들러 함수에 바인딩합니다. 왜 그럴까요? 데이터가 변경되었음에도 불구하고 아직 인터페이스에 채워지지 않았기 때문에 여기서 몇 가지 추가 작업을 수행해야 합니다.
또 다른 측면에서 보면, 데이터가 변경되면 이 변경 사항을 인터페이스, 즉 세 가지 스팬에 적용해야 합니다. 하지만 Angular는 더티 감지를 사용하므로 데이터를 변경한 후 더티 감지를 트리거하기 위해 직접 작업을 수행한 다음 이를 데이터에 해당하는 DOM 요소에 적용해야 함을 의미합니다. 문제는 더티 감지를 실행하는 방법입니다. 언제 트리거됩니까?
우리는 일부 setter 기반 프레임워크가 데이터 값을 설정할 때 DOM 요소에 바인딩된 변수를 재할당할 수 있다는 것을 알고 있습니다. 더티 감지 메커니즘에는 이 단계가 없으므로 데이터 변경 후 즉시 알림을 받을 수 없으므로 각 이벤트 항목에서 apply()를 수동으로 호출하여 데이터 변경 사항을 인터페이스에 적용할 수 있습니다. 실제 Angular 구현에서는 먼저 더티 감지를 수행하여 데이터가 변경되었는지 확인한 다음 인터페이스 값을 설정합니다.
따라서 실제 클릭을 ng-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这样把批量操作延时到一次更新,否则性能会更低。
所以说,两种不同的监控方式,各有其优缺点,最好的办法是了解各自使用方式的差异,考虑出它们性能的差异所在,在不同的业务场景中,避开最容易造成性能瓶颈的用法。
更多深入学习AngularJS中数据的双向绑定机制相关文章请关注PHP中文网!