angularJS详解

这篇文章转载  kooky798 的博客,http://blog.csdn.net/yy374864125/article/details/41349417,

写到这么详细也是没谁了,必须粉一个

 

 

1 前言

前端技术的发展是如此之快,各种优秀技术、优秀框架的出现简直让人目不暇接,紧跟时代潮流,学习掌握新知识自然是不敢怠慢。

AngularJS是google在维护,其在国外已经十分火热,可是国内的使用情况却有不小的差距,参考文献/网络文章也很匮乏。这里便将我学习AngularJS写成文档,一方面作为自己学习路程上的记录,另一方面也给有兴趣的同学一些参考。

首先我自己也是一名学习者,会以学习者的角度来整理我的行文思路,这里可能只是些探索,有理解或是技术上的错误还请大家指出;其次我特别喜欢编写小例子来把一件事情说明白,故在文中会尽可能多的用示例加代码讲解,我相信这会是一种比较好的方式;最后,我深知AngularJS的使用方式跟jquery的使用方式有很大不同,在大家都有jquery、ext经验的条件下对于angular的学习会困难重重,不过我更相信在大家的坚持下,能够快速的学好AngularJS,至少咱也能深入了解到AngularJS的基本思想,对咱们以后自己的插件开发、项目开发都会有很大的启示。


2 AngularJS概述


2.1 AngularJS是什么?

AngularJs(后面就简称ng了)是一个用于设计动态web应用的结构框架。首先,它是一个框架,不是类库,是像EXT一样提供一整套方案用于设计web应用。它不仅仅是一个javascript框架,因为它的核心其实是对HTML标签的增强。

何为HTML标签增强?其实就是使你能够用标签完成一部分页面逻辑,具体方式就是通过自定义标签、自定义属性等,这些HTML原生没有的标签/属性在ng中有一个名字:指令(directive)。后面会详细介绍。那么,什么又是动态web应用呢?与传统web系统相区别,web应用能为用户提供丰富的操作,能够随用户操作不断更新视图而不进行url跳转。ng官方也声明它更适用于开发CRUD应用,即数据操作比较多的应用,而非是游戏或图像处理类应用。

为了实现这些,ng引入了一些非常棒的特性,包括模板机制、数据绑定、模块、指令、依赖注入、路由。通过数据与模板的绑定,能够让我们摆脱繁琐的DOM操作,而将注意力集中在业务逻辑上。

  另外一个疑问,ng是MVC框架吗?还是MVVM框架?官网有提到ng的设计采用了MVC的基本思想,而又不完全是MVC,因为在书写代码时我们确实是在用ng-controller这个指令(起码从名字上看,是MVC吧),但这个controller处理的业务基本上都是与view进行交互,这么看来又很接近MVVM。让我们把目光移到官网那个非醒目的title上:“AngularJS — Superheroic JavaScript MVW Framework”。


2.2 AngularJS简单介绍

AngularJS 重新定义了前端应用的开发方式。面对HTML和JavaScript之间的界线,它

非但不畏缩不前,反而正面出击,提出了有效的解决方案。

很多前端应用的开发框架,比如Backbone、EmberJS等,都要求开发者继承此框架特有的一些JavaScript对象。这种方式有其长处,但它不必要地污染了开发者自己代码的对象空间,还要求开发者去了解内存里那些抽象对象。尽管如此我们还是接受了这种方式,因为网络最初的设计无法提供 我们今天所需的交互性,于是我们需要框架,来帮我们填补JavaScript和HTML之间的鸿沟。而且有了它,你不用再“直接”操控DOM,只要给你的DOM注上metadata(即AngularJS里的directive们),然后让AngularJS来帮你操纵DOM。同时,AngularJS不依赖(也不妨碍)任何其他的框架。你甚至可以基于其它的框架来开发AngularJS应用。

API地址:http://docs.angularjs.org/api/;

AngularJS在github上的中文粗译版地址:https://github.com/basestyle/angularjs-cn。


2.3 什么时候该用AngularJS

AngularJS是一个 MV* 框架,最适于开发客户端的单页面应用。它不是个功能库,而是用来开发动态网页的框架。它专注于扩展HTML的功能,提供动态数据绑定(data binding),而且它能跟其它框架(如jQuery)合作融洽。

如果你要开发的是单页应用,AngularJS就是你的上上之选。Gmail、Google Docs、Twitter和Facebook这样的应用,都很能发挥AngularJS的长处。但是像游戏开发之类对DOM进行大量操纵、又或者单纯需要 极高运行速度的应用,就不是AngularJS的用武之地了。


3 AugularJS特性

