Blog Logo

初识AngularJS

写于2015-12-07 15:51 阅读耗时7分钟 阅读量


什么是Angular?

从Angular的Github官网上可以看出创造者对该框架的简单定义:HTML enhanced for web apps.即Angular主要是针对Web应用,对HTML标签进行了增强。


使用Angular的好处?

Angular的使用在我看来和JavaEE的有些理念挺相似,什么依赖注入,控制反转,双向绑定等概念。那么使用Angular主要运用在哪些Web应用上呢?它给Web前端带来的好处又有哪些呢? 从Github上看使用Angular可以给开发者的带来的优势有以下几点:

  • 可以生成一个个模板引擎
  • UI表单的双向绑定
  • 实现依赖注入、控制反转
  • 解决异步回调

运用场合:经常CRUD的Web应用 AngularJS的语法及概念在这就不一一细说了,因为里面涉及到的东西太多,去官网看API和网上教程就行。想了解一个框架的本身,不在于可以用它实现什么,而在于它是怎么实现的。

下面的内容主要围绕"怎么实现"及"项目实战"开讲。


怎么实现

Angular核心原理

  • Angular启动过程分析
  • Provider与Injector执行过程
  • 指令的执行过程
  • $scope与双向数据绑定执行过程

Angular启动过程分析

源码解析Angular启动过程分析步骤: 1.自执行加载完整个angular.js暴露全局变量angular

(function(window,document){
    ...
    var angular = window.angular || (window.angular = {});
    ...
})(window,document);

2.是否存在angular对象

if (window.angular.bootstrap) {
    console.log('WARNING: Tried to load angular more than once.');
    return;
}

两种启动方式:

//自动启动:ng-app
<html ng-app='moduleName'>
    //code
</html>

//手动启动:
<script>
    angular.element(document).ready(function(){
        angular.bootstrap(document,['moduleName']);
    });
</script>

3.绑定jQuery

bindJQuery();

该方法判断用户是否自己导入jQuery,如果没有就导入jQlite:

function bindJQuery(){
    var jQuery = window.jQuery;
    if (jQuery && jQuery.fn.on) {
        jqLite = jQuery;
    }else{
        jqLite = JQLite;
    }
    angular.element = jqLite;
}

4.注入angularAPI

 publishExternalAPI(angular);

该方法给全局变量angular扩展方法及属性,构建模块加载器,注入内置provider注册器和ng指令:

function publishExternalAPI(angular) {
  extend(angular, {
    'bootstrap': bootstrap,
    'copy': copy,
    'extend': extend,
    'merge': merge,
    'equals': equals,
    'element': jqLite,
    'forEach': forEach,
    'injector': createInjector,
    'noop': noop,
    'bind': bind,
    'toJson': toJson,
    'fromJson': fromJson,
    'identity': identity,
    'isUndefined': isUndefined,
    'isDefined': isDefined,
    'isString': isString,
    'isFunction': isFunction,
    'isObject': isObject,
    'isNumber': isNumber,
    'isElement': isElement,
    'isArray': isArray,
    'version': version,
    'isDate': isDate,
    'lowercase': lowercase,
    'uppercase': uppercase,
    'callbacks': {counter: 0},
    'getTestability': getTestability,
    '$$minErr': minErr,
    '$$csp': csp,
    'reloadWithDebugInfo': reloadWithDebugInfo
  });
  angularModule = setupModuleLoader(window);
}

5.初始化

jqLite(document).ready(function() {
    angularInit(document, bootstrap);
});

该方法查找ng-app,如果找到执行bootstrap方法,没找到执行手动启动的bootstrap方法

function angularInit(element, bootstrap){
    if (appElement) { //ng-app
        config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
        bootstrap(appElement, module ? [module] : [], config);
  }
} 

bootstrap方法创建注册器,开始编译

function bootstrap(element, modules, config){
    ...
    var injector = createInjector(modules, config.strictDi);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
       function bootstrapApply(scope, element, compile, injector) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );
    ...
})

源码解析第5步之Provider与Injector执行过程

看源码的地方搜createInjector:

function createInjector(modulesToLoad, strictDi) {
    function provider(name, provider_) {
        ...
    }
    function factory(name, factoryFn, enforce) {
        return provider(name,{$get: ...});
    }
    function service(name, constructor) {
        return factory(name, ['$injector',...]);
    }
    function value(name, val) { 
        return factory(name, valueFn(val), false);
    }
    function constant(name, value) {
       ...
    }
    function decorator(serviceName, decorFn) {
        ...
    };
    return {
      invoke: invoke,
      instantiate: instantiate,
      get: getService,
      annotate: createInjector.$$annotate,
      has: function(name) {return ...}
    };
  }
});

Provider目的是让接口和实现分离。 进行注注入的有:provider、factory、service、constant、value 上面方法核心都是provider实现的,只是参数不同,从左到右灵活性越来越差。

接受注入的有:controller、config、module、run、filter等

注入的方式:

