65.9K
CodeProject 正在变化。 阅读更多。
Home

AngularJS 用法总结

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.60/5 (7投票s)

2016年1月27日

CPOL

4分钟阅读

viewsIcon

16246

我对 AngularJS 的理解。

引言

介绍 AngularJS 的一些基础和重要知识。

背景

我使用 AngularJS 一段时间了,我之所以使用它,是因为我们的客户要求我们在我们的项目中使用这项技术。 从完全空白到对 AngularJS 有一些了解,我认为有必要写一些关于我对 AngularJS 理解的东西。 

1.双向数据绑定

有很多不同种类的 MV** 框架非常流行,AngularJS 就是其中之一(MVVM)。 

MV** 框架最重要的事情是它们可以分离 View 层和 Model 层,解耦你的代码。 MVC、MVP、MVVM 都有相同的目标,它们之间的区别在于如何将模型层与视图层关联起来。

数据如何在模型层和视图层之间流动是关键问题。 双向数据绑定是视图上的更改可以反映在模型层中,反之亦然。 那么 AngularJS 如何进行双向绑定以及它如何变成脏检查呢? 让我们从一个前端问题开始

html:
<input type="button" value="increase 1" id="J-increase" />
<span id="J-count"></span>

js:
<script>
    var bindDate = {
        count: 1,
        appy: function () {
            document.querySelector('#J-count').innerHTML = this.count;
        },
        increase: function () {
            var _this = this;
            document.querySelector('#J-increase').addEventListener('click', function () {
                _this.count++;
                appy();
            }, true);
        },
        initialize: function () {            
            this.appy();            
            this.increase();
        }
    };
    bindDate.initialize();
</script>

如上例,有两个过程
a. 视图层影响模型层:点击页面上的按钮会增加计数。
b. 模型层反映视图层:在 'count' 改变后,它会通过 'apply' 函数在视图上表示出来。

这是我们使用 jQuery 或其他库的方式,有三个明显的缺陷
a. 它涉及太多 DOM 操作。
b. 过程很复杂。
c. 代码耦合度太高,不易编写单元测试。

让我们看看 AngularJS 如何处理数据

第 1 步,添加 Watcher:当数据更改定义需要检查哪个对象时,需要先注册。 

$watch: function(watchExp, listener, objectEquality) {
    var scope = this,
        array = scope.$$watchers,
        watcher = {
            fn: listener,
            last: initWatchVal,
            get: get,
            exp: watchExp,
            eq: !!objectEquality
        };
    if (!array) {
        array = scope.$$watchers = [];
    }
    array.unshift(watcher);
}

第 2 步,脏检查:当指定范围下的数据发生变化时,需要循环注册的 $$watchers = [...]

 $digest: function() {
     while (length--) {
         watch = watchers[length];
         watch.fn(value, lastValue, scope);
     }
 }

这实现了双向数据绑定,你可以看到类似于自定义事件,并使用了观察者设计模式或发布者-订阅者模式。

2.依赖注入

让我们看看如果我们不使用 DI(依赖注入)如何解决对象之间的相互依赖关系。

function Car() {
    ...
}
Car.prototype = {
    run: function () {...}
}
 
function Benz() {
    var cat = new Car();
}
Benz.prototype = {
    ...
}

如上所示,Benz 类依赖于 Car 类,它通过使用内部 New 来解决依赖关系。 这样做显然不好,代码耦合度高,不利于维护。
我们知道 javascript 语言没有注释机制,让我们看看 AngularJS 如何实现它。

第 1 步,模拟注释

function annotate(fn, strictDi, name) {
    var $inject;
    if (!($inject = fn.$inject)) {
        $inject = [];
        $inject.push(name);
    }else if (isArray(fn)) {
        $inject = fn.slice(0, last);
    }
    return $inject;
}
createInjector.$$annotate = annotate;

第 2 步,创建注入对象

function createInjector(modulesToLoad, strictDi) {
    var providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
    instanceCache = {},
    instanceInjector = (instanceCache.$injector =
        createInternalInjector(instanceCache, function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
        }));
    return instanceInjector;
}

第 3 步,获取注入对象

function invoke(fn, self, locals, serviceName) {
    var args = [],
        $inject = annotate(fn, strictDi, serviceName);
 
    for (...) {
        key = $inject[i];        
        args.push(
          locals && locals.hasOwnProperty(key)
          ? locals[key]
          : getService(key, serviceName)
        );
    }
    if (isArray(fn)) {
        fn = fn[length];
    }      
    return fn.apply(self, args);
}

所以如果没有注解,那么模拟一个,这就是 PPK 说 angular 是“非前端开发人员为非前端开发人员开发的框架”的原因。

3.控制器通信

在实际的开发工作中,应用程序系统会变得非常庞大,一个应用程序不可能只有一个控制器,如何处理不同控制器之间的通信? 有两种主要方式

1. 事件机制:在 $rootScope 上注册事件,这样会在 $rootScope 上注册太多事件,这会引起很多问题。

//controller1
app.controller('controller1', function ($rootScope) {
    $rootScope.$on('eventType', function (arg) {
        ......
    })
})
 
// controller2
app.controller('controller2', function ($rootScope) {
    $rootScope.$emit('eventType',arg);
    or
    $rootScope.$broadcast('eventType',arg);
})

2. 使用服务:充分利用 angular DI 的特性,使用 angular 服务是一个单例特性,它将充当不同控制器之间的桥梁。

// register service
app.service('Message', function () {
    return {
        count: void(0);
    }
})
 
// controller1
app.controller('controller1', function ($scope, Message) {
    $scope.count = 1;
    Message.count = $scope.count;
});
 
// controller2
app.controller('controller2', function ($scope, Message) {
    $scope.num = Message.count;
});

4.服务特性

1. 单例:在 AngularJS 中,只有服务才能进行依赖注入,类似控制器、指令等都没有这个特性,angular 服务只提供一些基本的服务,它不会与特定的业务相关联,但控制器、指令都与特定的业务紧密相关,所以我们需要保持服务的唯一性。

2. 延迟新建:angular 会先生成服务的提供者,但它不会立即生成对应的服务,它只会在我们需要使用它时才会被实例化。 

3. 提供者的类别:provider()、factory、service、value、constant。 哪个提供者是底层实现,其他方式都是基于它。 请注意,所有这些服务最终都需要添加 $get 方法,因为所有特定的服务都是通过执行 $get 方法生成的。

5. 指令的实现

指令的编译器有两个阶段:编译、链接。 简而言之,编译阶段主要处理模板 DOM,在此阶段不涉及范围问题,即没有数据渲染。 例如,ng-Repeat 指令只是使用 compile 来修改模板,在执行 compile 后它将返回 link 函数,覆盖后续的 link 函数; 另一方面,link 主要负责数据渲染,它分为两个步骤。 这两个步骤的编译顺序是相反的,post-link 将先编译内部,然后编译外部。 这对于 directie 编译是安全的,因为指令可以嵌入指令,与此同时 link 将处理实际的 DOM,这将涉及 DOM 操作性能问题。

© . All rights reserved.