AngularJS 소개
AngularJS는 Google에서 시작한 오픈 소스 프런트엔드 MVC 스크립트 프레임워크로 일반 WEB 애플리케이션과 SPA(단일 페이지 애플리케이션, 모든 사용자 작업이 한 페이지에서 완료됨)에 모두 적합합니다. MVC 프레임워크이기도 한 Dojo의 위치 지정과 달리 AngularJS는 jQuery에 비해 기능이 더 가볍습니다. AngularJS는 기계적 바인딩 작업을 많이 줄여줍니다. AngularJS는 높은 개발 속도가 필요하고 너무 풍부한 기능 모듈이 필요하지 않은 일부 비엔터프라이즈 수준 WEB 애플리케이션에 매우 좋은 선택입니다. AngularJS의 가장 복잡하고 강력한 부분은 데이터 바인딩 메커니즘입니다. 이 메커니즘은 기본 DOM에서 하위 수준 작업을 수행하는 대신 모델 설정 및 데이터 전달에 더 집중할 수 있도록 도와줍니다.
AngularJS Scope
jQuery를 기반으로 하는 기존 WEB 애플리케이션에서는 사용자 입력 및 기타 동작을 모니터링하기 위해 각 DOM 요소에 대한 청취 방법, 즉 다양한 이벤트를 모니터링하는 설정이 필요합니다. DOM에서 발생하면 jQuery가 응답하여 페이지에 표시합니다. 이 방법은 간단하고 직관적이지만, WEB 애플리케이션이 커지고 복잡해지면 모니터링 코드가 매우 기계적이고 중복되게 됩니다. 더욱 무서운 점은 DOM 이벤트 모니터링을 제대로 관리하지 않으면 브라우저에서 쉽게 문제를 일으킬 수 있다는 점입니다. 자원 누출.
위에 노출된 문제에 대응하여 AngularJS는 일련의 지침을 사용하여 jQuery의 이벤트 바인딩 코드를 대체합니다. 데이터 혼란을 일으키지 않고 다양한 명령 간의 조정을 구성하기 위해 AngularJS는 모델 계층의 범위 개념을 확장하여 컨트롤러와 협력하여 뷰 계층을 표시합니다.
스코프
AngularJS에서 스코프는 표현식의 실행 환경인 애플리케이션 모델을 가리키는 객체입니다. Scope는 해당 DOM과 거의 동일한 계층 구조를 가지고 있습니다. 범위는 표현식을 모니터링하고 이벤트를 전달할 수 있습니다.
HTML 코드에서 ng-app 지시어가 정의되면 범위가 생성됩니다. ng-app에 의해 생성된 범위는 다른 모든 $의 최상위 수준인 루트 범위($rootScope)입니다. 범위.
목록 1. 루트 범위 생성
<html> <head><script src="angular.min.js"></script></head> <body data-ng-app="app">...</body> </html>
ng-app 지시문을 사용하여 범위를 생성하는 것 외에도 ng - 컨트롤러, ng-repeat 등은 하나 이상의 범위를 생성합니다. 또한 AngularJS에서 제공하는 Scope 생성 팩토리 메소드를 통해 Scope를 생성할 수 있습니다. 이러한 각 범위에는 고유한 상속 컨텍스트가 있으며 루트 범위는 $rootScope입니다.
범위를 생성한 후 AngularJS 코드를 작성할 때 $scope 객체는 이 범위의 데이터 엔터티를 나타냅니다. $scope에서 다양한 데이터 유형을 정의한 다음 HTML을 허용하도록 {{ in HTML Variable name}}을 직접 사용할 수 있습니다. 이 변수에 접근하기 위한 코드는 다음과 같습니다.
Listing 2. Simple data 바인딩
<script> angular.module('app', []) .controller("ctrl", function ($scope) { $scope.btns = { ibm : 'ibm' }; }); </script> </head> <body data-ng-app="app" > <div data-ng-controller="ctrl"> <button>{{btns.ibm}}</button> </div> </body>
이것은 AngularJS에서 가장 간단한 데이터 바인딩 방법입니다. 가장 널리 사용되는 데이터 바인딩 방법이기도 합니다.
상속된 범위
AngularJS는 범위를 생성할 때 컨텍스트를 검색합니다. 컨텍스트에 이미 범위가 있는 경우 새로 생성된 범위는 JavaScript 프로토타입 상속 메커니즘을 사용하여 이를 상속합니다. 상위 범위(아래에 설명된 격리된 범위 제외)
일부 AngularJS 지시문은 새로운 하위 범위를 생성하고 프로토타입 상속을 수행합니다(ng-repeat, ng-include, ng-switch, ng-view, ng-controller, range: true 및 transclude: true로 생성된 지시문).
다음 HTML에는 ng-app 지시문에 의해 생성된 $rootScope, parentCtrl 및 childCtrl에 의해 생성된 하위 범위의 세 가지 범위가 정의되어 있으며, 그중 childCtrl에 의해 생성된 범위는 parentCtrl의 하위 범위입니다.
목록 3. 범위의 상속 인스턴스
<body data-ng-app="app"> <div data-ng-controller="parentCtrl"> <input data-ng-model="args"> <div data-ng-controller="childCtrl"> <input data-ng-model="args"> </div> </div> </body>
상속된 범위는 JavaScript의 프로토타입 상속 메커니즘을 준수합니다. 즉, 하위 범위에 있는 경우 도메인의 상위 범위에 정의된 속성에 액세스하기 위해 JavaScript는 먼저 하위 범위에서 속성을 검색합니다. 해당 속성이 없으면 프로토타입 체인의 상위 범위에서 검색합니다. 프로토타입 체인 위로 올라갈 것입니다. AngularJS에서 범위 프로토타입 체인의 최상위는 $rootScope입니다. AnguarJS는 $rootScope가 발견될 때까지 검색합니다.
이 메커니즘을 설명하기 위해 예제 코드를 사용합니다. 먼저, 프로토타입 데이터 유형에 대한 범위 상속 메커니즘에 대해 논의해 보겠습니다.
목록 4. 범위 상속 예 - 기본 유형 데이터 상속
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = 'IBM DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { }]); </script> <body data-ng-app="app"> <div data-ng-controller="parentCtrl"> <input data-ng-model="args"> <div data-ng-controller="childCtrl"> <input data-ng-model="args"> </div> </div> </body>
페이지에서 다음과 같은 결과를 얻습니다.
그림 1. 페이지 실행 결과.
这个结果我们非常好理解,虽然在 childCtrl 中没有定义具体的 args 属性,但是因为 childCtrl 的作用域继承自 parentCtrl 的作用域,因此,AngularJS 会找到父作用域中的 args 属性并设置到输入框中。而且,如果我们在第一个输入框中改变内容,内容将会同步的反应到第二个输入框:
图 2. 改变第一个输入框的内容后页面运行结果
假如我们修改第二个输入框的内容,此时会发生什么事情呢?答案是第二个输入框的内容从此将不再和第一个输入框的内容保持同步。在改变第二个输入框的内容时,因为 HTML 代码中 model 明确绑定在 childCtrl 的作用域中,因此 AngularJS 会为 childCtrl 生成一个 args 原始类型属性。这样,根据 AngularJS 作用域继承原型机制,childCtrl 在自己的作用域找得到 args 这个属性,从而也不再会去寻找 parentCtrl 的 args 属性。从此,两个输入框的内容所绑定的属性已经是两份不同的实例,因此不会再保持同步。
图 3.改变第二个输入框的内容后页面运行结果
假如我们将代码做如下修改,结合以上两个场景,思考下会出现怎样的结果?
清单 5. 作用域继承实例-对象数据继承
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'IBM DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { }]); </script> <body data-ng-app="app"> <div data-ng-controller="parentCtrl"> <input data-ng-model="args.content"> <div data-ng-controller="childCtrl"> <input data-ng-model="args.content"> </div> </div> </body>
答案是无论改变任何一个输入框的内容,两者的内容始终同步。
根据 AngularJS 的原型继承机制,如果 ng-model 绑定的是一个对象数据,那么 AngularJS 将不会为 childCtrl 创建一个 args 的对象,自然也不会有 args.content 属性。这样,childCtrl 作用域中将始终不会存在 args.content 属性,只能从父作用域中寻找,也即是两个输入框的的变化其实只是在改变 parentCtrl 作用域中的 args.content 属性。因此,两者的内容始终保持同步。
我们再看一个例子,这次请读者自行分析结果。
清单 6. 作用域继承实例-不再访问父作用域的数据对象。
<script type="text/javascript"> angular.module('app', []) .controller('parentCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'IBM DeveloperWorks'; }]) .controller('childCtrl', ['$scope', function($scope) { $scope.args = {}; $scope.args.content = 'IBM DeveloperWorks'; }]); </script> <body data-ng-app="app"> <div data-ng-controller="parentCtrl"> <input data-ng-model="args.content"> <div data-ng-controller="childCtrl"> <input data-ng-model="args.content"> </div> </div> </body>
答案是两个输入框的内容永远不会同步。
孤立作用域(Isolate Scope)
孤立作用域是 AngularJS 中一个非常特殊的作用域,它只在 directive 中出现。在对 directive 的定义中,我们添加上一个 scope:{} 属性,就为这个 directive 创建出了一个隔离作用域。
清单 7. directive 创建出一个孤立作用域
angular.module('isolate', []).directive("isolate", function () { return { scope : {}, }; })
孤立作用域最大的特点是不会原型继承其父作用域,对外界的父作用域保持相对的独立。因此,如果在定义了孤立作用域的 AngularJS directive 中想要访问其父作用域的属性,则得到的值为 undefined。代码如下:
清单 8. 孤立作用域的隔离性
<script type="text/javascript"> angular.module('app', []) .controller('ctrl', ['$scope', function($scope) { $scope.args = {}; }]) .directive("isolateDirective", function () { return { scope : {}, link : function($scope, $element, $attr) { console.log($scope.$args); //输出 undefined } }; }); </script> <body data-ng-app="app"> <div data-ng-controller="ctrl"> <div data-isolate-directive></div> </div> </body>
上面的代码中通过在 directive 中声明了 scope 属性从而创建了一个作用域,其父作用域为 ctrl 所属的作用域。但是,这个作用域是孤立的,因此,它访问不到父作用域的中的任何属性。存在这样设计机制的好处是:能够创建出一些列可复用的 directive,这些 directive 不会相互在拥有的属性值上产生串扰,也不会产生任何副作用。
AngularJS 孤立作用域的数据绑定
在继承作用域中,我们可以选择子作用域直接操作父作用域数据来实现父子作用域的通信,而在孤立作用域中,子作用域不能直接访问和修改父作用域的属性和值。为了能够使孤立作用域也能和外界通信,AngularJS 提供了三种方式用来打破孤立作用域“孤立”这一限制。
单向绑定(@ 或者 @attr)
这是 AngularJS 孤立作用域与外界父作用域进行数据通信中最简单的一种,绑定的对象只能是父作用域中的字符串值,并且为单向只读引用,无法对父作用域中的字符串值进行修改,此外,这个字符串还必须在父作用域的 HTML 节点中以 attr(属性)的方式声明。
使用这种绑定方式时,需要在 directive 的 scope 属性中明确指定引用父作用域中的 HTML 字符串属性,否则会抛异常。示例代码如下:
清单 9. 单向绑定示例
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, template: '<button>{{isolates}}</button>', scope : { isolates : '@', }, link : function($scope, $element, $attr) { $scope.isolates = "DeveloperWorks"; } }; }) .controller("ctrl", function ($scope) { $scope.btns = 'IBM'; }); </script> <body data-ng-app="isolateScope" > <div data-ng-controller="ctrl"> <button>{{btns}}</button> <div data-isolate-directive data-isolates="{{btns}}"></div> </div> </body>
简单分析下上面的代码,通过在 directive 中声明了 scope:{isolates:'@'} 使得 directive 拥有了父作用域中 data-isolates 这个 HTML 属性所拥有的值,这个值在控制器 ctrl 中被赋值为'IBM'。所以,代码的运行结果是页面上有两个名为 IBM 的按钮。
我们还注意到 link 函数中对 isolates 进行了修改,但是最终不会在运行结果中体现。这是因为 isolates 始终绑定为父作用域中的 btns 字符串,如果父作用域中的 btns 不改变,那么在孤立作用域中无论怎么修改 isolates 都不会起作用。
引用绑定(&或者&attr)
通过这种形式的绑定,孤立作用域将有能力访问到父作用域中的函数对象,从而能够执行父作用域中的函数来获取某些结果。这种方式的绑定跟单向绑定一样,只能以只读的方式访问父作用函数,并且这个函数的定义必须写在父作用域 HTML 中的 attr(属性)节点上。
这种方式的绑定虽然无法修改父作用域的 attr 所设定的函数对象,但是却可以通过执行函数来改变父作用域中某些属性的值,来达到一些预期的效果。示例代码如下:
清单 10. 引用绑定示例
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, scope : { isolates : '&', }, link : function($scope, $element, $attr) { var func = $scope.isolates(); func(); } }; }) .controller("ctrl", function ($scope) { $scope.func = function () { console.log("IBM DeveloperWorks"); } }); </script> <body data-ng-app="isolateScope" > <div data-ng-controller="ctrl"> <div data-isolate-directive data-isolates="func"></div> </div> </body>
这个例子中,浏览器的控制台将会输出一段“IBM DeveloperWorks”文字。
上面的代码中我们在父作用域中指定了一个函数对象$scope.func,在孤立作用域中通过对 HTML 属性的绑定从而引用了 func。需要注意的是 link 函数中对 func 对象的使用方法,$scope.isolates 获得的仅仅是函数对象,而不是调用这个对象,因此我们需要在调用完$scope.isolates 之后再调用这个函数,才能得到真正的执行结果。
双向绑定(=或者=attr)
双向绑定赋予 AngularJS 孤立作用域与外界最为自由的双向数据通信功能。在双向绑定模式下,孤立作用域能够直接读写父作用域中的属性和数据。和以上两种孤立作用域定义数据绑定一样,双向绑定也必须在父作用域的 HTML 中设定属性节点来绑定。
双向绑定非常适用于一些子 directive 需要频繁和父作用域进行数据交互,并且数据比较复杂的场景。不过,由于可以自由的读写父作用域中的属性和对象,所以在一些多个 directive 共享父作用域数据的场景下需要小心使用,很容易引起数据上的混乱。
示例代码如下:
清单 11. 双向绑定示例
<script> angular.module('isolateScope', []) .directive("isolateDirective", function () { return { replace : true, template: '<button>{{isolates}}</button>', scope : { isolates : '=', }, link : function($scope, $element, $attr) { $scope.isolates.ibm = "IBM"; } }; }) .controller("ctrl", function ($scope) { $scope.btns = { ibm : 'ibm', dw : 'DeveloperWorks' }; }); </script> <body data-ng-app="isolateScope" > <div data-ng-controller="ctrl"> <button>{{btns.dw}}</button> <button>{{btns.ibm}}</button> <div data-isolate-directive data-isolates="btns"></div> </div> </body>
上面的代码运行的结果是浏览器页面上出现三个按钮,其中第一个按钮标题为“DeveloperWorks”,第二和第三个按钮的标题为“IBM”。
初始时父作用域中的$scope.btns.ibm 为小写的“ibm”,通过双向绑定,孤立作用域中将父作用域的 ibm 改写成为大写的“IBM”并且直接生效,父作用域的值被更改。
总结
由于 AngularJS 框架的轻量性和其清晰的 MVC 特点使得其在推出之后就大受欢迎,实践中也很容易上手。AngularJS 比较难以掌握和理解的就是其作用域和绑定机制,本文重点将作用域和绑定机制做了分析与讨论,希望读者能够理解并熟练掌握这块内容。
更多JavaScript AngularJS 프레임워크의 범위 및 데이터 바인딩에 대한 자세한 설명相关文章请关注PHP中文网!