var app=angular.module('demo',[]);
// 推断型注入
app.controller('ctrl',function($scope){
    //code
});
//声明式注入
var ctrl=function(){//code};
ctrl.$inject=['$scope'];
app.controller('ctrl',ctrl);
//内联式注入
app.controller('ctrl',['$scope',function($scope){
    //code
}]);

内置Inject注册器有:

$provide.provider({
$anchorScroll: $AnchorScrollProvider,
$animate: $AnimateProvider,
$$animateQueue: $$CoreAnimateQueueProvider,
$$AnimateRunner: $$CoreAnimateRunnerProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
$document: $DocumentProvider,
$exceptionHandler: $ExceptionHandlerProvider,
$filter: $FilterProvider,
$interpolate: $InterpolateProvider,
$interval: $IntervalProvider,
$http: $HttpProvider,
$httpParamSerializer: $HttpParamSerializerProvider,
$httpParamSerializerJQLike:$HttpParamSerializerJQLikeProvider,
$httpBackend: $HttpBackendProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
$$q: $$QProvider,
$sce: $SceProvider,
$sceDelegate: $SceDelegateProvider,
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
$templateRequest: $TemplateRequestProvider,
$$testability: $$TestabilityProvider,
$timeout: $TimeoutProvider,
$window: $WindowProvider,
$$rAF: $$RAFProvider,
$$jqLite: $$jqLiteProvider,
$$HashMap: $$HashMapProvider,
$$cookieReader: $$CookieReaderProvider
});

源码解析第5步之指令的执行过程

看源码的地方搜compile:

function compile($compileNodes,transcludeFn,maxPriority,ignoreDirective,previousCompileContext){
    compile.$$addScopeClass($compileNodes);
    var compositeLinkFn=compileNodes(...);
    return function publicLinkFn(scope,cloneConnectFn,options){
        ...
    });
};

指令的compile与link:

var app=angular.module('demo',[]);
app.directive('Hello',function(){
    return{
        restrict:'EA',
        template:'<div>Hello</div>',
        replace:true,
        //一般不使用comile,使用link
        link:function(scope,element,attrs,controller){
            //el的获取设置attrs、scope或注册事件
        },
        compile:function(element,attrs,transclude){
            //code
            return function(scope,element,attrs,controller){
                //...
            }
        }
    };
});

compile指令作用是对指令的模板进行转换; link指令作用是在模型和视图之间建立关联,元素上的注册监听事件等;

内置指令有:

directive({
a: htmlAnchorDirective,
input: inputDirective,
textarea: inputDirective,
form: formDirective,
script: scriptDirective,
select: selectDirective,
style: styleDirective,
option: optionDirective,
ngBind: ngBindDirective,
ngBindHtml: ngBindHtmlDirective,
ngBindTemplate: ngBindTemplateDirective,
ngClass: ngClassDirective,
ngClassEven: ngClassEvenDirective,
ngClassOdd: ngClassOddDirective,
ngCloak: ngCloakDirective,
ngController: ngControllerDirective,
ngForm: ngFormDirective,
ngHide: ngHideDirective,
ngIf: ngIfDirective,
ngInclude: ngIncludeDirective,
ngInit: ngInitDirective,
ngNonBindable: ngNonBindableDirective,
ngPluralize: ngPluralizeDirective,
ngRepeat: ngRepeatDirective,
ngShow: ngShowDirective,
ngStyle: ngStyleDirective,
ngSwitch: ngSwitchDirective,
ngSwitchWhen: ngSwitchWhenDirective,
ngSwitchDefault: ngSwitchDefaultDirective,
ngOptions: ngOptionsDirective,
ngTransclude: ngTranscludeDirective,
ngModel: ngModelDirective,
ngList: ngListDirective,
ngChange: ngChangeDirective,
pattern: patternDirective,
ngPattern: patternDirective,
required: requiredDirective,
ngRequired: requiredDirective,
minlength: minlengthDirective,
ngMinlength: minlengthDirective,
maxlength: maxlengthDirective,
ngMaxlength: maxlengthDirective,
ngValue: ngValueDirective,
ngModelOptions: ngModelOptionsDirective
})

$scope与双向数据绑定执行过程

看源码的地方搜scope:

function Scope() {
  this.$id = nextUid();
  this.$$phase = ... = null;
  this.$root = this;
  this.$$destroyed = false;
  this.$$listeners = {};
  this.$$listenerCount = {};
  this.$$watchersCount = 0;
  this.$$isolateBindings = null;
}
Scope.prototype = {
    constructor: Scope,
    $new: function(isolate, parent){...},
    $watch: function(watchExp, listener,...) {},
    $watchGroup: function(watchExp, listener) {},
    $watchCollection: function(obj, listener) {},
    $digest: function() {},
    $destroy: function() {},
    $eval: function(expr, locals) {},
    $evalAsync: function(expr, locals) {},
    $$postDigest: function(fn) {},
    $apply: function(expr) {},
    $applyAsync: function(expr) {},
    $on: function(name, listener) {},
    $emit: function(name, args) {},
    $broadcast: function(name, args) {}
};
var $rootScope = new Scope();
...
compile.$$addScopeClass($compileNodes);
...

双向数据绑定:一维结构(表单)、二维结构(表格)、Tree型结构(建议不用双向绑定)


项目实战

在使用Angular开始应用的时候,需要明确一些步骤:

  1. 界面原型设计
  2. 搭建目录结构
  3. 选择UI框架编写UI
  4. 编写Controller
  5. 编写Service
  6. 编写Filter
  7. 测试

有空在细说每个步骤的内容,So Sorry!

Headshot of Maxi Ferreira

怀着敬畏之心,做好每一件事。