angularJS 入门1

来源:互联网 发布:范冰冰洪金宝关系 知乎 编辑:程序博客网 时间:2024/06/06 05:42
AngularJS  入门1



一. 声明式界面(模板、指令和视图)

先附实例代码:

  <!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="http://apps.bdimg.com/libs/angular.js/1.4.6/angular.min.js"></script> 
    <style type="text/css">
div.clock{
padding:10px;
margin: 0px auto;
width:400px;
background:#000;
color:#0f0;
}
    </style>
</head>
<body>

    <body ng-app="ezstuff">
        <!-- 我们自己定义的ez-clock指令通知编译器生成时钟widget-->
        <ez-directive></ez-directive>

<script>
angular.module("ezstuff", [])//创建模块ezstuff
.directive("ezDirective", function () {//在模块上注册指令ezClock的类工厂
    return {
        restrict: "E",
        replace: true,
        template: "<div class='clock'></div>",
        link: function (scope, element, attrs) {
            setInterval(function () {
                //获取当前时间
                var d = new Date();
                //element对应引用该指令的DOM对象的jqLite封装
                element.text(d.toString());
            }, 1000);
        }
    }
})

</script>

</body>
</html>


这里需要留意:
1.指令的使用:
 指令可以用来生成我们需要的组件。它提供了在静态化的HTML文件中,植入动态行为的能力
 注: 可以使用 .directive 函数来添加自定义的指令。

     要调用自定义指令,HTMl 元素上需要添加自定义指令名。

     使用驼峰法来命名一个指令, runoobDirective, 但在使用它时需要以 - 分割, runoob-directive:

     留意上方的自定义指令的命名方式与对应在HTML标签中的使用。错误的命名方式可能会造成指令组件无法正常运行。




二.数据绑定

1.数据变化的传播

数据绑定有两个方向:

  • 数据 → 界面:我们使用scope对象的$watch()方法监听数据的变化,来更新界面。
  • 界面 → 数据:我们在界面的DOM对象上监听变化事件,来更新数据,并通过$apply()方法传播变化。
  • $watch()

每个scope对象都维护了一个私有的监听队列,每次当我们在scope上执行一次$watch方法,就相当于 向这个监听队列里塞入一个监听函数。

  • $apply()

为了捕捉对数据的修改,AngularJS要求开发者使用scope对象的$apply方法对数据进行修改, $apply方法内部会自动地调用监听队列里的监听函数

注:对数据的变化监听,总是需要通过$apply方法的调用而被激活,如果 AngularJS没有获得一个机会来调用$apply,就需要你手工的调用它。


下面附实例代码:
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>数据绑定</title>
    <script src="angular-1.2.19/angular.min.js"></script>
</head>
<body ng-app="ezstuff" ng-init="sb={name:'somebody',gender:'male',age:28}">
    <ez-directive data="sb"></ez-directive>
    <div ezs-directive data="sb"></div>
<script>
    angular.module("ezstuff", []).directive("ezDirective", function () {
        return {
            restrict: 'E',  //元素指令
            template: "<ul class='nceditor'></ul>",
            replace: true,
            link: function (scope, element, attrs) {
                var model = attrs.data;

                element.append("<li>name:<input type='text' field='name'></li>")
                .append("<li>gender:<input type='text' field='gender'></li>")
                .append("<li>age:<input type='text' field='age'></li>");

                element.find("input").on("keyup", function (ev) {
                    var field = ev.target.getAttribute("field");
                    scope[model][field] = ev.target.value;
                    scope.$apply("");
                });
            }
        }
    })
    .directive("ezsDirective", function () {
        return {
            restrict: "A",  //属性指令
            link: function (scope, element, attrs) {
                var model = attrs.data;
                scope.$watch(model, function (nv) {
                    var cnt = JSON.stringify(nv, null, "  ");
                    element.html("<pre>"+cnt+"</pre>");
                }, true);
            }
        }
    });

</script>
</body>
</html>


三. 依赖注入

事实上,AngularJS把所有的功能组件都以依赖注入的方式组织起来:
 

在依赖注入的模式下,所有的组件必须通过容器才能相互访问,这导致了在AngularJS中, 你必须通过一个中介才能获得某个组件的实例对象:
  1. var injector= angular.injector(['ng']);
  2. injector.invokefunction$http){
  3. //do sth. with $http
  4. });