AngularJS是一个新出现的强大客户端技术,提供给大家的一种开发强大应用的方式。这种方式利用并且扩展HTML,CSS和javascript,并且弥补了它们的一些非常明显的不足。本应该使用HTML来实现而现在由它开发的动态一些内容。

AngularJS有五个最重要的功能和特性:


3.1 特性一:双向的数据绑定

数据绑定可能是AngularJS最酷最实用的特性。它能够帮助你避免书写大量的初始代码从而节约开发时间。一个典型的web应用可能包含了80%的代码用来处理,查询和监听DOM。数据绑定是的代码更少,你可以专注于你的应用。

我们想象一下Model是你的应用中的简单事实。你的Model是你用来读取或者更新的部分。数据绑定指令提供了你的Model投射到view的方法。这些投射可以无缝的,毫不影响的应用到web应用中。

传统来说,当model变化了。 开发人员需要手动处理DOM元素并且将属性反映到这些变化中。这个一个双向的过程。一方面,model变化驱动了DOM中元素变化,另一方面,DOM元素的变化也会影响到Model。这个在用户互动中更加复杂,因为开发人员需要处理和解析

这些互动,然后融合到一个model中,并且更新View。这是一个手动的复杂过程,当一个应用非常庞大的时候,将会是一件非常费劲的事情。

这里肯定有更好的解决方案!那就是AngularJS的双向数据绑定,能够同步DOM和Model等等。

这里有一个非常简单的例子,用来演示一个input输入框和

元素的双向绑定(例01):


说明:实际效果请大家看AngularJS/demo/index.html


3.2 特性二:模板

在AngularJS中,一个模板就是一个HTML文件。但是HTML的内容扩展了,包含了很多帮助你映射model到view的内容。

HTML模板将会被浏览器解析到DOM中。DOM然后成为AngularJS编译器的输入。AngularJS将会遍历DOM模板来生成一些指导,即,directive(指令)。所有的指令都负责针对view来设置数据绑定。

我们要理解AuguarJS并不把模板当做String来操作。输入AngularJS的是DOM而非string。数据绑定是DOM变化,不是字符串的连接或者innerHTML变化。使用DOM作为输入,而不是字符串,是AngularJS区别于其它的框架的最大原因。使用DOM允许你扩展指令词汇并且可以创建你自己的指令,甚至开发可重用的组件。

最大的好处是为设计师和开发者创建了一个紧密的工作流。设计师可以像往常一样开发标签,然后开发者拿过来添加上功能,通过数据绑定将会使得这个过程非常简单。

这里有一个例子,我们使用ng-repeat指令来循环图片数组并且加入img模板,如下:

function AlbumCtrl($scope) {

    scope.images = [

        {"image":"img/image_01.png", "description":"Image 01 description"},

        {"image":"img/image_02.png", "description":"Image 02 description"},

        {"image":"img/image_03.png", "description":"Image 03 description"},

        {"image":"img/image_04.png", "description":"Image 04 description"},

        {"image":"img/image_05.png", "description":"Image 05 description"}

    ];

}



 


       


  •      

       


  •  




这里还有一件事值得提一句,AngularJS并不强制你学习一个新的语法或者从你的应用中提出你的模板。


3.3 特性三:MVC

针对客户端应用开发AngularJS吸收了传统的MVC基本原则。MVC或者Model-View-Controll设计模式针对不同的人可能意味不同的东西。AngularJS并不执行传统意义上的MVC,更接近于MVVM(Moodel-View-ViewModel)。

Model

model是应用中的简单数据。一般是简单的javascript对象。这里没有必要继承框架的classes,使用proxy对象封装或者使用特别的setter/getter方法来访问。事实上我们处理vanilla javascript的方法就是一个非常好的特性,这种方法使得我们更少使用应用的原型。

ViewModel

viewmodel是一个用来提供特别数据和方法从而维护指定view的对象。

viewmodel是$scope的对象,只存在于AnguarJS的应用中。$scope只是一个简单的js对象,这个对象使用简单的API来侦测和广播状态变化。

Controller

controller负责设置初始状态和参数化$scope方法用以控制行为。需要指出的controller并不保存状态也不和远程服务互动。

View

view是AngularJS解析后渲染和绑定后生成的HTML 。这个部分帮助你创建web应用的架构。$scope拥有一个针对数据的参考,controller定义行为,view处理布局和互动。


3.4 特性四:服务和依赖注入

AngularJS服务其作用就是对外提供某个特定的功能。

AngularJS拥有内建的依赖注入(DI)子系统,可以帮助开发人员更容易的开发,理解和测试应用。

DI允许你请求你的依赖,而不是自己找寻它们。比如,我们需要一个东西,DI负责找创建并且提供给我们。

