标签:
AngularJS怎样处理指令其实是依赖于指令定义时返回的对象属性的,所以要想深入理解如何定义一个指令,首相需要理解指令定义时各个参数的含义。
angular.module(‘app‘, [])
.directive(‘demoDirective‘, function (){ // 依据官方规范,指令的定义时应该严格遵循驼峰式命名规则,使用时采用‘-‘连接单词
return {
restrict : String in [‘E‘, ‘A‘, ‘C‘, ‘M‘],
priority : Number,
terminal : Boolean,
template : String or Template Function : function (tElement, tAttrs) {...},
templateUrl : String,
require : String or String Array,
replace : Boolean or String,
scope : Boolean or Object,
transclude : false or element,
controller : String or function (scope, element, attrs, transclude, otherInjectable) {...},
controllerAs : String,
link : function (scope, element, attrs, otherController) {...},
compile : function (element, attrs, transclude) {
return object || function (...) {...}
}
};
});
上面的这么多参数如果按照功能划分的话,大致上可以分为如下三类:
1. 描述指令或是DOM自身特性的内部参数
2. 连接指令外界的参数,该类参数使得指令可与其他指令或控制器沟通
3. 描述指令自身行为的参数
·restrict :’E’ – Element , ‘A’ – Attribute ,’C’ – Class , ‘M’ – comMent
·priority :指令执行的优先级,默认值为0
·template : 与指令关联的HTML模板,
·**templateUrl :与指令关联的HTML模板路径,
·replace :是否采用HTML模板替换原有的元素
scope 参数的作用是:隔离指令与所在控制器(父级作用域的控制器)间的作用域,隔离指令与指令之间的作用域。
scope的值是可选的,可选值分别为:false,true,object,默认情况下为false;
·false 共享父作用域
·true 继承父作用域,并且新建独立作用域
·object 不继承父作用域,创建新的独立作用域
关于scope可选值的不同结果对比:
<!DOCTYPE html>
<html lang="zh_CN" ng-app="app">
<head>
<meta charset="UTF-8">
<title>AngularJS Index</title>
<script type="text/javascript" src="lib/jquery-1.12.3.js"></script>
<script type="text/javascript" src="lib/angular.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<style>
.box {
max-width: 680px;
min-height: 80px;
margin: 10px;
padding: 10px 15px;
border: solid 1px #2a3ca2;
}
.box span, .box input {
display: block;
}
input[type="text"] {
width: 280px;
margin: 5px 0;
}
</style>
</head>
<body>
<div ng-controller="parentCtrl">
<h3>scope参数选择不同值时的作用域情况对比</h3>
<span>parent</span>
<div class="box" >
<span>{{parentName}}</span>
<input type="text" ng-model="parentName">
<span>{{scopeName}}</span>
<input type="text" ng-model="scopeName">
</div>
<span>scop=False</span>
<dir-f class="box"></dir-f>
<span>scope=True</span>
<dir-t class="box"></dir-t>
<span>scope={}</span>
<dir-o-n class="box"></dir-o-n>
<span>scope={attrs}</span>
<dir-o class="box"></dir-o>
</div>
</body>
</html>
app.js
;(function($) {
‘use strict‘;
angular.module(‘app‘, [])
.controller(‘parentCtrl‘, [‘$scope‘, function($scope) {
$scope.parentName = ‘parentName‘;
$scope.scopeName = ‘scopeNameInParentScope‘;
}])
.directive(‘dirF‘, [function() {
// Runs during compile
return {
// name: ‘‘,
// priority: 1,
// terminal: true,
// scope: {}, // {} = isolate, true = child, false/undefined = no change
// controller: function($scope, $element, $attrs, $transclude) {},
// require: ‘ngModel‘, // Array = multiple requires, ? = optional, ^ = check parent elements
// restrict: ‘A‘, // E = Element, A = Attribute, C = Class, M = Comment
// template: ‘‘,
// templateUrl: ‘‘,
// replace: true,
// transclude: true,
// compile: function(tElement, tAttrs, function transclude(function(scope, cloneLinkingFn){ return function linking(scope, elm, attrs){}})),
//link: function($scope, iElm, iAttrs, controller) {}
restrict: ‘E‘,
scope: false,
replace: true,
template: [
‘<div>‘,
‘ <span>parentName</span>‘,
‘ <span>{{ parentName }}</span>‘,
‘ <input type="text" ng-model="parentName" />‘,
‘ <span>scopeName</span>‘,
‘ <span>{{ scopeName }}</span>‘,
‘ <input type="text" ng-model="scopeName" />‘,
‘</div>‘
].join(‘‘),
controller : [‘$scope‘, function ($scope){
$scope.scopeName = ‘123456‘;
}]
};
}])
.directive(‘dirT‘, [function() {
return {
restrict: ‘E‘,
scope: true,
replace: true,
template: [
‘<div>‘,
‘ <span>parentName</span>‘,
‘ <span>{{ parentName }}</span>‘,
‘ <input type="text" ng-model="parentName" />‘,
‘ <span>scopeName</span>‘,
‘ <span>{{ scopeName }}</span>‘,
‘ <input type="text" ng-model="scopeName" />‘,
‘</div>‘
].join(‘‘),
controller: [‘$scope‘, function ($scope) {
$scope.parentName = ‘parentNameInDirectiveTrue‘;
//$scope.scopeName = ‘scopeNameInDirectiveTrue‘;
// 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
$scope.$watch(‘parentName‘, function(newValue, oblValue) {
console.log(‘directive-true -->> from watch scope parentName = ‘ + newValue);
});
// 监听父作用域中的parentName
$scope.$parent.$watch(‘parentName‘, function(newValue, oldValue) {
console.log(‘directive-true -->> from watch parent scope parentName = ‘ + newValue);
});
}]
};
}])
.directive(‘dirON‘, [function() {
return {
restrict: ‘E‘,
scope: {
},
replace: true,
template: [
‘<div>‘,
‘ <span>parentName</span>‘,
‘ <span>{{ parentName }}</span>‘,
‘ <input type="text" ng-model="parentName" />‘,
‘ <span>scopeName</span>‘,
‘ <span>{{ scopeName }}</span>‘,
‘ <input type="text" ng-model="scopeName" />‘,
‘</div>‘
].join(‘‘),
controller: [‘$scope‘, function ($scope) {
$scope.parentName = ‘parentNameInDirectiveObjectWithNull‘;
//$scope.scopeName = ‘scopeNameInDirectiveObjectWithNull‘;
// 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
$scope.$watch(‘parentName‘, function(newValue, oblValue) {
console.log(‘directive-Object-Null -->> from watch scope parentName = ‘ + newValue);
});
// 监听父作用域中的parentName
$scope.$parent.$watch(‘parentName‘, function(newValue, oldValue) {
console.log(‘directive-Object-Null -->> from watch parent scope parentName = ‘ + newValue);
});
}]
}
}])
.directive(‘dirO‘, [function() {
return {
restrict: ‘E‘,
scope: {
parentName : ‘@‘
},
replace: true,
template: [
‘<div>‘,
‘ <span>parentName</span>‘,
‘ <span>{{ parentName }}</span>‘,
‘ <input type="text" ng-model="parentName" />‘,
‘ <span>scopeName</span>‘,
‘ <span>{{ scopeName }}</span>‘,
‘ <input type="text" ng-model="scopeName" />‘,
‘</div>‘
].join(‘‘),
controller: [‘$scope‘, function ($scope) {
$scope.parentName = ‘parentNameInDirectiveObject‘;
//$scope.scopeName = ‘scopeNameInDirectiveObject‘;
// 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
$scope.$watch(‘parentName‘, function(newValue, oblValue) {
console.log(‘directive-Object -->> from watch scope parentName = ‘ + newValue);
});
// 监听父作用域中的parentName
$scope.$parent.$watch(‘parentName‘, function(newValue, oldValue) {
console.log(‘directive-Object -->> from watch parent scope parentName = ‘ + newValue);
});
// 监听父作用域中的scopeName
$scope.$parent.$watch(‘scopeName‘, function(newValue, oldValue) {
console.log(‘directive-Object -->> from watch parent scope scopeName = ‘ + newValue);
});
}]
}
}]);
}(jQuery));
实验结果:
·scope取值为false时,指令和父作用域共同使用用同一个作用域–示例程序中的scopName
·scope取值为true时,指令和父作用域关系采用JavaScript的原型继承方式实现
·scope取值为object时,指令将采用完全独立的作用域,通过
自定义指令中,当scope取值为非空对象时,指令会将该对象处理成子域scope的扩展属性。这一扩展属性肩负起了指令和父作用域通信的任务。
scoep取值为对象时,对象的属性有三种不同的绑定策略,分别是:
‘@’ or ‘@alias’ | ‘=’ or ‘=alias’ | ‘&’
关于三者间的区别直接上代码,注意代码中的注释部分,解释了三者的区别:
html
<div ng-controller="parentScopeCtrl">
<div class="box">
<p>parentScope :</p>
<span>id</span>
<span>{{ id }}</span>
<input type="text" ng-model="id">
<span>gender</span>
<span>{{ gender }}</span>
<input type="text" ng-model="gender">
<span>demo</span>
<span>{{ demo }}</span>
<input type="text" ng-model="demo">
<span>other</span>
<span>{{ other }}</span>
<input type="text" ng-model="other">
</div>
<!-- ‘@‘和‘=‘对应Attribute属性的值, ‘@‘是单向绑定父域的机制,需要使用{{}}表达式;‘&‘对应的属性名必须要以on开头 -->
<dir-scope my-id="id" gender="gender" my-age="{{ age }}" on-speak="speak(‘demo‘)"></dir-scope>
app.js
.controller(‘parentScopeCtrl‘, [‘$scope‘, function ($scope){
$scope.id = 12345;
$scope.gender = "male";
$scope.age = 24;
$scope.demo = "haha";
$scope.other = "Other";
$scope.speak = function (msg){
console.log(msg);
}
}])
.directive(‘dirScope‘, [function(){
return {
restrict : ‘EA‘,
scope : {
myAge : ‘@‘, /* 子作用能感知到父作用域的变更,反之不行; 需要注意的是,属性赋值时需要使用{{}}表达式,而且该属性的值类型永远是String,也即是执行{{}}表达式返回的String */
myId : ‘=‘, /* 父子作用域双向绑定 */
myGender : ‘=gender‘, /* 父子作用域双向绑定; 与‘=‘不同在于使用属性时的采用gender=‘‘ */
onSpeak : ‘&‘ // 通常&
},
template : [
‘<div>‘,
‘ <span>myID</span>‘,
‘ <span>{{ myId }}</span>‘,
‘ <input type="text" ng-model="myId" />‘,
‘ <span>myGender</span>‘,
‘ <span>{{ myGender }}</span>‘,
‘ <input type="text" ng-model="myGender" />‘,
‘ <span>myAge</span>‘,
‘ <span>{{ myAge }}</span>‘,
‘ <input type="text" ng-model="myAge" />‘,
‘ <span>demo</span>‘,
‘ <span>{{ demo }}</span>‘,
‘ <input type="text" ng-model="demo" />‘,
‘ <span>other</span>‘,
‘ <span>{{ other }}</span>‘,
‘ <input type="text" ng-model="other" />‘,
‘ <button ng-click="log()">Show Different</button>‘,
‘</div>‘
].join(‘‘),
controller : [‘$scope‘, function ($scope){
$scope.log = function (){
console.log(typeof $scope.myId, $scope.myId);
console.log(typeof $scope.myGender, $scope.myGender);
console.log(typeof $scope.myAge, $scope.myAge);
console.log(typeof $scope.onSpeak, $scope.onSpeak);
$scope.onSpeak();
}
$scope.demo = "scope-hahaha"; // 完全与父域中同名属性隔离
// 指令中使用other属性,但是由于scope取值为对象,所以也是与父域完全隔离的
// 在当前$scope中以已经生明的情况下,$scope.$watch监听的是自身的parentName
$scope.$watch(‘demo‘, function(newValue, oblValue) {
console.log(‘directive-scope -->> from watch scope demo = ‘ + newValue);
});
// 监听父作用域中的parentName
$scope.$parent.$watch(‘demo‘, function(newValue, oldValue) {
console.log(‘directive-scope -->> from watch parent scope demo = ‘ + newValue);
});
}]
};
}]);
与scope参数一样,require参数也是指令与外界通信的媒介。scope参数主要负责的是指令与外界作用域间的通信,require参数主要负责指令与指令之间的通信。大部分自定义指令都很能独立完成某项复杂的任务,往往需要多个指令之间相互组合协作。
require参数接收一个String字符串或是一个字符串数组,String值为需要引入的外界指令的名称,实际传入的是外界指令对应的控制器(这个后续再讨论link参数时详细讨论)。require参数在寻找依赖指令时提供了两种策略’?’ 和 ‘^’。
‘?’ – 如果没有查找到相应的指令,则返回null
‘^’ – 从自身开始并向父级作用域链中搜索依赖指令,返回第一个匹配的值,如果没有’^’则仅仅在自身作用域查找。
link与controller参数都是描述指令行为的参数,但他们两分别负责不同的行为描述。
controller关注的是指令自身内部作用域具备什么样的行为,其关注点在于指令作用域的行为上。
link关注的是指令中HTML模板的操作行为,其关注点在于DOM操作行为上。
从AngularJS官方教程上我们可以获知,Angular在刚从Server Response中获取得到静态网页时,首先回去扫描整个页面并收集HTML页面中包含哪些指令(Angular原生的还是用户自定义的),然后再去加载指令的template中的HTML模板或是下载templateUrl中指定的模板,如此类推,如果加载进来的HTML模板中包含其他指令,继续上诉操作,最终形成模板树,并返回相应的模板函数,提供给下一阶段进行数据绑定。简单的应用示例;
<body ng-app=‘app‘>
<dir-demo></dir-demo>
<script >
angular.module(‘app‘ , [])
.directive( ‘dirDemo‘ , function () {
return {
restrict: ‘E‘ ,
template: ‘<p>dirDemo</p><dirDemo2></dirDemo2>‘,
link: function (scope) {
console.log( ‘dirDemo2‘ );
}
};
})
.directive( ‘dirDemo2‘ , function () {
return {
restrict: ‘E‘ ,
template: ‘<p>dirDemo2</p><dirDemo3></dirDemo3>‘,
link: function (scope) {
console.log( ‘dirDemo2‘ );
}
};
})
.directive( ‘dirDemo3‘ , function () {
return {
restrict: ‘E‘ ,
template: ‘<p>dirDemo3</p>‘,
link: function (scope) {
console.log( ‘dirDemo3‘ );
}
};
});
</script >
</ body>
运行上述程序观察输出: dirDemo, dirDemo2, dirDemo3
如果结合代码调试,在link中设置断点,我们可以发现整个执行顺序是:
1 加载模板,形成DOM树
2 执行link函数
3 数据绑定
为什么会是这个执行顺序呢?
其实:在刚形成DOM树的这个时间节点上,进行DOM操作的性能开销是最低的(这事应该是一个DOM片段),进行事件绑定等做操。
angular.module(‘app‘ , [])
.directive( ‘dirDemo‘ , function () {
return {
restrict: ‘E‘ ,
require : ‘^ngModel‘, // 需要引用外界指令ngModel的控制器
template: ‘<p>dirDemo</p><dirDemo2></dirDemo2>‘,
link: function ($scope, $element, $attrs, ctrl) {
$element.bind("click", function () {
console.log("绑定点击事件");
});
$element.append("<p>增加段落块</p>");
//设置样式
$element.css("background-color", "yellow");
//最佳实践中,应该下面的方法转移到controller中
$scope.hello = function () {
console.log("hello");
};
}
};
});
link函数的参数是固定的(
第四个参数即是require参数指定的外部指令的控制器,如果require是一个数组,那么第四个参数相应的也是一个数组,控制器的顺序同require参数中声明的顺序一样。
如果我们在上面的示例代码中添加上controller后,在此进行断点调试可发现,全局的执行顺序是:
1 执行controller,设置各个作用域的scope
2 加载模板,形成DOM模板树
3 执行link函数,设置各级DOM的行为
4 数据绑定,在各级scope中绑定DOM
我们通过定义一个compile来取代link函数。可以说compile提供了一个更细粒度的link函数形式,在compile函数中我们可以使用pre-link和post-link函数来替代link函数。
html
<level-one>
<level-two>
<level-three>Hello </level-three>
</level-two>
</level-one>
app.js
function createDirective(name){
return function(){
return {
restrict: ‘E‘,
compile: function(tElem, tAttrs){
console.log(name + ‘: compile‘);
return {
pre: function(scope, iElem, iAttrs){
console.log(name + ‘: pre-link‘);
},
post: function(scope, iElem, iAttrs){
console.log(name + ‘: post-link‘);
}
}
}
}
}
};
angular.module(‘app‘, [])
.directive(‘levelOne‘, createDirective(‘levelOne‘))
.directive(‘levelTwo‘, createDirective(‘levelTwo‘))
.directive(‘levelThree‘, createDirective(‘levelThree‘));
运行上面的实例程序,我们能够简单的了解到AngularJS在处理指令时的内部流程。
程序控制台输出为:
levelOne: compile
levelTwo: compile
levelThree: compile
levelOne: pre-link
levelTwo: pre-link
levelThree: pre-link
levelThree: post-link
levelTwo: post-link
levelOne: post-link
从上面的示例中我们可以发现,link过程,其实还分为pre-link和post-link阶段。同时Angular在指令的link函数调用前先调用了指令的compile函数对指令进行了编译。
关于compile的更多内容将在下一篇文章中进行分享。
参考文献
[AngularJS Developer Guide - Directive][https://docs.angularjs.org/guide/directive]
AngularJS 学习笔记 -- 指令(Directive)
标签:
原文地址:http://blog.csdn.net/shenlei19911210/article/details/51173291