这个中介,就是依赖注入模式中的容器,在AngularJS中,被称为:注入器

实例参考:
angular.element(document).ready(function(){
angular.injector(["ng"]).invoke(function($http){
//将$http对象转成字符串显示出来
var e = document.querySelector("#logger");  //<div id="logger"></div>
angular.element(e).text($http.toString());
});
});


1. 注入器/injector

注入器是AngularJS框架实现和应用开发的关键,这是一个DI/IoC容器的实现。

AngularJS将功能分成了不同类型的组件分别实现,这些组件有一个统称 - 供给者/provider, 下图中列出了AngularJS几个常用的内置服务:
 
 
 

 

AngularJS的组件之间不可以互相直接调用,一个组件必须通过注入器才 可以调用另一个组件。这样的好处是组件之间相互解耦,对象的整个生命周期的管理 甩给了注入器。

注入器实现了两个重要的功能

    1) 集中存储所有provider的配方

配方其实就是:名称+类构造函数。AngularJS启动时,这些provider首先使用其配方在注入器 内注册。比如,http请求服务组件封装在$httpProvider类内,它通过"$http"这个名字在注入 器内注册。 

    2)  按需提供功能组件的实例

其他组件,比如一个用户的控制器,如果需要使用http功能,使用"$http"这个名字 向注入器请求,就可以获得一个http服务实例了。



2. 注册服务组件

从injector的角度看,组件就是一个功能提供者,因此被称为供给者/Provider。 在AngularJS中,provider以javascript类(构造函数)的形式封装。
 

Provider类要求提供一个$get函数(类工厂),injector通过调用该函数, 就可以获得服务组件的实例。

名称和类函数的组合信息,被称为配方。injector中维护一个集中的配方库, 用来按需创建不同的组件。这个配方库,其实就是一个Hash对象,key就是服务名称,value 就是类定义。

在→_→的示例中,我们定义了一个简单的服务类,这个服务类的实例就是一个字符串:“hello,world!”。 我们使用"ezHello"作为其服务名在注入器里注册,并通过注入器将这个实例显示出来。


实例代码:

//在ezstuff模块上登记一个服务ezHello
angular.module("ezstuff",[])
.provider("ezHello",function(){ //通过 provider 供应者创建 ezHello 服务
//$get方法是一个类工厂,返回服务的实例
this.$get = function(){  //给该服务添加一个$get 工厂服务实例对象 用来返回一个指定的字符串
return "hello,world!";
};
});

angular.element(document).ready(function(){
        //通过 injector/注入器 容器对象 引入ng 及 自定义的ezstuff 模块
angular.injector(["ng","ezstuff"]).invoke(function(ezHello){ //调用自定义模块中的 ezHello 服务
//将ezHello实例对象转成字符串显示出来
var e = document.querySelector("#logger");
angular.element(e).text(ezHello);  //输出该服务 返回的信息
});
});

3.获得注入器对象


要使用AngularJS的功能,必须首先获取注入器。有两种方法取得注入器。

  • 创建一个新的注入器

可以使用angular.injector()创建一个新的注入器:

  1. angular.injectormodules [strictDi]);
  • 获取已经创建的注入器

如果AngularJS框架已经启动,那么可以使用DOM对象的injector()方法获 得已经创建的注入器:

  1. var element = angular.elementdom_element);
  2. var injector = element.injector();

通过注入器调用API

注入器有两个方法可供进行API调用:invoke()和get()。

  • invoke()

使用注入器的invoke()方法,可以直接调用一个用户自定义的函数体,并通过函数参数 注入所依赖的服务对象,这是AngularJS推荐和惯例的用法

  1. angular.injector(['ng'])
  2. .invokefunction$http){
  3. //do sth. with $http
  4. });
  • get()

也可以使用注入器的get()方法,获得指定名称的服务实例:

  1. var my$http = angular.injector(['ng']).get'$http');
  2. //do sth. with my$http

→_→的示例这次使用了get()方法直接获取一个服务实例