为了而得到核心的AngularJS服务,只需要添加一个简单服务作为参数,AngularJS会侦测并且提供给你:

function EditCtrl($scope, $location, $routeParams) {

     // Something clever here...

}

你也可以定义自己的服务并且让它们注入:

angular.module('MyServiceModule', []).

    factory('notify', ['$window', function (win) {

    return function (msg) {

        win.alert(msg);

    };

}]);

function myController(scope, notifyService) {

    scope.callNotify = function (msg) {

        notifyService(msg);

    };

}

myController.$inject = ['$scope', 'notify'];


3.5 特性五:指令(Directives)

指令是我个人最喜欢的特性。你是不是也希望浏览器可以做点儿有意思的事情?那么AngularJS可以做到。

指令可以用来创建自定义的标签。它们可以用来装饰元素或者操作DOM属性。可以作为标签、属性、注释和类名使用。

这里是一个例子,它监听一个事件并且针对的更新它的$scope ,如下:

myModule.directive('myComponent', function(mySharedService) {

    return {

        restrict: 'E',

        controller: function($scope, $attrs, mySharedService) {

            $scope.$on('handleBroadcast', function() {

                $scope.message = 'Directive: ' mySharedService.message;

            });

        },

        replace: true,

        template: ''

    };

});

然后,你可以使用这个自定义的directive来使用:



使用一系列的组件来创建你自己的应用将会让你更方便的添加,删除和更新功能。

 


4 功能介绍


4.1数据绑定

AngularJS的双向数据绑定,意味着你可以在Mode(JS)中改变数据,而这些变动立刻就会自动出现在View上,反之亦然。即:一方面可以做到model变化驱动了DOM中元素变化,另一方面也可以做到DOM元素的变化也会影响到Model。