//在ezstuff模块上登记一个服务ezHello
angular.module("ezstuff",[])
.provider("ezHello",function(){
//$get方法是一个类工厂,返回服务的实例
this.$get = function(){
return "hello,world!";
};
});

angular.element(document).ready(function(){
//直接通过注入器获取ezHello实例对象
var myHello = angular.injector(["ng","ezstuff"]).get("ezHello");
//将ezHello实例对象转成字符串显示出来
var e = document.querySelector("#logger");
angular.element(e).text(myHello);
});

4. 注入的方式和原理

有两种方法告知注入器需要注入的服务对象:参数名注入和依赖数组注入。

  • 参数名注入

AngularJS在执行invoke()函数时,将待注入函数定义转化为字符串,通过 正则表达式检查其参数表,从而发现并注入所所依赖的服务对象:

  1. //myfunc通过参数表声明这个函数依赖于"$http"服务
  2. var myfunc = function$http){
  3. //do sth. with $http
  4. };
  5. injector.invokemyfunc);//myfunc的定义将被转化为字符串进行参数名检查

这样有一个问题就是当我们对javascript代码进行压缩处理时,$http可能会被 变更成其他名称,这将导致注入失败。

  • 依赖数组注入

AngularJS采用依赖项数组的方法解决代码压缩混淆产生的问题。这时传入invoke()的 是一个数组,数组的最后一项是实际要执行的函数,其他项则指明需要向该函数注入 的服务名称。注入器将按照数组中的顺序,依次向函数注入依赖对象。

采用这种方法,待注入函数的参数表的名称就无关紧要了:

  1. //myfunc依赖于"$http"和"$compile"服务
  2. var myfunc = ["$http""$compile"functionp1p2){
  3. //do sth. with p1($http),p2($compile)
  4. }];
  5. injector.invokemyfunc);

→_→的实例这次采用依赖数组的方法注入了ezHello服务实例,可以改改参数名称 看有没有影响结果?


实例代码:

//在ezstuff模块上登记一个服务ezHello
angular.module("ezstuff",[])
.provider("ezHello",function(){
//$get方法是一个类工厂,返回服务的实例
this.$get = function(){
return "hello,world!";
};
});

angular.element(document).ready(function(){
angular.injector(["ng","ezstuff"]).invoke(["ezHello",function(hhh){ //这里的参数名可自定义
//将ezHello实例对象转成字符串显示出来
var e = document.querySelector("#logger");
angular.element(e).text(hhh);
}]);
});


四. AngularJS 的启动引导过程

当你在HTML文件中引入angular.min.js时,AngularJS只是建立了一个全局的 angular对象,这个对象有一些方法可供开发者调用,但应用的框架还没有建立。

只有通过启动引导,AngularJS框架才开始将那些组件拼接在一起,应用才真正 开始运转。
试着给html元素增加一个ng-app指令, 让它运行起来。


1. 创建注入器

  在自动启动引导的场景下,可以给ng-app赋值以指定一个需要载入的模块,比如:
  1. ng-app = "ezstuff"

在手动启动引导的场景下,通过bootstrap方法的第二个参数指定需要载入的模块,比如:

  1. angular.bootstrapdocument,["ezstuff"]);

INSIDE:无论自动启动还是手工启动,最终都是调用angular对象上的injector()方法创建了一个 注入器,然后把这个注入器存入了根对象的data里:

  1. var injector = angular.injector(["ng""ezstuff"]);
  2. angular.elementdocument).data"$injector"injector);

2. 创建根作用域

scope对象是AngularJS实现数据绑定的重要服务,所以,在引导启动建立了注入器之后, AngularJS马上在应用的根节点上创建一个根作用域:$rootScope对象。

如果是自动引导启动,那么ng-app所在的DOM节点对应着根作用域。如果是手工引导启动, 那么在bootstrap方法中指定的第一个参数就对应着根作用域。

无论哪一种情况,一旦$rootScope对象创建成功,AngularJS就将这个对象存储到根节点 的data中,我们可以使用如下的方法查看这个对象:

  1. angular.elementapproot).data"$rootScope");

你可以摆弄一下代码,看看$rootScope到底是什么东西。