在我们使用jQuery的时候,代码中会大量充斥类似这样的语句:var val = $(‘#id’).val(); $(‘#id’).html(str);等等,即频繁的DOM操作(读取和写入),其实我们的最终目的并不是要操作DOM,而是要实现业务逻辑。ng的绑定将让你摆脱DOM操作,只要模板与数据通过声明进行了绑定,两者将随时保持同步,最新的数据会实时显示在页面中,页面中用户修改的数据也会实时被记录在数据模型中。

从View到Controller再到View的数据交互(例01):



……



Hello, {{ user.name }}!

……

关键: ng-app 、 ng-model 和 { {user.name } }

首先: 元素的ng-app属性。标识这个DOM里面的内容将启用AngularJS应用。

其次:告诉AngularJS,对页面上的“user.name” 这个Model进行双向数据绑定。

第三:告诉AngularJS,在“{{ user.name}}”这个指令模版上显示“user.name”这个Model的数据。

从Server到Controller再到View的数据交互(例02):



……





获取名字

……

demoApp.controller("demoController", function($http, $scope){

$scope. getAjaxUser = function(){

// $http.get({url:"../xxx.action"}).success(function(data){

// $scope.user= data;

// });

$scope.user = {"name":"从JOSN中获取的名称","age":22};

};

});

改变$scope中的user,View也会自动更新。


4.2 scopes、module、controller


4.2.1 scopes

$scope是一个把view(一个DOM元素)连结到controller上的对象。在我们的MVC结构里,这个 $scope 将成为model,它提供一个绑定到DOM元素(以及其子元素)上的excecution context。

尽管听起来有点复杂,但 $scope 实际上就是一个JavaScript对象,controller和view都可以访问它,所以我们可以利用它在两者间传递信息。在这个 $scope 对象里,我们既存储数据,又存储将要运行在view上的函数。

每一个Angular应用都会有一个 $rootScope。这个 $rootScope 是最顶级的scope,它对应着含有 ng-app 指令属性的那个DOM元素。

app.run(function($rootScope) { $rootScope.name = "张三"; });

如果页面上没有明确设定 $scope ,Angular 就会把数据和函数都绑定到这里, 第一部分中的例子就是靠这一点成功运行的。

这样,我们就可以在view的任何地方访问这个name属性,使用模版表达式{{}},像这样:

{{ name }}  


4.2.2 module

首先需要明确一下模板的概念。在我还不知道有模板这个东西的时候,曾经用js拼接出很长的HTML字符串,然后append到页面中,这种方式想想真是又土又笨。后来又看到可以把HTML代码包裹在一个

将当前元素的内容添加到有ng-transclude属性的这个DIV下,默认是隐藏的。

3.Controller信息:

demoApp.controller("test7Controller", function($scope){

$scope.title = '个人简介';

$scope.text = '大家好,我正在研究AngularJs,欢迎大家与我交流。';

$scope.updateInfo = function (){

$scope.title = '个人信息';

$scope.text = '大家好,今天天气真好!';

}

});

4.指令使用方式(View信息)为:

{ {text} }

Etitle指向Controller中的$scope.title。注意命名方式:指令名为userInfo,对应的标签为user-info。


4.7服务(service)
4.7.1服务介绍

  服务这个概念其实并不陌生,在其他语言中如java便有这样的概念,其作用就是对外提供某个特定的功能,如消息服务,文件压缩服务等,是一个独立的模块。ng的服务是这样定义的:

Angular services are singletons objects or functions that carry out specific tasks common to web apps.

它是一个单例对象或函数,对外提供特定的功能。

首先是一个单例,即无论这个服务被注入到任何地方,对象始终只有一个实例。

其次这与我们自己定义一个function然后在其他地方调用不同,因为服务被定义在一个模块中,所以其使用范围是可以被我们管理的。ng的避免全局变量污染意识非常强。

  ng提供了很多内置的服务,可以到API中查看http://docs.angularjs.org/api/。知道了概念,我们来拉一个service出来溜溜,看看到底是个什么用法。  

  我们在controller中直接声明$location服务,这依靠ng的依赖注入机制。$location提供地址栏相关的服务,我们在此只是简单的获取当前的地址。

  服务的使用是如此简单,我们可以把服务注入到controller、指令或者是其他服务中。
4.7.2自定义服务

  如同指令一样,系统内置的服务以$开头,我们也可以自己定义一个服务。定义服务的方式有如下几种:

l 使用系统内置的$provide服务;

l 使用Module的factory方法;

l 使用Module的service方法。

  下面通过一个小例子来分别试验一下。我们定义一个名为remoteData服务,它可以从远程获取数据,这也是我们在程序中经常使用的功能。不过我这里没有远程服务器,就写死一点数据模拟一下。

//使用$provide来定义

var app = angular.module('MyApp', [], function($provide) {

    $provide.factory('remoteData', function() {

 var data = {name:'n',value:'v'};

        return data;

    });

});

//使用factory方法

app.factory('remoteData',function(){

    var data = {name:'n',value:'v'};

    return data;

});

//使用service方法

app.service('remoteData',function(){

    this.name = 'n';

    this.value = 'v';

});

Module的factory和$provide的factory方法是一模一样的,从官网文档看它们其实就是一回事。至于Module内部是如何调用的,我此处并不打算深究,我只要知道怎么用就好了。

再看Module的service方法,它没有return任何东西,是因为service方法本身返回一个构造器,系统会自动使用new关键字来创建出一个对象。所以我们看到在构造器函数内可以使用this,这样调用该服务的地方便可以直接通过remoteData.name来访问数据了。
4.7.3管理服务的依赖关系

  服务与服务中间可以有依赖关系,例如我们这里定义一个名为validate的服务,它的作用是验证数据是否合法,它需要依赖我们从远程获取数据的服务remoteData。代码如下:

   在factory的参数中,我们可以直接传入服务remoteData,ng的依赖注入机制便帮我们做好了其他工作。不过一定要保证这个参数的名称与服务名称一致,ng是根据名称来识别的。若参数的名次与服务名称不一致,你就必须显示的声明一下,方式如下:

app.factory('validate',['remoteData',function(remoteDataService){

    return function(){

        if(remoteDataService.name=='n'){

            alert('验证通过');

        }

    };

}]);  

我们在controller中注入服务也是同样的道理,使用的名称需要与服务名称一致才可以正确注入。否则,你必须使用$inject来手动指定注入的服务。比如:

function testC(scope,rd){

    scope.getData = function(){

        alert('name:' rd.name '   value:' rd.value);

    }

}

testC.$inject = ['$scope','remoteData'];

 

  在controller中注入服务,也可以在定义controller时使用数组作为第二个参数,在此处

把服务注入进去,这样在函数体中使用不一致的服务名称也是可以的,不过要确保注入的顺序是一致的,如:

app.controller('testC',['$scope','remoteData',function($scope,rd){

    $scope.getData = function(){

        alert('name:' rd.name '   value:' rd.value);

    }

}]);
4.7.4 自定义服务示例

接下来让我们看下例子(例08 自定义服务)代码,自定义userService服务:

demoApp.factory('userService', ['$http', function($http) {

var doGetUser = function(userId, path) {

//return $http({

//method: 'JSONP',

//url: path

//});

/*手动指定数据*/

var data = {userId:"woshishui",userName:"我是谁",userInfo:"我是谁!我是谁!"};;

if(userId=='zhangsan'){

data = {userId:"zhangsan",userName:"张三",userInfo:"我是张三,我为自己"};

}else if(userId=='lisi'){

data = {userId:"lisi",userName:"李四",userInfo:"我是李四,我为卿狂!"};

}

return data;

}

return {

/*userService对外暴露的函数,可有多个*/

getUser: function(userId) {

return doGetUser(userId, '../xxx/xxx.action');

}

};

}]);

我们创建了一个只有一个方法的userService,getUser为这个服务从后台获取用户信息的函数,并且对外暴露。当然,由于这是一个静态的例子,无法访问后台,那么我们便制定其返回的数据。

然后我们把这个服务添加到我们的controller中。我们建立一个controller并加载(或者注入)userService作为运行时依赖,我们把service的名字作为参数传递给controller 函数:

demoApp.controller("test8Controller", function($scope,userService){

/*文章信息*/

$scope.articles = [{

title : "爱飞像风",

userId : "zhangsan",

userName : "张三"

},{

title : "无法停止的雨",

userId : "lisi",

userName : "李四"

}];

$scope.showUserInfo = false;//显示作者详细信息开关

$scope.currentUser = {}; //当前选中的作者

$scope.getUserInfo = function(userId){

$scope.currentUser = userService.getUser(userId);

//调用 userService的getUser函数

$scope.showUserInfo = true;

setTimeout(function(){//定时器:隐藏作者详细信息

$scope.showUserInfo = false;

},3000);

}

});

我们的userService注入到我们的test8Controller后,我们就可以像使用其他服务(我们前面提到的$http服务)一样的使用userService了。

相关的HTML代码如下:

/* View HTML*/





{{article_.title}}











......


4.8依赖注入DI

通过依赖注入,ng想要推崇一种声明式的开发方式,即当我们需要使用某一模块或服务时,不需要关心此模块内部如何实现,只需声明一下就可以使用了。在多处使用只需进行多次声明,大大提高可复用性。

  比如我们的controller,在定义的时候用到一个$scope参数。

app.controller('testC',function($scope){});  

如果我们在此处还需操作其他的东西,比如与浏览器地址栏进行交互。我们只需再多添

一个参数$location进去:

app.controller('testC',function($scope,$location){});  

这样便可以通过$location来与地址栏进行交互了,我们仅仅是声明了一下,所需的其他代码,框架已经帮我们注入了。我们很明显的感觉到了这个函数已经不是常规意义上的javascript函数了,在常规的函数中,把形参换一个名字照样可以运行,但在此处若是把$scope换成别的名字,程序便不能运行了。因为这是已经定义好的服务名称。

这便是依赖注入机制。顺理成章的推断,我们可以自己定义模块和服务,然后在需要的地方进行声明,由框架来替我们注入。

来看下我们如何定义一个服务:

app.factory('tpls',function(){

    return ['tpl1','tpl2','tpl3','tpl4'];

});  

看上去相当简单,是因为我在这里仅仅是直接返回一个数组。在实际应用中,这里应该是需要向服务器发起一个请求,来获取到这些模板们。服务的定义方式有好几种,包括使用provider方法、使用factory方法,使用service方法。它们之间的区别暂且不关心。我们现在只要能创建一个服务出来就可以了。我使用了factory方法。一个需要注意的地方是,框架提供的服务名字都是由$开头的,所以我们自己定义的最好不要用$开头,防止发生命名冲突。

定义好一个服务后,我们就可以在控制器中声明使用了,如下:

app.controller('testC',function($scope,tpls){

    $scope.question = questionModel;

    $scope.nowTime = new Date().valueOf();

    $scope.templates = tpls; //赋值到$scope中

    $scope.addOption = function(){

        var o = {content:''};

        $scope.question.options.push(o);

    };

    $scope.delOption = function(index){

        $scope.question.options.splice(index,1);

    };

});  

此时,若在模板中书写如下代码,我们便可以获取到服务tpls所提供的数据了:

模板:


4.9路由(route)

在谈路由机制前有必要先提一下现在比较流行的单页面应用,就是所谓的single page APP。为了实现无刷新的视图切换,我们通常会用ajax请求从后台取数据,然后套上HTML模板渲染在页面上,然而ajax的一个致命缺点就是导致浏览器后退按钮失效,尽管我们可以在页面上放一个大大的返回按钮,让用户点击返回来导航,但总是无法避免用户习惯性的点后退。解决此问题的一个方法是使用hash,监听hashchange事件来进行视图切换,另一个方法是用HTML5的history API,通过pushState()记录操作历史,监听popstate事件来进行视图切换,也有人把这叫pjax技术。基本流程如下:

如此一来,便形成了通过地址栏进行导航的深度链接(deeplinking ),也就是我们所需要的路由机制。通过路由机制,一个单页应用的各个视图就可以很好的组织起来了。


4.9.1 ngRoute内容

  ng的路由机制是靠ngRoute提供的,通过hash和history两种方式实现了路由,可以检测浏览器是否支持history来灵活调用相应的方式。ng的路由(ngRoute)是一个单独的模块,包含以下内容:

l 服务$routeProvider用来定义一个路由表,即地址栏与视图模板的映射

l 服务$routeParams保存了地址栏中的参数,例如{id : 1, name : 'tom'}

l 服务$route完成路由匹配,并且提供路由相关的属性访问及事件,如访问当前路由对应的controller

l 指令ngView用来在主视图中指定加载子视图的区域

 以上内容再加上$location服务,我们就可以实现一个单页面应用了。下面来看一下具体如何使用这些内容。


4.9.2 ng的路由机制

  第一步:引入文件和依赖

  ngRoute模块包含在一个单独的文件中,所以第一步需要在页面上引入这个文件,如下:

        

 你可以把这段代码写在页面头部,这样就不必去请求它了。在实际项目中,你也可以将所有的模板内容集中在一个文件中,只加载一次,然后根据id来取用。

接下来我们来看另一个比较有用的配置:transclude,定义是否将当前元素的内容转移到模板中。看解释有点抽象,不过亲手试试就很清楚了,看下面的代码(例06):

app.directive('sayHello',function(){

return {

     restrict : 'E',

template : '

hello,
',

     replace : true,

      transclude : true

};

})         

指定了transclude为true,并且template修改了一下,加了一个标签,并在上面使用了ng-transclude指令,用来告诉指令把内容转移到的位置。那我们要转移的内容是什么呢?请看使用指令时的变化:

美女

内容是什么你也看到了哈~在运行的时候,美女将会被转移到标签中,原来此配置的作用就是——乾坤大挪移!看效果:

hello, 美女!

这个还是很有用的,因为你定义的指令不可能老是那么简单,只有一个空标签。当你需要对指令中的内容进行处理时,此参数便大有可用。


6.2.4指令的行为参数:compile和link

6.2.3中简单介绍了自定义一个指令的几个简单参数,restrict、template、templateUrl、replace、transclude,这几个理解起来相对容易很多,因为它们只涉及到了表现,而没有涉及行为。我们继续学习ng自定义指令的几个重量级参数:compile和link

l 理解compile和link

  不知大家有没有这样的感觉,自己定义指令的时候跟写jQuery插件有几分相似之处,都是先预先定义好页面结构及监听函数,然后在某个元素上调用一下,该元素便拥有了特殊的功能。区别在于,jQuery的侧重点是DOM操作,而ng的指令中除了可以进行DOM操作外,更注重的是数据和模板的绑定。jQuery插件在调用的时候才开始初始化,而ng指令在页面加载进来的时候就被编译服务($compile)初始化好了。

在指令定义对象中,有compile和link两个参数,它们是做什么的呢?从字面意义上看,编译、链接,貌似太抽象了点。其实可大有内涵,为了在自定义指令的时候能正确使用它们,现在有必要了解一下ng是如何编译指令的。

l 指令的解析流程详解

  我们知道ng框架会在页面载入完毕的时候,根据ng-app划定的作用域来调用$compile服务进行编译,这个$compile就像一个大总管一样,清点作用域内的DOM元素,看看哪些元素上使用了指令(如

),或者哪些元素本身就是个指令(如),或者使用了插值指令( {{}}也是一种指令,叫interpolation directive),$compile大总管会把清点好的财产做一个清单,然后根据这些指令的优先级(priority)排列一下,真是个细心的大总管哈~大总管还会根据指令中的配置参数(template,place,transclude等)转换DOM,让指令“初具人形”。

然后就开始按顺序执行各指令的compile函数,注意此处的compile可不是大总管$compile,人家带着$是土豪,此处执行的compile函数是我们指令中配置的,compile函数中可以访问到DOM节点并进行操作,其主要职责就是进行DOM转换,每个compile函数执行完后都会返回一个link函数,这些link函数会被大总管汇合一下组合成一个合体后的link函数,为了好理解,我们可以把它想象成葫芦小金刚,就像是进行了这样的处理。

//合体后的link函数

function AB(){

  A(); //子link函数

  B(); //子link函数

}  

接下来进入link阶段,合体后的link函数被执行。所谓的链接,就是把view和scope链接起来。链接成啥样呢?就是我们熟悉的数据绑定,通过在DOM上注册监听器来动态修改scope中的数据,或者是使用$watchs监听 scope中的变量来修改DOM,从而建立双向绑定。由此也可以断定,葫芦小金刚可以访问到scope和DOM节点。

不要忘了我们在定义指令中还配置着一个link参数呢,这么多link千万别搞混了。那这

个link函数是干嘛的呢,我们不是有葫芦小金刚了嘛?那我告诉你,其实它是一个小三。此话怎讲?compile函数执行后返回link函数,但若没有配置compile函数呢?葫芦小金刚自然就不存在了。

正房不在了,当然就轮到小三出马了,大总管$compile就把这里的link函数拿来执行。这就意味着,配置的link函数也可以访问到scope以及DOM节点。值得注意的是,compile函数通常是不会被配置的,因为我们定义一个指令的时候,大部分情况不会通过编程的方式进行DOM操作,而更多的是进行监听器的注册、数据的绑定。所以,小三名正言顺的被大总管宠爱。

听完了大总管、葫芦小金刚和小三的故事,你是不是对指令的解析过程比较清晰了呢?不过细细推敲,你可能还是会觉得情节生硬,有些细节似乎还是没有透彻的明白,所以还需要再理解下面的知识点:

l compile和link的区别

  其实在我看完官方文档后就一直有疑问,为什么监听器、数据绑定不能放在compile函数中,而偏偏要放在link函数中?为什么有了compile还需要link?就跟你质疑我编的故事一样,为什么最后小三被宠爱了?所以我们有必要探究一下,compile和link之间到底有什么区别。好,正房与小三的PK现在开始。

首先是性能。举个例子:



     


  •    

     


         

我们的观察目标是ng-repeat指令。假设一个前提是不存在link。大总管$compile在编译这段代码时,会查找到ng-repeat,然后执行它的compile函数,compile函数根据array的长度复制出n个
  • 标签。而复制出的
  • 节点中还有节点并且使用了ng-modle指令,所以compile还要扫描它并匹配指令,然后绑定监听器。每次循环都做如此多的工作。而更加糟糕的一点是,我们会在程序中向array中添加元素,此时页面上会实时更新DOM,每次有新元素进来,compile函数都把上面的步骤再走一遍,岂不是要累死了,这样性能必然不行。

    现在扔掉那个假设,在编译的时候compile就只管生成DOM的事,碰到需要绑定监听器的地方先存着,有几个存几个,最后把它们汇总成一个link函数,然后一并执行。这样就轻松多了,compile只需要执行一次,性能自然提升。

    另外一个区别是能力。

    尽管compile和link所做的事情差不多,但它们的能力范围还是不一样的。比如正房能管你的存款,小三就不能。小三能给你初恋的感觉,正房却不能。

    我们需要看一下compile函数和link函数的定义:

    function compile(tElement, tAttrs, transclude) { ... }

    function link(scope, iElement, iAttrs, controller) { ... }            

    这些参数都是通过依赖注入而得到的,可以按需声明使用。从名字也容易看出,两个函数各自的职责是什么,compile可以拿到transclude,允许你自己编程管理乾坤大挪移的行为。而link中可以拿到scope和controller,可以与scope进行数据绑定,与其他指令进行通信。两者虽然都可以拿到element,但是还是有区别的,看到各自的前缀了吧?compile拿到的是编译前的,是从template里拿过来的,而link拿到的是编译后的,已经与作用域建立了

    关联,这也正是link中可以进行数据绑定的原因。

      我暂时只能理解到这个程度了。实在不想理解这些知识的话,只要简单记住一个原则就行了:如果指令只进行DOM的修改,不进行数据绑定,那么配置在compile函数中,如果指令要进行数据绑定,那么配置在link函数中。
    6.2.5指令的划分作用域参数:scope

    我们在上面写了一个简单的,能够跟美女打招呼。但是看看人家ng内置的指令,都是这么用的:ng-model=”m”,ng-repeat=”a in array”,不单单是作为属性,还可以赋值给它,与作用域中的一个变量绑定好,内容就可以动态变化了。假如我们的sayHello可以这样用:美女,把要对美女说的话写在一个变量content中,然后只要在controller中修改content的值,页面就可以显示对美女说的不同的话。这样就灵活多了,不至于见了美女只会说一句hello,然后就没有然后。

    为了实现这样的功能,我们需要使用scope参数,下面来介绍一下。

    使用scope为指令划分作用域

      顾名思义,scope肯定是跟作用域有关的一个参数,它的作用是描述指令与父作用域的关系,这个父作用域是指什么呢?想象一下我们使用指令的场景,页面结构应该是这个样子:



        美女

      

    外层肯定会有一个controller,而在controller的定义中大体是这个样子:

    var app = angular.module('MyApp', [], function(){console.log('here')});

    app.controller('testC',function($scope){

    $scope.content = '今天天气真好!';

    }); 

    所谓sayHello的父作用域就是这个名叫testC的控制器所管辖的范围,指令与父作用域的关系可以有如下取值:

    取值
        

    说明

    false
        

    默认值。使用父作用域作为自己的作用域

    true
        

    新建一个作用域,该作用域继承父作用域

    javascript对象
        

    与父作用域隔离,并指定可以从父作用域访问的变量

    乍一看取值为false和true好像没什么区别,因为取值为true时会继承父作用域,即父作用域中的任何变量都可以访问到,效果跟直接使用父作用域差不多。但细细一想还是有区别的,有了自己的作用域后就可以在里面定义自己的东西,与跟父作用域混在一起是有本质上的区别。好比是父亲的钱你想花多少花多少,可你自己挣的钱父亲能花多少就不好说了。你若想看这两个作用域的区别,可以在link函数中打印出来看看,还记得link函数中可以访问到scope吧。

    最有用的还是取值为第三种,一个对象,可以用键值来显式的指明要从父作用域中使用属性的方式。当scope值为一个对象时,我们便建立了一个与父层隔离的作用域,不过也不是完全隔离,我们可以手工搭一座桥梁,并放行某些参数。我们要实现对美女说各种话就得靠这个。使用起来像这样:

    scope: {

            attributeName1: 'BINDING_STRATEGY',

            attributeName2: 'BINDING_STRATEGY',...

    }  

    键为属性名称,值为绑定策略。等等!啥叫绑定策略?最讨厌冒新名词却不解释的行为!别急,听我慢慢道来。

     

      先说属性名称吧,你是不是认为这个attributeName1就是父作用域中的某个变量名称?错!其实这个属性名称是指令自己的模板中要使用的一个名称,并不对应父作用域中的变量,稍后的例子中我们来说明。再来看绑定策略,它的取值按照如下的规则:

    符号
        

    说明
        

    举例

    @
        

    传递一个字符串作为属性的值
        

    str : ‘@string’

    =
        

    使用父作用域中的一个属性,绑定数据到指令的属性中
        

    name : ‘=username’

    &
        

    使用父作用域中的一个函数,可以在指令中调用
        

    getName : ‘&getUserName’

      总之就是用符号前缀来说明如何为指令传值。你肯定迫不及待要看例子了,我们结合例子看一下,小二,上栗子~

    举例说明

    我想要实现上面想像的跟美女多说点话的功能,即我们给sayHello指令加一个属性,通过给属性赋值来动态改变说话的内容 主要代码如下:

    app.controller('testC',function($scope){

       $scope.content = '今天天气真好!';

    });

    app.directive('sayHello',function(){

        return {

            restrict : 'E',

    template: '
    hello,,{{ cont }}
    ',

            replace : true,

            transclude : true,

            scope : {

     

                 cont : '=speak'

             }

        };

    });

    然后在模板中,我们如下使用指令:



        美女



    看看运行效果:

    美女今天天气真好!

      执行的流程是这样的:

      ① 指令被编译的时候会扫描到template中的{ {cont} },发现是一个表达式;

      ② 查找scope中的规则:通过speak与父作用域绑定,方式是传递父作用域中的属性;

      ③ speak与父作用域中的content属性绑定,找到它的值“今天天气真好!”;

      ④ 将content的值显示在模板中。

    这样我们说话的内容content就跟父作用域绑定到了一其,如果动态修改父作用域的content的值,页面上的内容就会跟着改变,正如你点击“换句话”所看到的一样。

      这个例子也太小儿科了吧!简单虽简单,但可以让我们理解清楚,为了检验你是不是真的明白了,可以思考一下如何修改指令定义,能让sayHello以如下两种方式使用:

    美女

    美女

      答案我就不说了,简单的很。下面有更重要的事情要做,我们说好了要写一个真正能用的东西来着。接下来就结合所学到的东西来写一个折叠菜单,即点击可展开,再点击一次就收缩回去的菜单。

    控制器及指令的代码如下(例07):

    app.controller('testC',function($scope){

            $scope.title = '个人简介';

        $scope.text = '大家好,我是一名前端工程师,我正在研究AngularJs,欢迎大家与我交流';

    });

        app.directive('expander',function(){

            return {

                restrict : 'E',

                templateUrl : 'expanderTemp.html',

                replace : true,

                transclude : true,

                scope : {

                    mytitle : '=etitle'

                },

                link : function(scope,element,attris){

                    scope.showText = false;

                    scope.toggleText = function(){

                        scope.showText = ! scope.showText;

                    }

                }

            };

        });

    HTML中的代码如下:

     





        {{text}}



      还是比较容易看懂的,我只做一点必要的解释。首先我们定义模板的时候使用了ng的一种定义方式
  • 你可能感兴趣的:(前端,angularjs)