代码实例:
//模拟引导启动过程
angular.element(document).ready(function(){
//第一步:创建注入器并保存到根对象的data中
var injector = angular.injector(["ng","ezstuff"]);
angular.element(document).data("$injector",injector);
//第二步:创建根作用域并保存到根对象的data中
var rootScope = injector.get("$rootScope");
angular.element(document).data("$rootScope",rootScope);
})
;
angular.module("ezstuff",[])
.directive("ezDuang",function(){
return {
restrict : "E",
template : "<img src='http://ww4.sinaimg.cn/bmiddle/757eb2ffjw1eptcr4qobjg209205dthh.gif'>"
};
});


3.编译器/$compile

编译器$compile是一个AngularJS的内置服务,它负责遍历DOM树来查找匹配指令, 并调用指令的实现代码进行处理。

HTML编译包括3个步骤:

  • 匹配指令

$compile遍历DOM树,如果发现有元素匹配了某个指令,那么这个指令将被加入 该DOM元素的指令列表中。一个DOM元素可能匹配多个指令。

  • 执行指令的编译函数

当一个DOM元素的所有指令都找齐后,编译器根据指令的优先级/priority指令进行排序。 每个指令的compile函数被依次执行。每个compile执行的结果产生一个link函数,这些 link函数合并成一个复合link函数。

  • 执行生成的链接函数

$compile通过执行指令的link函数,将模板和scope链接起来。结果就是一个DOM视图和scope对象模型 之间的动态数据绑定。

为何将编译和连接两个步骤分开?

简单说,当数据模型的变化会导致DOM结构变化时,指令就需要分别定义compile()函数和link函数。 例如,ng-repeat指令需要为数据集合中的每个成员复制DOM元素。将编译和链接过程分开可以有效 地提高性能,因为DOM的复制放在compile()里,仅需要执行一次,但链接则发生在每个生成的DOM元素 上,所以指令的link()函数会执行多次。

指令很少需要compile函数,因为大多数指令考虑的是作用于特定的DOM元素实例,而不是改变DOM 的结构。所以link函数更常用。


4.指令/directive

笼统地说,指令是DOM元素(例如属性、元素、CSS类等)上的标记符,用来告诉AngularJS的HTML编译器 ($compile服务)将特定的行为绑定到DOM元素,或者改变DOM元素。

指令可以放置在元素名、属性、CSS类名称及备注中。下面是一些等效的触发"ng-bind"指令的写法:

  1. <span ng-bind="exp"></span>
  2. <span class="ng-bind: exp;"></span>
  3. <ng-bind></ng-bind>
  4. <!-- directive: ng-bind exp -->

指令的实现本质上就是一个类工厂,它返回一个指令定义对象,编译器根据这个指令定义对象进行操作。


指令的规范化

AngularJS在进行匹配检测之前,首先对HTML元素的标签和属性名转化成规范的驼峰式字符串

  1. 去除名称前缀的x-和data-
  2. 以: , - 或 _ 为分割符,将字符串切分成单词,除第一个单词外,其余单词首字母大写
  3. 重新拼接各单词

例如,下面的写法都等效地匹配ngBind指令:

  1. <span ng-bind="name"></span> <br/>
  2. <span ng:bind="name"></span> <br/>
  3. <span ng_bind="name"></span> <br/>
  4. <span data-ng-bind="name"></span> <br/>
  5. <span x-ng-bind="name"></span> <br/>

所以,在前面的课程中,我们在HTML中使用的ez-duang指令,将被规范为ezDuang, 编译器使用这个规范化的名称与注册的指令进行匹配。



引导启动过程实例代码:

//模拟引导启动过程
angular.element(document).ready(function(){
//第一步:创建注入器并保存到根对象的data中
var injector = angular.injector(["ng","ezstuff"]);
angular.element(document).data("$injector",injector);
//第二步:创建根作用域并保存到根对象的data中
var rootScope = injector.get("$rootScope");
angular.element(document).data("$rootScope",rootScope);
//第三步:编译DOM树
var compile = injector.get("$compile")
compile(document)(rootScope);
})
;
angular.module("ezstuff",[])
.directive("ezDuang",function(){
return {
restrict : "E",
template : "<img style='display:none' src='http://ww4.sinaimg.cn/bmiddle/757eb2ffjw1eptcr4qobjg209205dthh.gif'>"
};
});
0 0
原创粉丝点击