zoukankan      html  css  js  c++  java
  • Angularjs 源码

    /**
     * @license AngularJS v1.3.0-beta.15
     * (c) 2010-2014 Google, Inc. http://angularjs.org
    function toKeyValue(obj) {
            var parts = [];
            forEach(obj, function(value, key) {
                if (isArray(value)) {
                    forEach(value, function(arrayValue) {
                        parts.push(encodeUriQuery(key, true) +
                        (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true)));
                    });
                } else {
                    parts.push(encodeUriQuery(key, true) +
                    (value === true ? '' : '=' + encodeUriQuery(value, true)));
                }
            });
            return parts.length ? parts.join('&') : '';
        }
    
    
        /**
         * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
         * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
         * segments:
         *    segment       = *pchar
         *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
         *    pct-encoded   = "%" HEXDIG HEXDIG
         *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
         *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
         *                     / "*" / "+" / "," / ";" / "="
         */
        function encodeUriSegment(val) {
            return encodeUriQuery(val, true).
                replace(/%26/gi, '&').
                replace(/%3D/gi, '=').
                replace(/%2B/gi, '+');
        }
    
    
        /**
         * This method is intended for encoding *key* or *value* parts of query component. We need a custom
         * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be
         * encoded per http://tools.ietf.org/html/rfc3986:
         *    query       = *( pchar / "/" / "?" )
         *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"//pchar为字符串的分割符,对应下面的三种情况
         *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
         *    pct-encoded   = "%" HEXDIG HEXDIG
         *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"//
         *                     / "*" / "+" / "," / ";" / "="
         */
        function encodeUriQuery(val, pctEncodeSpaces) {
            return encodeURIComponent(val).
                replace(/%40/gi, '@').
                replace(/%3A/gi, ':').
                replace(/%24/g, '$').
                replace(/%2C/gi, ',').
                replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
        }
    
        //定义angular属性的前缀,如ng-,data-ng,ng:-,x-ng-
        var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
    
        //参数示例:ng-repeat,repeat
        function getNgAttribute(element, ngAttr) {//获取angular的*ng-属性
            var attr, i, ii = ngAttrPrefixes.length, j, jj;
            element = jqLite(element);//获取传进来的element元素
            for (i=0; i<ii; ++i) {
                attr = ngAttrPrefixes[i] + ngAttr;//显示的示例代码,如:ng-app,ng-repeat
                if (isString(attr = element.attr(attr))) {//如果找到则立即返回,找到的内容
                    return attr;
                }
            }
            return null;//如果找不到则返回为空
        }
    
        /**
         * @ngdoc directive
         * @name ngApp
         * @module ng
         *
         * @element ANY //可以是任意内容
         * @param {angular.Module} ngApp an optional application//angular.module,是一个可选的
         *   {@link angular.module module} name to load.//按照模块的名称载入的,如angular.module('myModule',["book-store","book"]);,其中,myModule就是模块名
         * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be
         *   created in "strict-di" mode. This means that the application will fail to invoke functions which
         *   do not use explicit function annotation (and are thus unsuitable for minification), as described
         *   in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in
         *   tracking down the root of these bugs.
         *
         * @description
         *
         * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive
         * 用这个指令可以自动的去启动angular的应用程序,
         * designates the **root element** of the application and is typically placed near the root element
         * ngApp作为angular应用程序的根元素,它是一个典型的点位符,如<html><body><head><div> etc.
         * of the page - e.g. on the `<body>` or `<html>` tags.
         *
         * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp
         * 在每一个html文档中,仅只有一个angular应用程序可以被自动启动
         * found in the document will be used to define the root element to auto-bootstrap as an
         * 在文档中发现的第一个ngApp被视为可以自动启动的应用程序的根元素
         * application. To run multiple applications in an HTML document you must manually bootstrap them using
         * 在一个html文档中运行多个应用程序,则需要手动的启动他们。
         * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
         *angular应用程序不能够互相嵌套
         * You can specify an **AngularJS module** to be used as the root module for the application.  This
         * 你可以指定一个angular的模板,作为应用程序的顶级模块
         * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
         * should contain the application code needed or have dependencies on other modules that will
         * contain the code. See {@link angular.module} for more information.
         *当应用程序启动并且包含有它所依赖的其他模块代码时,上面所指定的模块可以被注入到应用程序当中
         * In the example below if the `ngApp` directive were not placed on the `html` element then the
         * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
         * would not be resolved to `3`.
         *在下面的示例中,如果ngApp在Html页面中找不到或不存在,则这个Html文档不能够被正常的编译,AppController这个控制器也不能够被正常的实例化
         * 解析出来的{{a+b}}的结果也不会为3
         * `ngApp` is the easiest, and most common, way to bootstrap an application.
         *ngApp是用来 启动一个应用程序最简单,最常用 的指令,
         <example module="ngAppDemo">
         <file name="index.html">
         <div ng-controller="ngAppDemoController">
         I can add: {{a}} + {{b}} =  {{ a+b }}
         </div>
         </file>
         <file name="script.js">
         angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) {
         $scope.a = 1;
         $scope.b = 2;
       });
         </file>
         </example>
         *
         * Using `ngStrictDi`, you would see something like this://启动严格模式
         *
         <example ng-app-included="true">
         <file name="index.html">
         <div ng-app="ngAppStrictDemo" ng-strict-di>//启动严格模式
         <div ng-controller="GoodController1">
         I can add: {{a}} + {{b}} =  {{ a+b }}
    
         <p>This renders because the controller does not fail to
         instantiate, by using explicit annotation style (see
         script.js for details)
         </p>
         </div>
    
         <div ng-controller="GoodController2">
         Name: <input ng-model="name"><br />
         Hello, {{name}}!
    
         <p>This renders because the controller does not fail to
         instantiate, by using explicit annotation style
         (see script.js for details)
         </p>
         </div>
    
         <div ng-controller="BadController">
         I can add: {{a}} + {{b}} =  {{ a+b }}
    
         <p>The controller could not be instantiated, due to relying
         on automatic function annotations (which are disabled in
         strict mode). As such, the content of this section is not
         interpolated, and there should be an error in your web console.
         </p>
         </div>
         </div>
         </file>
         <file name="script.js">
         angular.module('ngAppStrictDemo', [])
         // BadController will fail to instantiate, due to relying on automatic function annotation,
         // rather than an explicit annotation
         .controller('BadController', function($scope) {
           $scope.a = 1;
           $scope.b = 2;
         })
         // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated,
         // due to using explicit annotations using the array style and $inject property, respectively.
         .controller('GoodController1', ['$scope', function($scope) {
           $scope.a = 1;
           $scope.b = 2;
         }])
         .controller('GoodController2', GoodController2);
         function GoodController2($scope) {
           $scope.name = "World";
         }
         GoodController2.$inject = ['$scope'];
         </file>
         <file name="style.css">
         div[ng-controller] {
           margin-bottom: 1em;
           -webkit-border-radius: 4px;
           border-radius: 4px;
           border: 1px solid;
           padding: .5em;
       }
         div[ng-controller^=Good] {
           border-color: #d6e9c6;
           background-color: #dff0d8;
           color: #3c763d;
       }
         div[ng-controller^=Bad] {
           border-color: #ebccd1;
           background-color: #f2dede;
           color: #a94442;
           margin-bottom: 0;
       }
         </file>
         </example>
         */
        function angularInit(element, bootstrap) {//type:any,[]
            var elements = [element],
                appElement,
                module,
                config = {},
                names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],//angular所能识别的ngApp指令
                options = {
                    'boolean': ['strict-di']
                },
                NG_APP_CLASS_REGEXP = /sng[:-]app(:s*([wd_]+);?)?s/;//*ng-指令的正则表达式,如:ng-app:red
    
            function append(element) {//内部函数
                element && elements.push(element);
            }
    
            forEach(names, function(name) {
                names[name] = true;
                append(document.getElementById(name));
                name = name.replace(':', '\:');//示例内容,如:ng:app转换为,ng:app
                /*
                * //获取<div>中的所有图像(和getElementsByTaName("img")一样)
                 var images = document.getElementById("myDiv").querySelectorAll("img");
    
                 //获取所有包含“selected”类的元素
                 var selected = document.querySelectorall(".selected");
    
                 //获取所有<p>元素中的<strong>元素
                 var strongs = document.querySelectorAll("p strong");
                * */
                if (element.querySelectorAll) {//只要调用querySelectorAll()都会返回一个StaticNodeList对象不管匹配的元素有几个;如果没有匹配,那么StaticNodeList为空。
                    forEach(element.querySelectorAll('.' + name), append);//.ng-App
                    forEach(element.querySelectorAll('.' + name + '\:'), append);//.ng:App
                    forEach(element.querySelectorAll('[' + name + ']'), append);//[ng-App]
                }
            });
    
            forEach(elements, function(element) {
                if (!appElement) {
                    var className = ' ' + element.className + ' ';
                    var match = NG_APP_CLASS_REGEXP.exec(className);//判断是否匹配*ng-指令的正则表达式,如ng-app:red
                    if (match) {
                        appElement = element;
                        module = (match[2] || '').replace(/s+/g, ',');
                    } else {
                        forEach(element.attributes, function(attr) {
                            if (!appElement && names[attr.name]) {
                                appElement = element;
                                module = attr.value;
                            }
                        });
                    }
                }
            });
            if (appElement) {
                config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
                bootstrap(appElement, module ? [module] : [], config);//示例代码,如:angular.module(document,[],cofnig);//是一个回调函数
            }
        }
    
        /**
         * @ngdoc function
         * @name angular.bootstrap
         * @module ng
         * @description
         * Use this function to manually start up angular application.
         *用这个函数手动启动angular应用程序
         * See: {@link guide/bootstrap Bootstrap}
         *
         * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually.
         * They must use {@link ng.directive:ngApp ngApp}.
         *注意:protractor是基于end-toend来测试的,因此不能够使用这个函数来手动启动应用程序
         * Angular will detect if it has been loaded into the browser more than once and only allow the
         * first loaded script to be bootstrapped and will report a warning to the browser console for
         * each of the subsequent scripts. This prevents strange results in applications, where otherwise
         * multiple instances of Angular try to work on the DOM.
         *
         * ```html
         * <!doctype html>
         * <html>
         * <body>
         * <div ng-controller="WelcomeController">
         *   {{greeting}}
         * </div>
         *
         * <script src="angular.js"></script>
         * <script>
         *   var app = angular.module('demo', [])
         *   .controller('WelcomeController', function($scope) {
     *       $scope.greeting = 'Welcome!';
     *   });
         *   angular.bootstrap(document, ['demo']);//document,[],config
         * </script>
         * </body>
         * </html>
         * ```
         *
         * @param {DOMElement} element DOM element which is the root of angular application.//dom元素,angular应用程序的根元素
         * @param {Array<String|Function|Array>=} modules an array of modules to load into the application.//被加载到应用程序中的依赖模块
         *     Each item in the array should be the name of a predefined module or a (DI annotated)//在数组中的每一项,应该被预先定义或注入
         *     function that will be invoked by the injector as a run block.
         *     See: {@link angular.module modules}
         * @param {Object=} config an object for defining configuration options for the application. The
         *     following keys are supported:
         *
         *     - `strictDi`: disable automatic function annotation for the application. This is meant to
         *       assist in finding bugs which break minified code.
         *
         * @returns {auto.$injector} Returns the newly created injector for this app.
         * 返回新这个应用程序新创建的注入器
         */
        function bootstrap(element, modules, config) {
            if (!isObject(config)) config = {};
            var defaultConfig = {
                strictDi: false
            };
            /**
             * function extend(dst) {//这里实现config继承
                  var h = dst.$$hashKey;
                  forEach(arguments, function(obj) {
                    if (obj !== dst) {
                      forEach(obj, function(value, key) {
                        dst[key] = value;
                      });
                    }
                  });
    
                  setHashKey(dst,h);
                  return dst;
                }
             */
            config = extend(defaultConfig, config);//这里实现config继承
            var doBootstrap = function() {//返回新创建的注入器
                element = jqLite(element);
    
                if (element.injector()) {//App Already Bootstrapped with this Element
                    var tag = (element[0] === document) ? 'document' : startingTag(element);
                    throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
                }
    
                modules = modules || [];//element所依赖的模块数组
                modules.unshift(['$provide', function($provide) {
                    $provide.value('$rootElement', element);//注入根元素,如:document,html,ng-app
                }]);
                modules.unshift('ng');//添加anggular的前缀,ng
                var injector = createInjector(modules, config.strictDi);//为模块数组创建一个注入器
                injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',//用新创建的注入器注入默认的对象,如:$rootScope,$rootEolement,$compile,$injector
                        function(scope, element, compile, injector) {//这个函数的参数与injector.invoke里面的第一个参数所对应
                            scope.$apply(function() {
                                element.data('$injector', injector);
                                compile(element)(scope);
                            });
                        }]
                );
                return injector;//返回新创建的注入器
            };
    
            var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;//angular延迟启动
    
            if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
                return doBootstrap();
            }
    
            window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
            angular.resumeBootstrap = function(extraModules) {//重新激活angular应用程序
                forEach(extraModules, function(module) {
                    modules.push(module);
                });
                doBootstrap();
            };
        }
    
        var SNAKE_CASE_REGEXP = /[A-Z]/g;
        function snake_case(name, separator) {
            separator = separator || '_';
            return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
                return (pos ? separator : '') + letter.toLowerCase();
            });
        }
    
        function bindJQuery() {
            var originalCleanData;
            // bind to jQuery if present;
            jQuery = window.jQuery;
            // Use jQuery if it exists with proper functionality, otherwise default to us.
            // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support.
            if (jQuery && jQuery.fn.on) {//判断是否已经引入其他版本的JQuery,如果是则angular使用的是外部的jQuery
                jqLite = jQuery;
                extend(jQuery.fn, {
                    scope: JQLitePrototype.scope,
                    isolateScope: JQLitePrototype.isolateScope,
                    controller: JQLitePrototype.controller,
                    injector: JQLitePrototype.injector,
                    inheritedData: JQLitePrototype.inheritedData
                });
    
                originalCleanData = jQuery.cleanData;
                // Prevent double-proxying.
                originalCleanData = originalCleanData.$$original || originalCleanData;
    
                // All nodes removed from the DOM via various jQuery APIs like .remove()
                // are passed through jQuery.cleanData. Monkey-patch this method to fire
                // the $destroy event on all removed nodes.
                jQuery.cleanData = function(elems) {
                    for (var i = 0, elem; (elem = elems[i]) != null; i++) {
                        jQuery(elem).triggerHandler('$destroy');
                    }
                    originalCleanData(elems);
                };
                jQuery.cleanData.$$original = originalCleanData;
            } else {//如果没有引入外部的jQuery,则angular使用其内容的jQlite
                jqLite = JQLite;
            }
    
            angular.element = jqLite;//把jqLit绑定到angular中
        }
    
        /**
         * throw error if the argument is falsy.
         */
        function assertArg(arg, name, reason) {
            if (!arg) {
                throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required"));
            }
            return arg;
        }
    
        function assertArgFn(arg, name, acceptArrayAnnotation) {
            if (acceptArrayAnnotation && isArray(arg)) {
                arg = arg[arg.length - 1];
            }
    
            assertArg(isFunction(arg), name, 'not a function, got ' +
            (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
            return arg;
        }
    
        /**
         * throw error if the name given is hasOwnProperty
         * @param  {String} name    the name to test
         * @param  {String} context the context in which the name is used, such as module or directive
         */
        function assertNotHasOwnProperty(name, context) {
            if (name === 'hasOwnProperty') {
                throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context);
            }
        }
    
        /**
         * Return the value accessible from the object by path. Any undefined traversals are ignored
         * @param {Object} obj starting object
         * @param {String} path path to traverse
         * @param {boolean} [bindFnToScope=true]
         * @returns {Object} value as accessible by path
         */
    //TODO(misko): this function needs to be removed
        function getter(obj, path, bindFnToScope) {
            if (!path) return obj;
            var keys = path.split('.');
            var key;
            var lastInstance = obj;
            var len = keys.length;
    
            for (var i = 0; i < len; i++) {
                key = keys[i];
                if (obj) {
                    obj = (lastInstance = obj)[key];
                }
            }
            if (!bindFnToScope && isFunction(obj)) {
                return bind(lastInstance, obj);
            }
            return obj;
        }
    
        /**
         * Return the DOM siblings between the first and last node in the given array.
         * @param {Array} array like object
         * @returns {DOMElement} object containing the elements
         */
        function getBlockElements(nodes) {
            var startNode = nodes[0],
                endNode = nodes[nodes.length - 1];
            if (startNode === endNode) {
                return jqLite(startNode);
            }
    
            var element = startNode;
            var elements = [element];
    
            do {
                element = element.nextSibling;
                if (!element) break;
                elements.push(element);
            } while (element !== endNode);
    
            return jqLite(elements);
        }
    
        /**
         * @ngdoc type
         * @name angular.Module
         * @module ng
         * @description
         *
         * Interface for configuring angular {@link angular.module modules}.
         */
    
        function setupModuleLoader(window) {
    
            var $injectorMinErr = minErr('$injector');
            var ngMinErr = minErr('ng');
    
            function ensure(obj, name, factory) {
                return obj[name] || (obj[name] = factory());
            }
    
            var angular = ensure(window, 'angular', Object);
    
            // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
            angular.$$minErr = angular.$$minErr || minErr;
    
            return ensure(angular, 'module', function() {
                /** @type {Object.<string, angular.Module>} */
                var modules = {};
    
                /**
                 * @ngdoc function
                 * @name angular.module
                 * @module ng
                 * @description
                 *
                 * The `angular.module` is a global place for creating, registering and retrieving Angular
                 * modules.
                 * All modules (angular core or 3rd party) that should be available to an application must be
                 * registered using this mechanism.
                 *
                 * When passed two or more arguments, a new module is created.  If passed only one argument, an
                 * existing module (the name passed as the first argument to `module`) is retrieved.
                 *
                 *
                 * # Module
                 *
                 * A module is a collection of services, directives, controllers, filters, and configuration information.
                 * `angular.module` is used to configure the {@link auto.$injector $injector}.
                 *
                 * ```js
                 * // Create a new module
                 * var myModule = angular.module('myModule', []);
                 *
                 * // register a new service
                 * myModule.value('appName', 'MyCoolApp');
                 *
                 * // configure existing services inside initialization blocks.
                 * myModule.config(['$locationProvider', function($locationProvider) {
         *   // Configure existing providers
         *   $locationProvider.hashPrefix('!');
         * }]);
                 * ```
                 *
                 * Then you can create an injector and load your modules like this:
                 *
                 * ```js
                 * var injector = angular.injector(['ng', 'myModule'])
                 * ```
                 *
                 * However it's more likely that you'll just use
                 * {@link ng.directive:ngApp ngApp} or
                 * {@link angular.bootstrap} to simplify this process for you.
                 *
                 * @param {!string} name The name of the module to create or retrieve.
                 * @param {!Array.<string>=} requires If specified then new module is being created. If
                 *        unspecified then the module is being retrieved for further configuration.
                 * @param {Function=} configFn Optional configuration function for the module. Same as
                 *        {@link angular.Module#config Module#config()}.
                 * @returns {module} new module with the {@link angular.Module} api.
                 */
                return function module(name, requires, configFn) {//var myModule = angular.module('myModule', []);
                    var assertNotHasOwnProperty = function(name, context) {
                        if (name === 'hasOwnProperty') {
                            throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
                        }
                    };
    
                    assertNotHasOwnProperty(name, 'module');
                    if (requires && modules.hasOwnProperty(name)) {
                        modules[name] = null;
                    }
                    return ensure(modules, name, function() {
                        if (!requires) {
                            throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
                            "the module name or forgot to load it. If registering a module ensure that you " +
                            "specify the dependencies as the second argument.", name);
                        }
    
                        /** @type {!Array.<Array.<*>>} */
                        var invokeQueue = [];
    
                        /** @type {!Array.<Function>} */
                        var configBlocks = [];
    
                        /** @type {!Array.<Function>} */
                        var runBlocks = [];
    
                        var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
    
                        /** @type {angular.Module} */
                        var moduleInstance = {
                            // Private state
                            _invokeQueue: invokeQueue,
                            _configBlocks: configBlocks,
                            _runBlocks: runBlocks,
    
                            /**
                             * @ngdoc property
                             * @name angular.Module#requires
                             * @module ng
                             * @returns {Array.<string>} List of module names which must be loaded before this module.
                             * @description
                             * Holds the list of modules which the injector will load before the current module is
                             * loaded.
                             */
                            requires: requires,
    
                            /**
                             * @ngdoc property
                             * @name angular.Module#name
                             * @module ng
                             * @returns {string} Name of the module.
                             * @description
                             */
                            name: name,
    
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#provider
                             * @module ng
                             * @param {string} name service name
                             * @param {Function} providerType Construction function for creating new instance of the
                             *                                service.
                             * @description
                             * See {@link auto.$provide#provider $provide.provider()}.
                             */
                            provider: invokeLater('$provide', 'provider'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#factory
                             * @module ng
                             * @param {string} name service name
                             * @param {Function} providerFunction Function for creating new instance of the service.
                             * @description
                             * See {@link auto.$provide#factory $provide.factory()}.
                             */
                            factory: invokeLater('$provide', 'factory'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#service
                             * @module ng
                             * @param {string} name service name
                             * @param {Function} constructor A constructor function that will be instantiated.
                             * @description
                             * See {@link auto.$provide#service $provide.service()}.
                             */
                            service: invokeLater('$provide', 'service'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#value
                             * @module ng
                             * @param {string} name service name
                             * @param {*} object Service instance object.
                             * @description
                             * See {@link auto.$provide#value $provide.value()}.
                             */
                            value: invokeLater('$provide', 'value'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#constant
                             * @module ng
                             * @param {string} name constant name
                             * @param {*} object Constant value.
                             * @description
                             * Because the constant are fixed, they get applied before other provide methods.
                             * See {@link auto.$provide#constant $provide.constant()}.
                             */
                            constant: invokeLater('$provide', 'constant', 'unshift'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#animation
                             * @module ng
                             * @param {string} name animation name
                             * @param {Function} animationFactory Factory function for creating new instance of an
                             *                                    animation.
                             * @description
                             *
                             * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
                             *
                             *
                             * Defines an animation hook that can be later used with
                             * {@link ngAnimate.$animate $animate} service and directives that use this service.
                             *
                             * ```js
                             * module.animation('.animation-name', function($inject1, $inject2) {
               *   return {
               *     eventName : function(element, done) {
               *       //code to run the animation
               *       //once complete, then run done()
               *       return function cancellationFunction(element) {
               *         //code to cancel the animation
               *       }
               *     }
               *   }
               * })
                             * ```
                             *
                             * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and
                             * {@link ngAnimate ngAnimate module} for more information.
                             */
                            animation: invokeLater('$animateProvider', 'register'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#filter
                             * @module ng
                             * @param {string} name Filter name.
                             * @param {Function} filterFactory Factory function for creating new instance of filter.
                             * @description
                             * See {@link ng.$filterProvider#register $filterProvider.register()}.
                             */
                            filter: invokeLater('$filterProvider', 'register'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#controller
                             * @module ng
                             * @param {string|Object} name Controller name, or an object map of controllers where the
                             *    keys are the names and the values are the constructors.
                             * @param {Function} constructor Controller constructor function.
                             * @description
                             * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
                             */
                            controller: invokeLater('$controllerProvider', 'register'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#directive
                             * @module ng
                             * @param {string|Object} name Directive name, or an object map of directives where the
                             *    keys are the names and the values are the factories.
                             * @param {Function} directiveFactory Factory function for creating new instance of
                             * directives.
                             * @description
                             * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
                             */
                            directive: invokeLater('$compileProvider', 'directive'),
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#config
                             * @module ng
                             * @param {Function} configFn Execute this function on module load. Useful for service
                             *    configuration.
                             * @description
                             * Use this method to register work which needs to be performed on module loading.
                             * For more about how to configure services, see
                             * {@link providers#providers_provider-recipe Provider Recipe}.
                             */
                            config: config,
    
                            /**
                             * @ngdoc method
                             * @name angular.Module#run
                             * @module ng
                             * @param {Function} initializationFn Execute this function after injector creation.
                             *    Useful for application initialization.
                             * @description
                             * Use this method to register work which should be performed when the injector is done
                             * loading all modules.
                             */
                            run: function(block) {
                                runBlocks.push(block);
                                return this;
                            }
                        };
    
                        if (configFn) {
                            config(configFn);
                        }
    
                        return  moduleInstance;
    
                        /**
                         * @param {string} provider
                         * @param {string} method
                         * @param {String=} insertMethod
                         * @returns {angular.Module}
                         */
                        function invokeLater(provider, method, insertMethod, queue) {
                            if (!queue) queue = invokeQueue;
                            return function() {
                                queue[insertMethod || 'push']([provider, method, arguments]);
                                return moduleInstance;
                            };
                        }
                    });
                };
            });
    
        }
    
        /* global angularModule: true,//angular全局模块
         version: true,
    
         $LocaleProvider,//local
         $CompileProvider,//compile
    
         htmlAnchorDirective,//<a href="http://www.baidu.com">baidu</a>
         inputDirective,//<input type='text' name='name'>
         inputDirective,//<input type='text' name='pwd'>
         formDirective,//<form action ='localhost:8080/book/booklist/' method='post'>
         scriptDirective,//<script src='angular.min.js'></script>
         selectDirective,//<select name='city' ></select>
         styleDirective,//<style></style>
         optionDirective,<option></option>
         ngBindDirective,<div ng-bind='{{bookPrice}}'></div>
         ngBindHtmlDirective,
         ngBindTemplateDirective,
         ngClassDirective,
         ngClassEvenDirective,
         ngClassOddDirective,
         ngCspDirective,
         ngCloakDirective,
         ngControllerDirective,
         ngFormDirective,
         ngHideDirective,
         ngIfDirective,
         ngIncludeDirective,
         ngIncludeFillContentDirective,
         ngInitDirective,
         ngNonBindableDirective,
         ngPluralizeDirective,
         ngRepeatDirective,
         ngShowDirective,
         ngStyleDirective,
         ngSwitchDirective,
         ngSwitchWhenDirective,
         ngSwitchDefaultDirective,
         ngOptionsDirective,
         ngTranscludeDirective,
         ngModelDirective,
         ngListDirective,
         ngChangeDirective,
         patternDirective,
         patternDirective,
         requiredDirective,
         requiredDirective,
         minlengthDirective,
         minlengthDirective,
         maxlengthDirective,
         maxlengthDirective,
         ngValueDirective,
         ngModelOptionsDirective,
         ngAttributeAliasDirectives,
         ngEventDirectives,
    
         $AnchorScrollProvider,
         $AnimateProvider,
         $BrowserProvider,
         $CacheFactoryProvider,
         $ControllerProvider,
         $DocumentProvider,
         $ExceptionHandlerProvider,
         $FilterProvider,
         $InterpolateProvider,
         $IntervalProvider,
         $HttpProvider,
         $HttpBackendProvider,
         $LocationProvider,
         $LogProvider,
         $ParseProvider,
         $RootScopeProvider,
         $QProvider,
         $$QProvider,
         $$SanitizeUriProvider,
         $SceProvider,
         $SceDelegateProvider,
         $SnifferProvider,
         $TemplateCacheProvider,
         $TimeoutProvider,
         $$RAFProvider,
         $$AsyncCallbackProvider,
         $WindowProvider
         */
    
    
        /**
         * @ngdoc object
         * @name angular.version
         * @module ng
         * @description
         * An object that contains information about the current AngularJS version. This object has the
         * following properties:
         *
         * - `full` – `{string}` – Full version string, such as "0.9.18".
         * - `major` – `{number}` – Major version number, such as "0".
         * - `minor` – `{number}` – Minor version number, such as "9".
         * - `dot` – `{number}` – Dot version number, such as "18".
         * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
         */
        var version = {
            full: '1.3.0-beta.15',    // all of these placeholder strings will be replaced by grunt's
            major: 1,    // package task
            minor: 3,
            dot: 0,
            codeName: 'unbelievable-advancement'
        };
    
    查看代码
    
    
    function publishExternalAPI(angular){
            extend(angular, {
                'bootstrap': bootstrap,
                'copy': copy,
                'extend': extend,
                '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},
                '$$minErr': minErr,
                '$$csp': csp
            });
    
            angularModule = setupModuleLoader(window);
            try {
                angularModule('ngLocale');
            } catch (e) {
                angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
            }
    
            angularModule('ng', ['ngLocale'], ['$provide',
                function ngModule($provide) {
                    // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
                    $provide.provider({
                        $$sanitizeUri: $$SanitizeUriProvider
                    });
                    $provide.provider('$compile', $CompileProvider).
                        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
                        }).
                        directive({
                            ngInclude: ngIncludeFillContentDirective
                        }).
                        directive(ngAttributeAliasDirectives).
                        directive(ngEventDirectives);
                    $provide.provider({
                        $anchorScroll: $AnchorScrollProvider,
                        $animate: $AnimateProvider,
                        $browser: $BrowserProvider,
                        $cacheFactory: $CacheFactoryProvider,
                        $controller: $ControllerProvider,
                        $document: $DocumentProvider,
                        $exceptionHandler: $ExceptionHandlerProvider,
                        $filter: $FilterProvider,
                        $interpolate: $InterpolateProvider,
                        $interval: $IntervalProvider,
                        $http: $HttpProvider,
                        $httpBackend: $HttpBackendProvider,
                        $location: $LocationProvider,
                        $log: $LogProvider,
                        $parse: $ParseProvider,
                        $rootScope: $RootScopeProvider,
                        $q: $QProvider,
                        $$q: $$QProvider,
                        $sce: $SceProvider,
                        $sceDelegate: $SceDelegateProvider,
                        $sniffer: $SnifferProvider,
                        $templateCache: $TemplateCacheProvider,
                        $timeout: $TimeoutProvider,
                        $window: $WindowProvider,
                        $$rAF: $$RAFProvider,
                        $$asyncCallback : $$AsyncCallbackProvider
                    });
                }
            ]);
        }
    
        /* global JQLitePrototype: true,
         addEventListenerFn: true,
         removeEventListenerFn: true,
         BOOLEAN_ATTR: true,
         ALIASED_ATTR: true,
         */
    
    //////////////////////////////////
    //JQLite
    //////////////////////////////////
    
        /**
         * @ngdoc function
         * @name angular.element
         * @module ng
         * @kind function
         *
         * @description
         * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
         *
         * If jQuery is available, `angular.element` is an alias for the
         * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element`
         * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite."
         *
         * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows
         * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
         * commonly needed functionality with the goal of having a very small footprint.</div>
         *
         * To use jQuery, simply load it before `DOMContentLoaded` event fired.
         *
         * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
         * jqLite; they are never raw DOM references.</div>
         *
         * ## Angular's jqLite
         * jqLite provides only the following jQuery methods:
         *
         * - [`addClass()`](http://api.jquery.com/addClass/)
         * - [`after()`](http://api.jquery.com/after/)
         * - [`append()`](http://api.jquery.com/append/)
         * - [`attr()`](http://api.jquery.com/attr/)
         * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData
         * - [`children()`](http://api.jquery.com/children/) - Does not support selectors
         * - [`clone()`](http://api.jquery.com/clone/)
         * - [`contents()`](http://api.jquery.com/contents/)
         * - [`css()`](http://api.jquery.com/css/)
         * - [`data()`](http://api.jquery.com/data/)
         * - [`empty()`](http://api.jquery.com/empty/)
         * - [`eq()`](http://api.jquery.com/eq/)
         * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name
         * - [`hasClass()`](http://api.jquery.com/hasClass/)
         * - [`html()`](http://api.jquery.com/html/)
         * - [`next()`](http://api.jquery.com/next/) - Does not support selectors
         * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData
         * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors
         * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors
         * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors
         * - [`prepend()`](http://api.jquery.com/prepend/)
         * - [`prop()`](http://api.jquery.com/prop/)
         * - [`ready()`](http://api.jquery.com/ready/)
         * - [`remove()`](http://api.jquery.com/remove/)
         * - [`removeAttr()`](http://api.jquery.com/removeAttr/)
         * - [`removeClass()`](http://api.jquery.com/removeClass/)
         * - [`removeData()`](http://api.jquery.com/removeData/)
         * - [`replaceWith()`](http://api.jquery.com/replaceWith/)
         * - [`text()`](http://api.jquery.com/text/)
         * - [`toggleClass()`](http://api.jquery.com/toggleClass/)
         * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers.
         * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces
         * - [`val()`](http://api.jquery.com/val/)
         * - [`wrap()`](http://api.jquery.com/wrap/)
         *
         * ## jQuery/jqLite Extras
         * Angular also provides the following additional methods and events to both jQuery and jqLite:
         *
         * ### Events
         * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event
         *    on all DOM nodes being removed.  This can be used to clean up any 3rd party bindings to the DOM
         *    element before it is removed.
         *
         * ### Methods
         * - `controller(name)` - retrieves the controller of the current element or its parent. By default
         * 返回当前元素的控制器或它的父节点。
         *   retrieves controller associated with the `ngController` directive. If `name` is provided as、
         *  默认的返回与 之关联的控制器,如果ame是camelCase命名的指令
         *   camelCase directive name, then the controller for this directive will be retrieved (e.g.
         *   这个控制器将会被直接返回
         *   `'ngModel'`).例如,ngModel
         * - `injector()` - retrieves the injector of the current element or its parent.
         * 返回当前元素的注入器或它的父节点
         * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
         *   element or its parent.
         *   返回当前元素的顶级作用域或它的父元素
         * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
         * 如果一旦被附加到当前的元素
         *   current element. This getter should be used only on elements that contain a directive which starts a new isolate
         *   当启用一个新的隔离作用域时,它只能作用于某一个元素,包括指令
         *   scope. Calling `scope()` on this element always returns the original non-isolate scope.
         *   当在这个当前元素调用scope()方法时,它将总是返回其原来的非隔离作用域
         * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
         *   parent element is reached.
         *
         * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery.
         * @returns {Object} jQuery object.
         */
    
        JQLite.expando = 'ng339';
    
        var jqCache = JQLite.cache = {},
            jqId = 1,
            addEventListenerFn = (window.document.addEventListener
                ? function(element, type, fn) {element.addEventListener(type, fn, false);}
                : function(element, type, fn) {element.attachEvent('on' + type, fn);}),
            removeEventListenerFn = (window.document.removeEventListener
                ? function(element, type, fn) {element.removeEventListener(type, fn, false); }
                : function(element, type, fn) {element.detachEvent('on' + type, fn); });
    
        /*
         * !!! This is an undocumented "private" function !!!
         */
        var jqData = JQLite._data = function(node) {
            //jQuery always returns an object on cache miss
            //jQuery总是从cache返回一个对象
            return this.cache[node[this.expando]] || {};
        };
    
        function jqNextId() { return ++jqId; }
    
    
        var SPECIAL_CHARS_REGEXP = /([:-\_]+(.))/g;
        var MOZ_HACK_REGEXP = /^moz([A-Z])/;
        var jqLiteMinErr = minErr('jqLite');
    
        /**
         * Converts snake_case to camelCase.
         * Also there is special case for Moz prefix starting with upper case letter.
         * @param name Name to normalize
         */
        function camelCase(name) {
            return name.
                replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
                    return offset ? letter.toUpperCase() : letter;
                }).
                replace(MOZ_HACK_REGEXP, 'Moz$1');
        }
    
        var SINGLE_TAG_REGEXP = /^<(w+)s*/?>(?:</1>|)$/;
        var HTML_REGEXP = /<|&#?w+;/;
        var TAG_NAME_REGEXP = /<([w:]+)/;
        var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([w:]+)[^>]*)/>/gi;
    
        var wrapMap = {
            'option': [1, '<select multiple="multiple">', '</select>'],
    
            'thead': [1, '<table>', '</table>'],
            'col': [2, '<table><colgroup>', '</colgroup></table>'],
            'tr': [2, '<table><tbody>', '</tbody></table>'],
            'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
            '_default': [0, "", ""]
        };
    
        wrapMap.optgroup = wrapMap.option;
        wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
        wrapMap.th = wrapMap.td;
    
        function jqLiteIsTextNode(html) {
            return !HTML_REGEXP.test(html);
        }
    
        function jqLiteAcceptsData(node) {
            // The window object can accept data but has no nodeType
            // Otherwise we are only interested in elements (1) and documents (9)
            return !node.nodeType || node.nodeType === 1 || node.nodeType === 9;
        }
    
        function jqLiteBuildFragment(html, context) {
            var elem, tmp, tag, wrap,
                fragment = context.createDocumentFragment(),
                nodes = [], i;
    
            if (jqLiteIsTextNode(html)) {
                // Convert non-html into a text node
                nodes.push(context.createTextNode(html));
            } else {
                // Convert html into DOM nodes
                tmp = tmp || fragment.appendChild(context.createElement("div"));
                tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase();
                wrap = wrapMap[tag] || wrapMap._default;
                tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2];
    
                // Descend through wrappers to the right content
                i = wrap[0];
                while (i--) {
                    tmp = tmp.lastChild;
                }
    
                nodes = concat(nodes, tmp.childNodes);
    
                tmp = fragment.firstChild;
                tmp.textContent = "";
            }
    
            // Remove wrapper from fragment
            fragment.textContent = "";
            fragment.innerHTML = ""; // Clear inner HTML
            forEach(nodes, function(node) {
                fragment.appendChild(node);
            });
    
            return fragment;
        }
    
        function jqLiteParseHTML(html, context) {
            context = context || document;
            var parsed;
    
            if ((parsed = SINGLE_TAG_REGEXP.exec(html))) {
                return [context.createElement(parsed[1])];
            }
    
            if ((parsed = jqLiteBuildFragment(html, context))) {
                return parsed.childNodes;
            }
    
            return [];
        }
    
    /////////////////////////////////////////////
        function JQLite(element) {
            if (element instanceof JQLite) {
                return element;
            }
            if (isString(element)) {
                element = trim(element);
            }
            if (!(this instanceof JQLite)) {
                if (isString(element) && element.charAt(0) != '<') {
                    throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
                }
                return new JQLite(element);
            }
    
            if (isString(element)) {
                jqLiteAddNodes(this, jqLiteParseHTML(element));
            } else {
                jqLiteAddNodes(this, element);
            }
        }
    
        function jqLiteClone(element) {
            return element.cloneNode(true);
        }
    
        function jqLiteDealoc(element, onlyDescendants){
            if (!onlyDescendants) jqLiteRemoveData(element);
    
            if (element.childNodes && element.childNodes.length) {
                // we use querySelectorAll because documentFragments don't have getElementsByTagName
                var descendants = element.getElementsByTagName ? element.getElementsByTagName('*') :
                    element.querySelectorAll ? element.querySelectorAll('*') : [];
                for (var i = 0, l = descendants.length; i < l; i++) {
                    jqLiteRemoveData(descendants[i]);
                }
            }
        }
    
        function jqLiteOff(element, type, fn, unsupported) {
            if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument');
    
            var events = jqLiteExpandoStore(element, 'events'),
                handle = jqLiteExpandoStore(element, 'handle');
    
            if (!handle) return; //no listeners registered
    
            if (isUndefined(type)) {
                forEach(events, function(eventHandler, type) {
                    removeEventListenerFn(element, type, eventHandler);
                    delete events[type];
                });
            } else {
                forEach(type.split(' '), function(type) {
                    if (isUndefined(fn)) {
                        removeEventListenerFn(element, type, events[type]);
                        delete events[type];
                    } else {
                        arrayRemove(events[type] || [], fn);
                    }
                });
            }
        }
    
        function jqLiteRemoveData(element, name) {
            var expandoId = element.ng339,
                expandoStore = jqCache[expandoId];
    
            if (expandoStore) {
                if (name) {
                    delete jqCache[expandoId].data[name];
                    return;
                }
    
                if (expandoStore.handle) {
                    expandoStore.events.$destroy && expandoStore.handle({}, '$destroy');
                    jqLiteOff(element);
                }
                delete jqCache[expandoId];
                element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
            }
        }
    
        function jqLiteExpandoStore(element, key, value) {
            var expandoId = element.ng339,
                expandoStore = jqCache[expandoId || -1];
    
            if (isDefined(value)) {
                if (!expandoStore) {
                    element.ng339 = expandoId = jqNextId();
                    expandoStore = jqCache[expandoId] = {};
                }
                expandoStore[key] = value;
            } else {
                return expandoStore && expandoStore[key];
            }
        }
    
        function jqLiteData(element, key, value) {
            if (jqLiteAcceptsData(element)) {
                var data = jqLiteExpandoStore(element, 'data'),
                    isSetter = isDefined(value),
                    keyDefined = !isSetter && isDefined(key),
                    isSimpleGetter = keyDefined && !isObject(key);
    
                if (!data && !isSimpleGetter) {
                    jqLiteExpandoStore(element, 'data', data = {});
                }
    
                if (isSetter) {
                    data[key] = value;
                } else {
                    if (keyDefined) {
                        if (isSimpleGetter) {
                            // don't create data in this case.
                            return data && data[key];
                        } else {
                            extend(data, key);
                        }
                    } else {
                        return data;
                    }
                }
            }
        }
    
        function jqLiteHasClass(element, selector) {
            if (!element.getAttribute) return false;
            return ((" " + (element.getAttribute('class') || '') + " ").replace(/[
    	]/g, " ").
                indexOf( " " + selector + " " ) > -1);
        }
    
        function jqLiteRemoveClass(element, cssClasses) {
            if (cssClasses && element.setAttribute) {
                forEach(cssClasses.split(' '), function(cssClass) {
                    element.setAttribute('class', trim(
                            (" " + (element.getAttribute('class') || '') + " ")
                                .replace(/[
    	]/g, " ")
                                .replace(" " + trim(cssClass) + " ", " "))
                    );
                });
            }
        }
    
        function jqLiteAddClass(element, cssClasses) {
            if (cssClasses && element.setAttribute) {
                var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ')
                    .replace(/[
    	]/g, " ");
    
                forEach(cssClasses.split(' '), function(cssClass) {
                    cssClass = trim(cssClass);
                    if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
                        existingClasses += cssClass + ' ';
                    }
                });
    
                element.setAttribute('class', trim(existingClasses));
            }
        }
    
    
        function jqLiteAddNodes(root, elements) {
            // THIS CODE IS VERY HOT. Don't make changes without benchmarking.
    
            if (elements) {
    
                // if a Node (the most common case)
                if (elements.nodeType) {
                    root[root.length++] = elements;
                } else {
                    var length = elements.length;
    
                    // if an Array or NodeList and not a Window
                    if (typeof length === 'number' && elements.window !== elements) {
                        if (length) {
                            if (elements.item) {
                                // convert NodeList to an Array to make PhantomJS 1.x happy
                                elements = slice.call(elements);
                            }
                            push.apply(root, elements);
                        }
                    } else {
                        root[root.length++] = elements;
                    }
                }
            }
        }
    
    
        function jqLiteController(element, name) {
            return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller');
        }
    
        function jqLiteInheritedData(element, name, value) {
            element = jqLite(element);
    
            // if element is the document object work with the html element instead
            // this makes $(document).scope() possible
            if(element[0].nodeType == 9) {
                element = element.find('html');
            }
            var names = isArray(name) ? name : [name];
    
            while (element.length) {
                var node = element[0];
                for (var i = 0, ii = names.length; i < ii; i++) {
                    if ((value = element.data(names[i])) !== undefined) return value;
                }
    
                // If dealing with a document fragment node with a host element, and no parent, use the host
                // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
                // to lookup parent controllers.
                element = jqLite(node.parentNode || (node.nodeType === 11 && node.host));
            }
        }
    
        function jqLiteEmpty(element) {
            jqLiteDealoc(element, true);
            while (element.firstChild) {
                element.removeChild(element.firstChild);
            }
        }
    
    //////////////////////////////////////////
    // Functions which are declared directly.
    //////////////////////////////////////////
        var JQLitePrototype = JQLite.prototype = {
            ready: function(fn) {
                var fired = false;
    
                function trigger() {
                    if (fired) return;
                    fired = true;
                    fn();
                }
    
                // check if document already is loaded
                if (document.readyState === 'complete'){//判断文档是否已经加载完成
                    setTimeout(trigger);
                } else {
                    this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9
                    // we can not use jqLite since we are not done loading and jQuery could be loaded later.
                    // jshint -W064
                    JQLite(window).on('load', trigger); // fallback to window.onload for others
                    // jshint +W064
                }
            },
            toString: function() {
                var value = [];
                forEach(this, function(e){ value.push('' + e);});
                return '[' + value.join(', ') + ']';
            },
    
            eq: function(index) {
                return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]);
            },
    
            length: 0,
            push: push,
            sort: [].sort,
            splice: [].splice
        };
    
    //////////////////////////////////////////
    // Functions iterating getter/setters.
    // these functions return self on setter and
    // value on get.
    //////////////////////////////////////////
        var BOOLEAN_ATTR = {};
        forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) {
            BOOLEAN_ATTR[lowercase(value)] = value;
        });
        var BOOLEAN_ELEMENTS = {};
        forEach('input,select,option,textarea,button,form,details'.split(','), function(value) {
            BOOLEAN_ELEMENTS[value] = true;
        });
        var ALIASED_ATTR = {
            'ngMinlength' : 'minlength',
            'ngMaxlength' : 'maxlength',
            'ngPattern' : 'pattern'
        };
    
        function getBooleanAttrName(element, name) {
            // check dom last since we will most likely fail on name
            var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()];
    
            // booleanAttr is here twice to minimize DOM access
            return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr;
        }
    
        function getAliasedAttrName(element, name) {
            var nodeName = element.nodeName;
            return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name];
        }
    
        forEach({
            data: jqLiteData,
            inheritedData: jqLiteInheritedData,
    
            scope: function(element) {
                // Can't use jqLiteData here directly so we stay compatible with jQuery!
                return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
            },
    
            isolateScope: function(element) {
                // Can't use jqLiteData here directly so we stay compatible with jQuery!
                return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate');
            },
    
            controller: jqLiteController,
    
            injector: function(element) {
                return jqLiteInheritedData(element, '$injector');
            },
    
            removeAttr: function(element,name) {
                element.removeAttribute(name);
            },
    
            hasClass: jqLiteHasClass,
    
            css: function(element, name, value) {
                name = camelCase(name);
    
                if (isDefined(value)) {
                    element.style[name] = value;
                } else {
                    var val;
    
                    if (msie <= 8) {
                        // this is some IE specific weirdness that jQuery 1.6.4 does not sure why
                        val = element.currentStyle && element.currentStyle[name];
                        if (val === '') val = 'auto';
                    }
    
                    val = val || element.style[name];
    
                    if (msie <= 8) {
                        // jquery weirdness :-/
                        val = (val === '') ? undefined : val;
                    }
    
                    return  val;
                }
            },
    
            attr: function(element, name, value){
                var lowercasedName = lowercase(name);
                if (BOOLEAN_ATTR[lowercasedName]) {
                    if (isDefined(value)) {
                        if (!!value) {
                            element[name] = true;
                            element.setAttribute(name, lowercasedName);
                        } else {
                            element[name] = false;
                            element.removeAttribute(lowercasedName);
                        }
                    } else {
                        return (element[name] ||
                        (element.attributes.getNamedItem(name)|| noop).specified)
                            ? lowercasedName
                            : undefined;
                    }
                } else if (isDefined(value)) {
                    element.setAttribute(name, value);
                } else if (element.getAttribute) {
                    // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code
                    // some elements (e.g. Document) don't have get attribute, so return undefined
                    var ret = element.getAttribute(name, 2);
                    // normalize non-existing attributes to undefined (as jQuery)
                    return ret === null ? undefined : ret;
                }
            },
    
            prop: function(element, name, value) {
                if (isDefined(value)) {
                    element[name] = value;
                } else {
                    return element[name];
                }
            },
    
            text: (function() {
                getText.$dv = '';
                return getText;
    
                function getText(element, value) {
                    if (isUndefined(value)) {
                        var nodeType = element.nodeType;
                        return (nodeType === 1 || nodeType === 3) ? element.textContent : '';
                    }
                    element.textContent = value;
                }
            })(),
    
            val: function(element, value) {
                if (isUndefined(value)) {
                    if (element.multiple && nodeName_(element) === 'select') {
                        var result = [];
                        forEach(element.options, function (option) {
                            if (option.selected) {
                                result.push(option.value || option.text);
                            }
                        });
                        return result.length === 0 ? null : result;
                    }
                    return element.value;
                }
                element.value = value;
            },
    
            html: function(element, value) {
                if (isUndefined(value)) {
                    return element.innerHTML;
                }
                jqLiteDealoc(element, true);
                element.innerHTML = value;
            },
    
            empty: jqLiteEmpty
        }, function(fn, name){
            /**
             * Properties: writes return selection, reads return first value
             */
            JQLite.prototype[name] = function(arg1, arg2) {
                var i, key;
                var nodeCount = this.length;
    
                // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
                // in a way that survives minification.
                // jqLiteEmpty takes no arguments but is a setter.
                if (fn !== jqLiteEmpty &&
                    (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) {
                    if (isObject(arg1)) {
    
                        // we are a write, but the object properties are the key/values
                        for (i = 0; i < nodeCount; i++) {
                            if (fn === jqLiteData) {
                                // data() takes the whole object in jQuery
                                fn(this[i], arg1);
                            } else {
                                for (key in arg1) {
                                    fn(this[i], key, arg1[key]);
                                }
                            }
                        }
                        // return self for chaining
                        return this;
                    } else {
                        // we are a read, so read the first child.
                        // TODO: do we still need this?
                        var value = fn.$dv;
                        // Only if we have $dv do we iterate over all, otherwise it is just the first element.
                        var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
                        for (var j = 0; j < jj; j++) {
                            var nodeValue = fn(this[j], arg1, arg2);
                            value = value ? value + nodeValue : nodeValue;
                        }
                        return value;
                    }
                } else {
                    // we are a write, so apply to all children
                    for (i = 0; i < nodeCount; i++) {
                        fn(this[i], arg1, arg2);
                    }
                    // return self for chaining
                    return this;
                }
            };
        });
    
        function createEventHandler(element, events) {
            var eventHandler = function (event, type) {
                if (!event.preventDefault) {
                    event.preventDefault = function() {
                        event.returnValue = false; //ie
                    };
                }
    
                if (!event.stopPropagation) {
                    event.stopPropagation = function() {
                        event.cancelBubble = true; //ie
                    };
                }
    
                if (!event.target) {
                    event.target = event.srcElement || document;
                }
    
                if (isUndefined(event.defaultPrevented)) {
                    var prevent = event.preventDefault;
                    event.preventDefault = function() {
                        event.defaultPrevented = true;
                        prevent.call(event);
                    };
                    event.defaultPrevented = false;
                }
    
                event.isDefaultPrevented = function() {
                    return event.defaultPrevented || event.returnValue === false;
                };
    
                // Copy event handlers in case event handlers array is modified during execution.
                var eventHandlersCopy = shallowCopy(events[type || event.type] || []);
    
                forEach(eventHandlersCopy, function(fn) {
                    fn.call(element, event);
                });
    
                // Remove monkey-patched methods (IE),
                // as they would cause memory leaks in IE8.
                if (msie <= 8) {
                    // IE7/8 does not allow to delete property on native object
                    event.preventDefault = null;
                    event.stopPropagation = null;
                    event.isDefaultPrevented = null;
                } else {
                    // It shouldn't affect normal browsers (native methods are defined on prototype).
                    delete event.preventDefault;
                    delete event.stopPropagation;
                    delete event.isDefaultPrevented;
                }
            };
            eventHandler.elem = element;
            return eventHandler;
        }
    
    //////////////////////////////////////////
    // Functions iterating traversal.
    // These functions chain results into a single
    // selector.
    //////////////////////////////////////////
        forEach({
            removeData: jqLiteRemoveData,
    
            on: function onFn(element, type, fn, unsupported){
                if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters');
    
                // Do not add event handlers to non-elements because they will not be cleaned up.
                if (!jqLiteAcceptsData(element)) {
                    return;
                }
    
                var events = jqLiteExpandoStore(element, 'events'),
                    handle = jqLiteExpandoStore(element, 'handle');
    
                if (!events) jqLiteExpandoStore(element, 'events', events = {});
                if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events));
    
                forEach(type.split(' '), function(type){
                    var eventFns = events[type];
    
                    if (!eventFns) {
                        if (type == 'mouseenter' || type == 'mouseleave') {
                            var contains = document.body.contains || document.body.compareDocumentPosition ?
                                function( a, b ) {
                                    // jshint bitwise: false
                                    var adown = a.nodeType === 9 ? a.documentElement : a,
                                        bup = b && b.parentNode;
                                    return a === bup || !!( bup && bup.nodeType === 1 && (
                                            adown.contains ?
                                                adown.contains( bup ) :
                                            a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
                                        ));
                                } :
                                function( a, b ) {
                                    if ( b ) {
                                        while ( (b = b.parentNode) ) {
                                            if ( b === a ) {
                                                return true;
                                            }
                                        }
                                    }
                                    return false;
                                };
    
                            events[type] = [];
    
                            // Refer to jQuery's implementation of mouseenter & mouseleave
                            // Read about mouseenter and mouseleave:
                            // http://www.quirksmode.org/js/events_mouse.html#link8
                            var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"};
    
                            onFn(element, eventmap[type], function(event) {
                                var target = this, related = event.relatedTarget;
                                // For mousenter/leave call the handler if related is outside the target.
                                // NB: No relatedTarget if the mouse left/entered the browser window
                                if ( !related || (related !== target && !contains(target, related)) ){
                                    handle(event, type);
                                }
                            });
    
                        } else {
                            addEventListenerFn(element, type, handle);
                            events[type] = [];
                        }
                        eventFns = events[type];
                    }
                    eventFns.push(fn);
                });
            },
    
            off: jqLiteOff,
    
            one: function(element, type, fn) {
                element = jqLite(element);
    
                //add the listener twice so that when it is called
                //you can remove the original function and still be
                //able to call element.off(ev, fn) normally
                element.on(type, function onFn() {
                    element.off(type, fn);
                    element.off(type, onFn);
                });
                element.on(type, fn);
            },
    
            replaceWith: function(element, replaceNode) {
                var index, parent = element.parentNode;
                jqLiteDealoc(element);
                forEach(new JQLite(replaceNode), function(node){
                    if (index) {
                        parent.insertBefore(node, index.nextSibling);
                    } else {
                        parent.replaceChild(node, element);
                    }
                    index = node;
                });
            },
    
            children: function(element) {
                var children = [];
                forEach(element.childNodes, function(element){
                    if (element.nodeType === 1)
                        children.push(element);
                });
                return children;
            },
    
            contents: function(element) {
                return element.contentDocument || element.childNodes || [];
            },
    
            append: function(element, node) {
                forEach(new JQLite(node), function(child){
                    if (element.nodeType === 1 || element.nodeType === 11) {
                        element.appendChild(child);
                    }
                });
            },
    
            prepend: function(element, node) {
                if (element.nodeType === 1) {
                    var index = element.firstChild;
                    forEach(new JQLite(node), function(child){
                        element.insertBefore(child, index);
                    });
                }
            },
    
            wrap: function(element, wrapNode) {
                wrapNode = jqLite(wrapNode)[0];
                var parent = element.parentNode;
                if (parent) {
                    parent.replaceChild(wrapNode, element);
                }
                wrapNode.appendChild(element);
            },
    
            remove: function(element) {
                jqLiteDealoc(element);
                var parent = element.parentNode;
                if (parent) parent.removeChild(element);
            },
    
            after: function(element, newElement) {
                var index = element, parent = element.parentNode;
                forEach(new JQLite(newElement), function(node){
                    parent.insertBefore(node, index.nextSibling);
                    index = node;
                });
            },
    
            addClass: jqLiteAddClass,
            removeClass: jqLiteRemoveClass,
    
            toggleClass: function(element, selector, condition) {
                if (selector) {
                    forEach(selector.split(' '), function(className){
                        var classCondition = condition;
                        if (isUndefined(classCondition)) {
                            classCondition = !jqLiteHasClass(element, className);
                        }
                        (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className);
                    });
                }
            },
    
            parent: function(element) {
                var parent = element.parentNode;
                return parent && parent.nodeType !== 11 ? parent : null;
            },
    
            next: function(element) {
                if (element.nextElementSibling) {
                    return element.nextElementSibling;
                }
    
                // IE8 doesn't have nextElementSibling
                var elm = element.nextSibling;
                while (elm != null && elm.nodeType !== 1) {
                    elm = elm.nextSibling;
                }
                return elm;
            },
    
            find: function(element, selector) {
                if (element.getElementsByTagName) {
                    return element.getElementsByTagName(selector);
                } else {
                    return [];
                }
            },
    
            clone: jqLiteClone,
    
            triggerHandler: function(element, eventName, eventData) {
                var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName];
    
                eventData = eventData || [];
    
                var event = [{
                    preventDefault: function() {
                        this.defaultPrevented = true;
                    },
                    isDefaultPrevented: function() {
                        return this.defaultPrevented === true;
                    },
                    stopPropagation: noop
                }];
    
                forEach(eventFns, function(fn) {
                    fn.apply(element, event.concat(eventData));
                });
            }
        }, function(fn, name){
            /**
             * chaining functions
             */
            JQLite.prototype[name] = function(arg1, arg2, arg3) {
                var value;
                for(var i=0; i < this.length; i++) {
                    if (isUndefined(value)) {
                        value = fn(this[i], arg1, arg2, arg3);
                        if (isDefined(value)) {
                            // any function which returns a value needs to be wrapped
                            value = jqLite(value);
                        }
                    } else {
                        jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3));
                    }
                }
                return isDefined(value) ? value : this;
            };
    
            // bind legacy bind/unbind to on/off
            JQLite.prototype.bind = JQLite.prototype.on;
            JQLite.prototype.unbind = JQLite.prototype.off;
        });
    
        /**
         * Computes a hash of an 'obj'.
         * Hash of a:
         *  string is string
         *  number is number as string
         *  object is either result of calling $$hashKey function on the object or uniquely generated id,
         *         that is also assigned to the $$hashKey property of the object.
         *
         * @param obj
         * @returns {string} hash string such that the same input will have the same hash string.
         *         The resulting string key is in 'type:hashKey' format.
         */
        function hashKey(obj, nextUidFn) {
            var objType = typeof obj,
                key;
    
            if (objType == 'function' || (objType == 'object' && obj !== null)) {
                if (typeof (key = obj.$$hashKey) == 'function') {
                    // must invoke on object to keep the right this
                    key = obj.$$hashKey();
                } else if (key === undefined) {
                    key = obj.$$hashKey = (nextUidFn || nextUid)();
                }
            } else {
                key = obj;
            }
    
            return objType + ':' + key;
        }
    
        /**
         * HashMap which can use objects as keys
         */
        function HashMap(array, isolatedUid) {
            if (isolatedUid) {
                var uid = 0;
                this.nextUid = function() {
                    return ++uid;
                };
            }
            forEach(array, this.put, this);
        }
        HashMap.prototype = {
            /**
             * Store key value pair
             * @param key key to store can be any type
             * @param value value to store can be any type
             */
            put: function(key, value) {
                this[hashKey(key, this.nextUid)] = value;
            },
    
            /**
             * @param key
             * @returns {Object} the value for the key
             */
            get: function(key) {
                return this[hashKey(key, this.nextUid)];
            },
    
            /**
             * Remove the key/value pair
             * @param key
             */
            remove: function(key) {
                var value = this[key = hashKey(key, this.nextUid)];
                delete this[key];
                return value;
            }
        };
    
        /**
         * @ngdoc function
         * @module ng
         * @name angular.injector
         * @kind function
         *
         * @description
         * Creates an injector function that can be used for retrieving services as well as for
         * dependency injection (see {@link guide/di dependency injection}).
         *
    
         * @param {Array.<string|Function>} modules A list of module functions or their aliases. See
         *        {@link angular.module}. The `ng` module must be explicitly added.
         * @returns {function()} Injector function. See {@link auto.$injector $injector}.
         *
         * @example
         * Typical usage
         * ```js
         *   // create an injector
         *   var $injector = angular.injector(['ng']);
         *
         *   // use the injector to kick off your application
         *   // use the type inference to auto inject arguments, or use implicit injection
         *   $injector.invoke(function($rootScope, $compile, $document){
     *     $compile($document)($rootScope);
     *     $rootScope.$digest();
     *   });
         * ```
         *
         * Sometimes you want to get access to the injector of a currently running Angular app
         * from outside Angular. Perhaps, you want to inject and compile some markup after the
         * application has been bootstrapped. You can do this using the extra `injector()` added
         * to JQuery/jqLite elements. See {@link angular.element}.
         *
         * *This is fairly rare but could be the case if a third party library is injecting the
         * markup.*
         *
         * In the following example a new block of HTML containing a `ng-controller`
         * directive is added to the end of the document body by JQuery. We then compile and link
         * it into the current AngularJS scope.
         *
         * ```js
         * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>');
         * $(document.body).append($div);
         *
         * angular.element(document).injector().invoke(function($compile) {
     *   var scope = angular.element($div).scope();
     *   $compile($div)(scope);
     * });
         * ```
         */
    
    
        /**
         * @ngdoc module
         * @name auto
         * @description
         *
         * Implicit module which gets automatically added to each {@link auto.$injector $injector}.
         */
    
        var FN_ARGS = /^functions*[^(]*(s*([^)]*))/m;
        var FN_ARG_SPLIT = /,/;
        var FN_ARG = /^s*(_?)(S+?)1s*$/;
        var STRIP_COMMENTS = /((//.*$)|(/*[sS]*?*/))/mg;
        var $injectorMinErr = minErr('$injector');
    
        function anonFn(fn) {
            // For anonymous functions, showing at the very least the function signature can help in
            // debugging.
            var fnText = fn.toString().replace(STRIP_COMMENTS, ''),
                args = fnText.match(FN_ARGS);
            if (args) {
                return 'function(' + (args[1] || '').replace(/[s
    ]+/, ' ') + ')';
            }
            return 'fn';
        }
    
        function annotate(fn, strictDi, name) {
            var $inject,
                fnText,
                argDecl,
                last;
    
            if (typeof fn === 'function') {
                if (!($inject = fn.$inject)) {
                    $inject = [];
                    if (fn.length) {
                        if (strictDi) {
                            if (!isString(name) || !name) {
                                name = fn.name || anonFn(fn);
                            }
                            throw $injectorMinErr('strictdi',
                                '{0} is not using explicit annotation and cannot be invoked in strict mode', name);
                        }
                        fnText = fn.toString().replace(STRIP_COMMENTS, '');
                        argDecl = fnText.match(FN_ARGS);
                        forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
                            arg.replace(FN_ARG, function(all, underscore, name){
                                $inject.push(name);
                            });
                        });
                    }
                    fn.$inject = $inject;
                }
            } else if (isArray(fn)) {
                last = fn.length - 1;
                assertArgFn(fn[last], 'fn');
                $inject = fn.slice(0, last);
            } else {
                assertArgFn(fn, 'fn', true);
            }
            return $inject;
        }
    
    ///////////////////////////////////////
    
        /**
         * @ngdoc service
         * @name $injector
         * @kind function
         *
         * @description
         *
         * `$injector` is used to retrieve object instances as defined by
         * {@link auto.$provide provider}, instantiate types, invoke methods,
         * and load modules.
         *
         * The following always holds true:
         *
         * ```js
         *   var $injector = angular.injector();
         *   expect($injector.get('$injector')).toBe($injector);
         *   expect($injector.invoke(function($injector){
     *     return $injector;
     *   }).toBe($injector);
         * ```
         *
         * # Injection Function Annotation
         *
         * JavaScript does not have annotations, and annotations are needed for dependency injection. The
         * following are all valid ways of annotating function with injection arguments and are equivalent.
         *
         * ```js
         *   // inferred (only works if code not minified/obfuscated)
         *   $injector.invoke(function(serviceA){});
         *
         *   // annotated
         *   function explicit(serviceA) {};
         *   explicit.$inject = ['serviceA'];
         *   $injector.invoke(explicit);
         *
         *   // inline
         *   $injector.invoke(['serviceA', function(serviceA){}]);
         * ```
         *
         * ## Inference
         *
         * In JavaScript calling `toString()` on a function returns the function definition. The definition
         * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
         * minification, and obfuscation tools since these tools change the argument names.
         *
         * ## `$inject` Annotation
         * By adding an `$inject` property onto a function the injection parameters can be specified.
         *
         * ## Inline
         * As an array of injection names, where the last item in the array is the function to call.
         */
    
        /**
         * @ngdoc method
         * @name $injector#get
         *
         * @description
         * Return an instance of the service.
         *
         * @param {string} name The name of the instance to retrieve.
         * @return {*} The instance.
         */
    
        /**
         * @ngdoc method
         * @name $injector#invoke
         *
         * @description
         * Invoke the method and supply the method arguments from the `$injector`.
         *
         * @param {!Function} fn The function to invoke. Function parameters are injected according to the
         *   {@link guide/di $inject Annotation} rules.
         * @param {Object=} self The `this` for the invoked method.
         * @param {Object=} locals Optional object. If preset then any argument names are read from this
         *                         object first, before the `$injector` is consulted.
         * @returns {*} the value returned by the invoked `fn` function.
         */
    
        /**
         * @ngdoc method
         * @name $injector#has
         *
         * @description
         * Allows the user to query if the particular service exists.
         *
         * @param {string} Name of the service to query.
         * @returns {boolean} returns true if injector has given service.
         */
    
        /**
         * @ngdoc method
         * @name $injector#instantiate
         * @description
         * Create a new instance of JS type. The method takes a constructor function, invokes the new
         * operator, and supplies all of the arguments to the constructor function as specified by the
         * constructor annotation.
         *
         * @param {Function} Type Annotated constructor function.
         * @param {Object=} locals Optional object. If preset then any argument names are read from this
         * object first, before the `$injector` is consulted.
         * @returns {Object} new instance of `Type`.
         */
    
        /**
         * @ngdoc method
         * @name $injector#annotate
         *
         * @description
         * Returns an array of service names which the function is requesting for injection. This API is
         * used by the injector to determine which services need to be injected into the function when the
         * function is invoked. There are three ways in which the function can be annotated with the needed
         * dependencies.
         *
         * # Argument names
         *
         * The simplest form is to extract the dependencies from the arguments of the function. This is done
         * by converting the function into a string using `toString()` method and extracting the argument
         * names.
         * ```js
         *   // Given
         *   function MyController($scope, $route) {
     *     // ...
     *   }
         *
         *   // Then
         *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
         * ```
         *
         * This method does not work with code minification / obfuscation. For this reason the following
         * annotation strategies are supported.
         *
         * # The `$inject` property
         *
         * If a function has an `$inject` property and its value is an array of strings, then the strings
         * represent names of services to be injected into the function.
         * ```js
         *   // Given
         *   var MyController = function(obfuscatedScope, obfuscatedRoute) {
     *     // ...
     *   }
         *   // Define function dependencies
         *   MyController['$inject'] = ['$scope', '$route'];
         *
         *   // Then
         *   expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
         * ```
         *
         * # The array notation
         *
         * It is often desirable to inline Injected functions and that's when setting the `$inject` property
         * is very inconvenient. In these situations using the array notation to specify the dependencies in
         * a way that survives minification is a better choice:
         *
         * ```js
         *   // We wish to write this (not minification / obfuscation safe)
         *   injector.invoke(function($compile, $rootScope) {
     *     // ...
     *   });
         *
         *   // We are forced to write break inlining
         *   var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
     *     // ...
     *   };
         *   tmpFn.$inject = ['$compile', '$rootScope'];
         *   injector.invoke(tmpFn);
         *
         *   // To better support inline function the inline annotation is supported
         *   injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
     *     // ...
     *   }]);
         *
         *   // Therefore
         *   expect(injector.annotate(
         *      ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}])
         *    ).toEqual(['$compile', '$rootScope']);
         * ```
         *
         * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
         * be retrieved as described above.
         *
         * @returns {Array.<string>} The names of the services which the function requires.
         */
    
    
    
    
        /**
         * @ngdoc service
         * @name $provide
         *
         * @description
         *
         * The {@link auto.$provide $provide} service has a number of methods for registering components
         * with the {@link auto.$injector $injector}. Many of these functions are also exposed on
         * {@link angular.Module}.
         *
         * An Angular **service** is a singleton object created by a **service factory**.  These **service
         * Angular服务是一个单例的对象通过factory创建
         * factories** are functions which, in turn, are created by a **service provider**.
         * 这个factory是一个函数,反过来说则通过服务的提供者创建
         * The **service providers** are constructor functions. When instantiated they must contain a
         * 服务提供者是一个构造函数,当需要被实例化的时候,它们则必须包含一个$get的属性
         * property called `$get`, which holds the **service factory** function.
         *包含了服务工厂函数
         * When you request a service, the {@link auto.$injector $injector} is responsible for finding the
         * correct **service provider**, instantiating it and then calling its `$get` **service factory**
         * function to get the instance of the **service**.
         *当你请求一个服务时,注入器会去查找服务提供者,实例化并且调用它的$get服务工厂获取上面请求的实例。
         * Often services have no configuration options and there is no need to add methods to the service
         * 其他的服务没有配置项并且不需要给服务提供者添加多余的方法
         * provider.  The provider will be no more than a constructor function with a `$get` property. For
         * 提供者不仅仅是一个包含$get属性的构造函数,它还包括其他的内容
         * these cases the {@link auto.$provide $provide} service has additional helper methods to register
         * services without specifying a provider.
         *
         * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the
         *     {@link auto.$injector $injector}注入器
         * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by
         *     providers and services.常量,注入一个键值对,val/objec
         * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by
         *     services, not providers.
         * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`,
         *     that will be wrapped in a **service provider** object, whose `$get` property will contain the
         *     given factory function.
         * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class`
         *     that will be wrapped in a **service provider** object, whose `$get` property will instantiate
         *      a new object using the given constructor function.
         *
         * See the individual methods for more information and examples.
         */
    
        /**
         * @ngdoc method
         * @name $provide#provider
         * @description
         *
         * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions
         * are constructor functions, whose instances are responsible for "providing" a factory for a
         * service.
         *
         * Service provider names start with the name of the service they provide followed by `Provider`.
         * For example, the {@link ng.$log $log} service has a provider called
         * {@link ng.$logProvider $logProvider}.
         *
         * Service provider objects can have additional methods which allow configuration of the provider
         * and its service. Importantly, you can configure what kind of service is created by the `$get`
         * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a
         * method {@link ng.$logProvider#debugEnabled debugEnabled}
         * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the
         * console or not.
         *
         * @param {string} name The name of the instance. NOTE: the provider will be available under `name +
         'Provider'` key.
         * @param {(Object|function())} provider If the provider is:
         *
         *   - `Object`: then it should have a `$get` method. The `$get` method will be invoked using
         *     {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created.
         *   - `Constructor`: a new instance of the provider will be created using
         *     {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`.
         *
         * @returns {Object} registered provider instance
    
         * @example
         *
         * The following example shows how to create a simple event tracking service and register it using
         * {@link auto.$provide#provider $provide.provider()}.
         *
         * ```js
         *  // Define the eventTracker provider
         *  function EventTrackerProvider() {
     *    var trackingUrl = '/track';
     *
     *    // A provider method for configuring where the tracked events should been saved
     *    this.setTrackingUrl = function(url) {
     *      trackingUrl = url;
     *    };
     *
     *    // The service factory function
     *    this.$get = ['$http', function($http) {
     *      var trackedEvents = {};
     *      return {
     *        // Call this to track an event
     *        event: function(event) {
     *          var count = trackedEvents[event] || 0;
     *          count += 1;
     *          trackedEvents[event] = count;
     *          return count;
     *        },
     *        // Call this to save the tracked events to the trackingUrl
     *        save: function() {
     *          $http.post(trackingUrl, trackedEvents);
     *        }
     *      };
     *    }];
     *  }
         *
         *  describe('eventTracker', function() {
     *    var postSpy;
     *
     *    beforeEach(module(function($provide) {
     *      // Register the eventTracker provider
     *      $provide.provider('eventTracker', EventTrackerProvider);
     *    }));
     *
     *    beforeEach(module(function(eventTrackerProvider) {
     *      // Configure eventTracker provider
     *      eventTrackerProvider.setTrackingUrl('/custom-track');
     *    }));
     *
     *    it('tracks events', inject(function(eventTracker) {
     *      expect(eventTracker.event('login')).toEqual(1);
     *      expect(eventTracker.event('login')).toEqual(2);
     *    }));
     *
     *    it('saves to the tracking url', inject(function(eventTracker, $http) {
     *      postSpy = spyOn($http, 'post');
     *      eventTracker.event('login');
     *      eventTracker.save();
     *      expect(postSpy).toHaveBeenCalled();
     *      expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track');
     *      expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track');
     *      expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 });
     *    }));
     *  });
         * ```
         */
    
        /**
         * @ngdoc method
         * @name $provide#factory
         * @description
         *
         * Register a **service factory**, which will be called to return the service instance.
         * This is short for registering a service where its provider consists of only a `$get` property,
         * which is the given service factory function.
         * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to
         * configure your service in a provider.
         *
         * @param {string} name The name of the instance.
         * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
         *                            for `$provide.provider(name, {$get: $getFn})`.
         * @returns {Object} registered provider instance
         *
         * @example
         * Here is an example of registering a service
         * ```js
         *   $provide.factory('ping', ['$http', function($http) {
     *     return function ping() {
     *       return $http.send('/ping');
     *     };
     *   }]);
         * ```
         * You would then inject and use this service like this:
         * ```js
         *   someModule.controller('Ctrl', ['ping', function(ping) {
     *     ping();
     *   }]);
         * ```
         */
    
    
        /**
         * @ngdoc method
         * @name $provide#service
         * @description
         *
         * Register a **service constructor**, which will be invoked with `new` to create the service
         * instance.
         * This is short for registering a service where its provider's `$get` property is the service
         * constructor function that will be used to instantiate the service instance.
         *
         * You should use {@link auto.$provide#service $provide.service(class)} if you define your service
         * as a type/class.
         *
         * @param {string} name The name of the instance.
         * @param {Function} constructor A class (constructor function) that will be instantiated.
         * @returns {Object} registered provider instance
         *
         * @example
         * Here is an example of registering a service using
         * {@link auto.$provide#service $provide.service(class)}.
         * ```js
         *   var Ping = function($http) {
     *     this.$http = $http;
     *   };
         *
         *   Ping.$inject = ['$http'];
         *
         *   Ping.prototype.send = function() {
     *     return this.$http.get('/ping');
     *   };
         *   $provide.service('ping', Ping);
         * ```
         * You would then inject and use this service like this:
         * ```js
         *   someModule.controller('Ctrl', ['ping', function(ping) {
     *     ping.send();
     *   }]);
         * ```
         */
    
    
        /**
         * @ngdoc method
         * @name $provide#value
         * @description
         *
         * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a
         * number, an array, an object or a function.  This is short for registering a service where its
         * provider's `$get` property is a factory function that takes no arguments and returns the **value
         * service**.
         *
         * Value services are similar to constant services, except that they cannot be injected into a
         * module configuration function (see {@link angular.Module#config}) but they can be overridden by
         * an Angular
         * {@link auto.$provide#decorator decorator}.
         *
         * @param {string} name The name of the instance.
         * @param {*} value The value.
         * @returns {Object} registered provider instance
         *
         * @example
         * Here are some examples of creating value services.
         * ```js
         *   $provide.value('ADMIN_USER', 'admin');
         *
         *   $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 });
         *
         *   $provide.value('halfOf', function(value) {
     *     return value / 2;
     *   });
         * ```
         */
    
    
        /**
         * @ngdoc method
         * @name $provide#constant
         * @description
         *
         * Register a **constant service**, such as a string, a number, an array, an object or a function,
         * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be
         * injected into a module configuration function (see {@link angular.Module#config}) and it cannot
         * be overridden by an Angular {@link auto.$provide#decorator decorator}.
         *
         * @param {string} name The name of the constant.
         * @param {*} value The constant value.
         * @returns {Object} registered instance
         *
         * @example
         * Here a some examples of creating constants:
         * ```js
         *   $provide.constant('SHARD_HEIGHT', 306);
         *
         *   $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']);
         *
         *   $provide.constant('double', function(value) {
     *     return value * 2;
     *   });
         * ```
         */
    
    
        /**
         * @ngdoc method
         * @name $provide#decorator
         * @description
         *
         * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator
         * intercepts the creation of a service, allowing it to override or modify the behaviour of the
         * service. The object returned by the decorator may be the original service, or a new service
         * object which replaces or wraps and delegates to the original service.
         *
         * @param {string} name The name of the service to decorate.
         * @param {function()} decorator This function will be invoked when the service needs to be
         *    instantiated and should return the decorated service instance. The function is called using
         *    the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
         *    Local injection arguments:
         *
         *    * `$delegate` - The original service instance, which can be monkey patched, configured,
         *      decorated or delegated to.
         *
         * @example
         * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting
         * calls to {@link ng.$log#error $log.warn()}.
         * ```js
         *   $provide.decorator('$log', ['$delegate', function($delegate) {
     *     $delegate.warn = $delegate.error;
     *     return $delegate;
     *   }]);
         * ```
         */
    
    
        function createInjector(modulesToLoad, strictDi) {
            strictDi = (strictDi === true);
            var INSTANTIATING = {},
                providerSuffix = 'Provider',
                path = [],
                loadedModules = new HashMap([], true),
                providerCache = {
                    $provide: {
                        provider: supportObject(provider),
                        factory: supportObject(factory),
                        service: supportObject(service),
                        value: supportObject(value),
                        constant: supportObject(constant),
                        decorator: decorator
                    }
                },
                providerInjector = (providerCache.$injector =
                    createInternalInjector(providerCache, function() {
                        throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
                    }, strictDi)),
                instanceCache = {},
                instanceInjector = (instanceCache.$injector =
                    createInternalInjector(instanceCache, function(servicename) {
                        var provider = providerInjector.get(servicename + providerSuffix);
                        return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
                    }, strictDi));
    
    
            forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
    
            return instanceInjector;
    
            ////////////////////////////////////
            // $provider
            ////////////////////////////////////
    
            function supportObject(delegate) {
                return function(key, value) {
                    if (isObject(key)) {
                        forEach(key, reverseParams(delegate));
                    } else {
                        return delegate(key, value);
                    }
                };
            }
    
            function provider(name, provider_) {
                assertNotHasOwnProperty(name, 'service');
                if (isFunction(provider_) || isArray(provider_)) {
                    provider_ = providerInjector.instantiate(provider_);
                }
                if (!provider_.$get) {
                    throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name);
                }
                return providerCache[name + providerSuffix] = provider_;
            }
    
            function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); }
    
            function service(name, constructor) {
                return factory(name, ['$injector', function($injector) {
                    return $injector.instantiate(constructor);
                }]);
            }
    
            function value(name, val) { return factory(name, valueFn(val)); }
    
            function constant(name, value) {
                assertNotHasOwnProperty(name, 'constant');
                providerCache[name] = value;
                instanceCache[name] = value;
            }
    
            function decorator(serviceName, decorFn) {
                var origProvider = providerInjector.get(serviceName + providerSuffix),
                    orig$get = origProvider.$get;
    
                origProvider.$get = function() {
                    var origInstance = instanceInjector.invoke(orig$get, origProvider);
                    return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
                };
            }
    
            ////////////////////////////////////
            // Module Loading
            ////////////////////////////////////
            function loadModules(modulesToLoad){
                var runBlocks = [], moduleFn, invokeQueue;
                forEach(modulesToLoad, function(module) {
                    if (loadedModules.get(module)) return;
                    loadedModules.put(module, true);
    
                    function runInvokeQueue(queue) {
                        var i, ii;
                        for(i = 0, ii = queue.length; i < ii; i++) {
                            var invokeArgs = queue[i],
                                provider = providerInjector.get(invokeArgs[0]);
    
                            provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
                        }
                    }
    
                    try {
                        if (isString(module)) {
                            moduleFn = angularModule(module);
                            runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
                            runInvokeQueue(moduleFn._invokeQueue);
                            runInvokeQueue(moduleFn._configBlocks);
                        } else if (isFunction(module)) {
                            runBlocks.push(providerInjector.invoke(module));
                        } else if (isArray(module)) {
                            runBlocks.push(providerInjector.invoke(module));
                        } else {
                            assertArgFn(module, 'module');
                        }
                    } catch (e) {
                        if (isArray(module)) {
                            module = module[module.length - 1];
                        }
                        if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
                            // Safari & FF's stack traces don't contain error.message content
                            // unlike those of Chrome and IE
                            // So if stack doesn't contain message, we create a new string that contains both.
                            // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here.
                            /* jshint -W022 */
                            e = e.message + '
    ' + e.stack;
                        }
                        throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:
    {1}",
                            module, e.stack || e.message || e);
                    }
                });
                return runBlocks;
            }
    
            ////////////////////////////////////
            // internal Injector
            ////////////////////////////////////
    
            function createInternalInjector(cache, factory) {
    
                function getService(serviceName) {
                    if (cache.hasOwnProperty(serviceName)) {
                        if (cache[serviceName] === INSTANTIATING) {
                            throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
                                serviceName + ' <- ' + path.join(' <- '));
                        }
                        return cache[serviceName];
                    } else {
                        try {
                            path.unshift(serviceName);
                            cache[serviceName] = INSTANTIATING;
                            return cache[serviceName] = factory(serviceName);
                        } catch (err) {
                            if (cache[serviceName] === INSTANTIATING) {
                                delete cache[serviceName];
                            }
                            throw err;
                        } finally {
                            path.shift();
                        }
                    }
                }
    
                function invoke(fn, self, locals, serviceName){
                    if (typeof locals === 'string') {
                        serviceName = locals;
                        locals = null;
                    }
    
                    var args = [],
                        $inject = annotate(fn, strictDi, serviceName),
                        length, i,
                        key;
    
                    for(i = 0, length = $inject.length; i < length; i++) {
                        key = $inject[i];
                        if (typeof key !== 'string') {
                            throw $injectorMinErr('itkn',
                                'Incorrect injection token! Expected service name as string, got {0}', key);
                        }
                        args.push(
                            locals && locals.hasOwnProperty(key)
                                ? locals[key]
                                : getService(key)
                        );
                    }
                    if (isArray(fn)) {
                        fn = fn[length];
                    }
    
                    // http://jsperf.com/angularjs-invoke-apply-vs-switch
                    // #5388
                    return fn.apply(self, args);
                }
    
                function instantiate(Type, locals, serviceName) {
                    var Constructor = function() {},
                        instance, returnedValue;
    
                    // Check if Type is annotated and use just the given function at n-1 as parameter
                    // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
                    Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
                    instance = new Constructor();
                    returnedValue = invoke(Type, instance, locals, serviceName);
    
                    return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
                }
    
                return {
                    invoke: invoke,
                    instantiate: instantiate,
                    get: getService,
                    annotate: annotate,
                    has: function(name) {
                        return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
                    }
                };
            }
        }
    
        createInjector.$$annotate = annotate;
    
        /**
         * @ngdoc service
         * @name $anchorScroll
         * @kind function
         * @requires $window
         * @requires $location
         * @requires $rootScope
         *
         * @description
         * When called, it checks current value of `$location.hash()` and scrolls to the related element,
         * according to rules specified in
         * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
         *
         * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
         * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
         *
         * @example
         <example module="anchorScrollExample">
         <file name="index.html">
         <div id="scrollArea" ng-controller="ScrollController">
         <a ng-click="gotoBottom()">Go to bottom</a>
         <a id="bottom"></a> You're at the bottom!
         </div>
         </file>
         <file name="script.js">
         angular.module('anchorScrollExample', [])
         .controller('ScrollController', ['$scope', '$location', '$anchorScroll',
         function ($scope, $location, $anchorScroll) {
                 $scope.gotoBottom = function() {
                   // set the location.hash to the id of
                   // the element you wish to scroll to.
                   $location.hash('bottom');
    
                   // call $anchorScroll()
                   $anchorScroll();
                 };
               }]);
         </file>
         <file name="style.css">
         #scrollArea {
             height: 350px;
             overflow: auto;
           }
    
         #bottom {
             display: block;
             margin-top: 2000px;
           }
         </file>
         </example>
         */
        function $AnchorScrollProvider() {
    
            var autoScrollingEnabled = true;
    
            this.disableAutoScrolling = function() {
                autoScrollingEnabled = false;
            };
    
            this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
                var document = $window.document;
    
                // helper function to get first anchor from a NodeList
                // can't use filter.filter, as it accepts only instances of Array
                // and IE can't convert NodeList to an array using [].slice
                // TODO(vojta): use filter if we change it to accept lists as well
                function getFirstAnchor(list) {
                    var result = null;
                    forEach(list, function(element) {
                        if (!result && nodeName_(element) === 'a') result = element;
                    });
                    return result;
                }
    
                function scroll() {
                    var hash = $location.hash(), elm;
    
                    // empty hash, scroll to the top of the page
                    if (!hash) $window.scrollTo(0, 0);
    
                    // element with given id
                    else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
    
                    // first anchor with given name :-D
                    else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
    
                    // no element and hash == 'top', scroll to the top of the page
                    else if (hash === 'top') $window.scrollTo(0, 0);
                }
    
                // does not scroll when user clicks on anchor link that is currently on
                // (no url change, no $location.hash() change), browser native does scroll
                if (autoScrollingEnabled) {
                    $rootScope.$watch(function autoScrollWatch() {return $location.hash();},
                        function autoScrollWatchAction() {
                            $rootScope.$evalAsync(scroll);
                        });
                }
    
                return scroll;
            }];
        }
    
        var $animateMinErr = minErr('$animate');
    
        /**
         * @ngdoc provider
         * @name $animateProvider
         *
         * @description
         * Default implementation of $animate that doesn't perform any animations, instead just
         * synchronously performs DOM
         * updates and calls done() callbacks.
         *
         * In order to enable animations the ngAnimate module has to be loaded.
         *
         * To see the functional implementation check out src/ngAnimate/animate.js
         */
        var $AnimateProvider = ['$provide', function($provide) {
    
    
            this.$$selectors = {};
    
    
            /**
             * @ngdoc method
             * @name $animateProvider#register
             *
             * @description
             * Registers a new injectable animation factory function. The factory function produces the
             * animation object which contains callback functions for each event that is expected to be
             * animated.
             *
             *   * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction`
             *   must be called once the element animation is complete. If a function is returned then the
             *   animation service will use this function to cancel the animation whenever a cancel event is
             *   triggered.
             *
             *
             * ```js
             *   return {
         *     eventFn : function(element, done) {
         *       //code to run the animation
         *       //once complete, then run done()
         *       return function cancellationFunction() {
         *         //code to cancel the animation
         *       }
         *     }
         *   }
             * ```
             *
             * @param {string} name The name of the animation.
             * @param {Function} factory The factory function that will be executed to return the animation
             *                           object.
             */
            this.register = function(name, factory) {
                var key = name + '-animation';
                if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel',
                    "Expecting class selector starting with '.' got '{0}'.", name);
                this.$$selectors[name.substr(1)] = key;
                $provide.factory(key, factory);
            };
    
            /**
             * @ngdoc method
             * @name $animateProvider#classNameFilter
             *
             * @description
             * Sets and/or returns the CSS class regular expression that is checked when performing
             * an animation. Upon bootstrap the classNameFilter value is not set at all and will
             * therefore enable $animate to attempt to perform an animation on any element.
             * When setting the classNameFilter value, animations will only be performed on elements
             * that successfully match the filter expression. This in turn can boost performance
             * for low-powered devices as well as applications containing a lot of structural operations.
             * @param {RegExp=} expression The className expression which will be checked against all animations
             * @return {RegExp} The current CSS className expression value. If null then there is no expression value
             */
            this.classNameFilter = function(expression) {
                if(arguments.length === 1) {
                    this.$$classNameFilter = (expression instanceof RegExp) ? expression : null;
                }
                return this.$$classNameFilter;
            };
    
            this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) {
    
                function async(fn) {
                    fn && $$asyncCallback(fn);
                }
    
                /**
                 *
                 * @ngdoc service
                 * @name $animate
                 * @description The $animate service provides rudimentary DOM manipulation functions to
                 * insert, remove and move elements within the DOM, as well as adding and removing classes.
                 * This service is the core service used by the ngAnimate $animator service which provides
                 * high-level animation hooks for CSS and JavaScript.
                 *
                 * $animate is available in the AngularJS core, however, the ngAnimate module must be included
                 * to enable full out animation support. Otherwise, $animate will only perform simple DOM
                 * manipulation operations.
                 *
                 * To learn more about enabling animation support, click here to visit the {@link ngAnimate
         * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service
         * page}.
                 */
                return {
    
                    /**
                     *
                     * @ngdoc method
                     * @name $animate#enter
                     * @kind function
                     * @description Inserts the element into the DOM either after the `after` element or
                     * as the first child within the `parent` element. Once complete, the done() callback
                     * will be fired (if provided).
                     * @param {DOMElement} element the element which will be inserted into the DOM
                     * @param {DOMElement} parent the parent element which will append the element as
                     *   a child (if the after element is not present)
                     * @param {DOMElement} after the sibling element which will append the element
                     *   after itself
                     * @param {Function=} done callback function that will be called after the element has been
                     *   inserted into the DOM
                     */
                    enter : function(element, parent, after, done) {
                        after
                            ? after.after(element)
                            : parent.prepend(element);
                        async(done);
                        return noop;
                    },
    
                    /**
                     *
                     * @ngdoc method
                     * @name $animate#leave
                     * @kind function
                     * @description Removes the element from the DOM. Once complete, the done() callback will be
                     *   fired (if provided).
                     * @param {DOMElement} element the element which will be removed from the DOM
                     * @param {Function=} done callback function that will be called after the element has been
                     *   removed from the DOM
                     */
                    leave : function(element, done) {
                        element.remove();
                        async(done);
                        return noop;
                    },
    
                    /**
                     *
                     * @ngdoc method
                     * @name $animate#move
                     * @kind function
                     * @description Moves the position of the provided element within the DOM to be placed
                     * either after the `after` element or inside of the `parent` element. Once complete, the
                     * done() callback will be fired (if provided).
                     *
                     * @param {DOMElement} element the element which will be moved around within the
                     *   DOM
                     * @param {DOMElement} parent the parent element where the element will be
                     *   inserted into (if the after element is not present)
                     * @param {DOMElement} after the sibling element where the element will be
                     *   positioned next to
                     * @param {Function=} done the callback function (if provided) that will be fired after the
                     *   element has been moved to its new position
                     */
                    move : function(element, parent, after, done) {
                        // Do not remove element before insert. Removing will cause data associated with the
                        // element to be dropped. Insert will implicitly do the remove.
                        return this.enter(element, parent, after, done);
                    },
    
                    /**
                     *
                     * @ngdoc method
                     * @name $animate#addClass
                     * @kind function
                     * @description Adds the provided className CSS class value to the provided element. Once
                     * complete, the done() callback will be fired (if provided).
                     * @param {DOMElement} element the element which will have the className value
                     *   added to it
                     * @param {string} className the CSS class which will be added to the element
                     * @param {Function=} done the callback function (if provided) that will be fired after the
                     *   className value has been added to the element
                     */
                    addClass : function(element, className, done) {
                        className = !isString(className)
                            ? (isArray(className) ? className.join(' ') : '')
                            : className;
                        forEach(element, function (element) {
                            jqLiteAddClass(element, className);
                        });
                        async(done);
                        return noop;
                    },
    
                    /**
                     *
                     * @ngdoc method
                     * @name $animate#removeClass
                     * @kind function
                     * @description Removes the provided className CSS class value from the provided element.
                     * Once complete, the done() callback will be fired (if provided).
                     * @param {DOMElement} element the element which will have the className value
                     *   removed from it
                     * @param {string} className the CSS class which will be removed from the element
                     * @param {Function=} done the callback function (if provided) that will be fired after the
                     *   className value has been removed from the element
                     */
                    removeClass : function(element, className, done) {
                        className = isString(className) ?
                            className :
                            isArray(className) ? className.join(' ') : '';
                        forEach(element, function (element) {
                            jqLiteRemoveClass(element, className);
                        });
                        async(done);
                        return noop;
                    },
    
                    /**
                     *
                     * @ngdoc method
                     * @name $animate#setClass
                     * @kind function
                     * @description Adds and/or removes the given CSS classes to and from the element.
                     * Once complete, the done() callback will be fired (if provided).
                     * @param {DOMElement} element the element which will have its CSS classes changed
                     *   removed from it
                     * @param {string} add the CSS classes which will be added to the element
                     * @param {string} remove the CSS class which will be removed from the element
                     * @param {Function=} done the callback function (if provided) that will be fired after the
                     *   CSS classes have been set on the element
                     */
                    setClass : function(element, add, remove, done) {
                        forEach(element, function (element) {
                            jqLiteAddClass(element, add);
                            jqLiteRemoveClass(element, remove);
                        });
                        async(done);
                        return noop;
                    },
    
                    enabled : noop
                };
            }];
        }];
    
        function $$AsyncCallbackProvider(){
            this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) {
                return $$rAF.supported
                    ? function(fn) { return $$rAF(fn); }
                    : function(fn) {
                    return $timeout(fn, 0, false);
                };
            }];
        }
    
        /**
         * ! This is a private undocumented service !
         *
         * @name $browser
         * @requires $log
         * @description
         * This object has two goals:
         *
         * - hide all the global state in the browser caused by the window object
         * - abstract away all the browser specific features and inconsistencies
         *
         * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
         * service, which can be used for convenient testing of the application without the interaction with
         * the real browser apis.
         */
        /**
         * @param {object} window The global window object.
         * @param {object} document jQuery wrapped document.
         * @param {function()} XHR XMLHttpRequest constructor.
         * @param {object} $log console.log or an object with the same interface.
         * @param {object} $sniffer $sniffer service
         */
        function Browser(window, document, $log, $sniffer) {
            var self = this,
                rawDocument = document[0],
                location = window.location,
                history = window.history,
                setTimeout = window.setTimeout,
                clearTimeout = window.clearTimeout,
                pendingDeferIds = {};
    
            self.isMock = false;
    
            var outstandingRequestCount = 0;
            var outstandingRequestCallbacks = [];
    
            // TODO(vojta): remove this temporary api
            self.$$completeOutstandingRequest = completeOutstandingRequest;
            self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
    
            /**
             * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
             * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed.
             */
            function completeOutstandingRequest(fn) {
                try {
                    fn.apply(null, sliceArgs(arguments, 1));
                } finally {
                    outstandingRequestCount--;
                    if (outstandingRequestCount === 0) {
                        while(outstandingRequestCallbacks.length) {
                            try {
                                outstandingRequestCallbacks.pop()();
                            } catch (e) {
                                $log.error(e);
                            }
                        }
                    }
                }
            }
    
            /**
             * @private
             * Note: this method is used only by scenario runner
             * TODO(vojta): prefix this method with $$ ?
             * @param {function()} callback Function that will be called when no outstanding request
             */
            self.notifyWhenNoOutstandingRequests = function(callback) {
                // force browser to execute all pollFns - this is needed so that cookies and other pollers fire
                // at some deterministic time in respect to the test runner's actions. Leaving things up to the
                // regular poller would result in flaky tests.
                forEach(pollFns, function(pollFn){ pollFn(); });
    
                if (outstandingRequestCount === 0) {
                    callback();
                } else {
                    outstandingRequestCallbacks.push(callback);
                }
            };
    
    查看代码
    
     * License: MIT
     */
    (function(window, document, undefined) {'use strict';
    
        /**
         * @description
         *
         * This object provides a utility for producing rich Error messages within
         * Angular. It can be called as follows:
         *当Angular在发生错误的时候,这个对象提供一个处理错误的工具类,它的调用方式如下:
         * 例如:
         * var exampleMinErr = minErr('example');
         * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
         *
         * The above creates an instance of minErr in the example namespace. The
         * resulting error will have a namespaced error code of example.one.  The
         * resulting error will replace {0} with the value of foo, and {1} with the
         * value of bar. The object is not restricted in the number of arguments it can
         * take.
         *在上面的代码中,在example命名空间中创建了一个minErr的实例。错误的结果会在example,
         *里面生成一个错误码的example.one命名空间。这个错误的结果会用foo的值和bar的值,
         *来进行替换前面的点位符{0},{1}.这个对象不严格必须给定错误参数,因此错误参数可有可无。
         *
         * If fewer arguments are specified than necessary for interpolation, the extra
         * interpolation markers will be preserved in the final string.
         *如果指定的错误对象中的错误参数,则它会保留在最终生成的字符串中。
         *
         * Since data will be parsed statically during a build step, some restrictions
         * are applied with respect to how minErr instances are created and called.
         * Instances should have names of the form namespaceMinErr for a minErr created
         * using minErr('namespace') . Error codes, namespaces and template strings
         * should all be static strings, not variables or general expressions.
         *在编译阶段,错误参数中的数据会自己生成与解析,错误对象中的一些限制将会监视错误对象的
         实例该如何被创建与如何被调用。
         *
         * @param {string} module The namespace to use for the new minErr instance.
         * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
         */
    
    //module:模板名称,将会用于新的错误对象的实例中
        function minErr(module) {
            return function () {//直接进行返回操作
                //获取错误参数中的错误标识符,如,上面示例中的one
                var code = arguments[0],
                //获取错误对象模板的前缀
                    prefix = '[' + (module ? module + ':' : '') + code + '] ',
                    template = arguments[1],
                    templateArgs = arguments,
                    //这个属于偏置函数
                    stringify = function (obj) {
                        if (typeof obj === 'function') {//如果参数是一个函数
                            //返回的结果,如:{'item in items '}-->''
                            return obj.toString().replace(/ {[sS]*$/, '');
                        } else if (typeof obj === 'undefined') {
                            return 'undefined';
                        } else if (typeof obj !== 'string') {
                            return JSON.stringify(obj);//强制参数转换为Json的字符串形式
                        }
                        return obj;
                    },
                    message, i;
    
    
                message = prefix + template.replace(/{d+}/g, function (match) {
                    var index = +match.slice(1, -1), arg;
    
                    if (index + 2 < templateArgs.length) {
                        arg = templateArgs[index + 2];
                        if (typeof arg === 'function') {
                            return arg.toString().replace(/ ?{[sS]*$/, '');
                        } else if (typeof arg === 'undefined') {
                            return 'undefined';
                        } else if (typeof arg !== 'string') {
                            return toJson(arg);
                        }
                        return arg;
                    }
                    return match;
                });
    
                message = message + '
    http://errors.angularjs.org/1.3.0-beta.15/' +
                (module ? module + '/' : '') + code;
                for (i = 2; i < arguments.length; i++) {
                    message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
                    encodeURIComponent(stringify(arguments[i]));
                }
    
                return new Error(message);
            };
        }
    
        /* We need to tell jshint what variables are being exported */
        /* global angular: true,
         msie: true,
         jqLite: true,
         jQuery: true,
         slice: true,
         push: true,
         toString: true,
         ngMinErr: true,
         angularModule: true,
         nodeName_: true,
         uid: true,
         REGEX_STRING_REGEXP: true,
         VALIDITY_STATE_PROPERTY: true,
    
         lowercase: true,
         uppercase: true,
         manualLowercase: true,
         manualUppercase: true,
         nodeName_: true,
         isArrayLike: true,
         forEach: true,
         sortedKeys: true,
         forEachSorted: true,
         reverseParams: true,
         nextUid: true,
         setHashKey: true,
         extend: true,
         int: true,
         inherit: true,
         noop: true,
         identity: true,
         valueFn: true,
         isUndefined: true,
         isDefined: true,
         isObject: true,
         isString: true,
         isNumber: true,
         isDate: true,
         isArray: true,
         isFunction: true,
         isRegExp: true,
         isWindow: true,
         isScope: true,
         isFile: true,
         isBlob: true,
         isBoolean: true,
         trim: true,
         isElement: true,
         makeMap: true,
         map: true,
         size: true,
         includes: true,
         indexOf: true,
         arrayRemove: true,
         isLeafNode: true,
         copy: true,
         shallowCopy: true,
         equals: true,
         csp: true,
         concat: true,
         sliceArgs: true,
         bind: true,
         toJsonReplacer: true,
         toJson: true,
         fromJson: true,
         startingTag: true,
         tryDecodeURIComponent: true,
         parseKeyValue: true,
         toKeyValue: true,
         encodeUriSegment: true,
         encodeUriQuery: true,
         angularInit: true,
         bootstrap: true,
         snake_case: true,
         bindJQuery: true,
         assertArg: true,
         assertArgFn: true,
         assertNotHasOwnProperty: true,
         getter: true,
         getBlockElements: true,
         hasOwnProperty: true,
         */
    
    ////////////////////////////////////
    
        /**
         * @ngdoc module
         * @name ng
         * @module ng
         * @description
         *
         * # ng (core module)
         * The ng module is loaded by default when an AngularJS application is started. The module itself
         * contains the essential components for an AngularJS application to function. The table below
         * lists a high level breakdown of each of the services/factories, filters, directives and testing
         * components available within this core module.
         *
         * <div doc-module-components="ng"></div>
         *
         * ng模板默认在Angular应用程序启动的时候会被自动加载。这个模板包含了Angular应用程序运行所必须的组件。
         *  下表列出了在这个核心的模块中所包含的服务/工厂,过滤器,指令和一些测试的组件
         */
    
        var REGEX_STRING_REGEXP = /^/(.+)/([a-z]*)$/;
    
    // The name of a form control's ValidityState property.
    // This is used so that it's possible for internal tests to create mock ValidityStates.
        var VALIDITY_STATE_PROPERTY = 'validity';
    
        /**
         * @ngdoc function
         * @name angular.lowercase
         * @module ng
         * @kind function
         *
         * @description Converts the specified string to lowercase.
         * @param {string} string String to be converted to lowercase.
         * @returns {string} Lowercased string.
         */
            //转换指定的字符串为小写
        var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
        var hasOwnProperty = Object.prototype.hasOwnProperty;
    
        /**
         * @ngdoc function
         * @name angular.uppercase
         * @module ng
         * @kind function
         *
         * @description Converts the specified string to uppercase.
         * @param {string} string String to be converted to uppercase.
         * @returns {string} Uppercased string.
         */
            //转换指定的字符串为大写
        var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};
    
    
        var manualLowercase = function(s) {
            /* jshint bitwise: false */
            return isString(s)
                ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
                : s;
        };
        var manualUppercase = function(s) {
            /* jshint bitwise: false */
            return isString(s)
                ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
                : s;
        };
    
    
    // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
    // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
    // with correct but slower alternatives.
        if ('i' !== 'I'.toLowerCase()) {
            lowercase = manualLowercase;
            uppercase = manualUppercase;
        }
    
    
        var /** holds major version number for IE or NaN for real browsers */
            msie,
            jqLite,           // delay binding since jQuery could be loaded after us.
            jQuery,           // delay binding
            slice             = [].slice,//取出指定位置的数组数据
            push              = [].push,//把指定的数据放到数据里面
            toString          = Object.prototype.toString,//重写Object原型中的toString方法
            ngMinErr          = minErr('ng'),//定义一个全局的错误对象,对应的错误参数为,ng
    
            /** @name angular */
                //判断系统是否注册了angular应用程序,如果没有注册则注册angular应用程序的为空对象
            angular           = window.angular || (window.angular = {}),
            angularModule,
            nodeName_,
            uid               = 0;
    
        /**
         * IE 11 changed the format of the UserAgent string.
         * See http://msdn.microsoft.com/en-us/library/ms537503.aspx
         */
        //判断浏览器的类型
        msie = int((/msie (d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
        if (isNaN(msie)) {
            msie = int((/trident/.*; rv:(d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
        }
    
    
        /**
         * @private
         * @param {*} obj
         * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
         *                   String ...)
         */
        //判断传入的对象obj是否是数组或有类似数组的行为,它可以是json字符串或数组
        function isArrayLike(obj) {
            if (obj == null || isWindow(obj)) {//为空或window对象,则返回false
                return false;
            }
    
            var length = obj.length;//获取数组的长度
    
            if (obj.nodeType === 1 && length) {//判断obj的节点类型,如果为元素,则返回true
                return true;
            }
    
            return isString(obj) || isArray(obj) || length === 0 ||
                typeof length === 'number' && length > 0 && (length - 1) in obj;
        }
    
        /**
         * @ngdoc function
         * @name angular.forEach
         * @module ng
         * @kind function
         *
         * @description
         * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
         * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
         * is the value of an object property or an array element and `key` is the object property key or
         * array element index. Specifying a `context` for the function is optional.
         *
         * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
         * using the `hasOwnProperty` method.
         *
         ```js
         var values = {name: 'misko', gender: 'male'};
         var log = [];
         angular.forEach(values, function(value, key) {
           this.push(key + ': ' + value);
         }, log);
         expect(log).toEqual(['name: misko', 'gender: male']);
         ```
         *
         * @param {Object|Array} obj Object to iterate over.//可以是一个对象或一个数组
         * @param {Function} iterator Iterator function.//迭代器是一个函数
         * @param {Object=} context Object to become context (`this`) for the iterator function.//这个对象是一个上下文对象充当this的角色
         * @returns {Object|Array} Reference to `obj`.
         */
        //其中context可选参数,iterator是一个迭代器,它是一个函数
        //它不能获取的继承的属性,因为程序中用hasOwnProperty来过滤了
        function forEach(obj, iterator, context) {
            var key, length;
            if (obj) {
                if (isFunction(obj)) {
                    for (key in obj) {
                        // Need to check if hasOwnProperty exists,
                        // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
                        if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
                            iterator.call(context, obj[key], key);
                        }
                    }
                } else if (isArray(obj) || isArrayLike(obj)) {
                    for (key = 0, length = obj.length; key < length; key++) {
                        iterator.call(context, obj[key], key);
                    }
                } else if (obj.forEach && obj.forEach !== forEach) {
                    obj.forEach(iterator, context);
                } else {
                    for (key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            iterator.call(context, obj[key], key);
                        }
                    }
                }
            }
            return obj;
        }
    
        function sortedKeys(obj) {//按照键来排序
            var keys = [];
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    keys.push(key);
                }
            }
            return keys.sort();//采用数组默认的排序方式
        }
    
        function forEachSorted(obj, iterator, context) {//先按照键排序,然后再遍历|迭代
            var keys = sortedKeys(obj);
            for ( var i = 0; i < keys.length; i++) {
                iterator.call(context, obj[keys[i]], keys[i]);
            }
            return keys;
        }
    
    
        /**
         * when using forEach the params are value, key, but it is often useful to have key, value.
         * @param {function(string, *)} iteratorFn
         * @returns {function(*, string)}
         */
        function reverseParams(iteratorFn) {
            return function(value, key) { iteratorFn(key, value); };
        }
    
        /**
         * A consistent way of creating unique IDs in angular.
         *
         * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
         * we hit number precision issues in JavaScript.
         *
         * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
         *
         * @returns {number} an unique alpha-numeric string
         */
        function nextUid() {//生成一个常量,它在andular里面是唯一的
            return ++uid;
        }
    
    
        /**
         * Set or clear the hashkey for an object.
         * @param obj object//参数是一个对象
         * @param h the hashkey (!truthy to delete the hashkey)
         */
        function setHashKey(obj, h) {
            if (h) {//如果存在就设置
                obj.$$hashKey = h;
            }
            else {//如果不存在则删除
                delete obj.$$hashKey;
            }
        }
    
        /**
         * @ngdoc function
         * @name angular.extend
         * @module ng
         * @kind function
         *
         * @description
         * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
         * to `dst`. You can specify multiple `src` objects.
         *
         * @param {Object} dst Destination object.//最终的对象
         * @param {...Object} src Source object(s).//源对象
         * @returns {Object} Reference to `dst`.//返回最终的对象
         */
        function extend(dst) {//在javascript中实现继承
            var h = dst.$$hashKey;
            forEach(arguments, function(obj) {//这里采用拷贝属性的方式实现继承
                if (obj !== dst) {
                    forEach(obj, function(value, key) {
                        dst[key] = value;
                    });
                }
            });
    
            setHashKey(dst,h);
            return dst;
        }
    
        function int(str) {//转换为整形
            return parseInt(str, 10);
        }
    
    
        function inherit(parent, extra) {//继承
            return extend(new (extend(function() {}, {prototype:parent}))(), extra);
        }
    
        /**
         * @ngdoc function
         * @name angular.noop
         * @module ng
         * @kind function
         *
         * @description
         * A function that performs no operations. This function can be useful when writing code in the
         * functional style.
         ```js
         function foo(callback) {
           var result = calculateResult();
           (callback || angular.noop)(result);
         }
         ```
         */
        function noop() {}//在写样式代码的时候会被用到
        noop.$inject = [];
    
    
        /**
         * @ngdoc function
         * @name angular.identity
         * @module ng
         * @kind function
         *
         * @description
         * A function that returns its first argument. This function is useful when writing code in the
         * functional style.
         *
         ```js
         function transformer(transformationFn, value) {
           return (transformationFn || angular.identity)(value);
         };
         ```
         */
        function identity($) {return $;}//在写样式代码的时候会被用到//定义angular的标识
        identity.$inject = [];
    
    
        function valueFn(value) {return function() {return value;};}
    
        /**
         * @ngdoc function
         * @name angular.isUndefined
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is undefined.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is undefined.
         */
        function isUndefined(value){return typeof value === 'undefined';}
    
    
        /**
         * @ngdoc function
         * @name angular.isDefined
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is defined.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is defined.
         */
        function isDefined(value){return typeof value !== 'undefined';}
    
    
        /**
         * @ngdoc function
         * @name angular.isObject
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
         * considered to be objects. Note that JavaScript arrays are objects.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is an `Object` but not `null`.
         */
        function isObject(value){return value != null && typeof value === 'object';}
    
    
        /**
         * @ngdoc function
         * @name angular.isString
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a `String`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `String`.
         */
        function isString(value){return typeof value === 'string';}
    
    
        /**
         * @ngdoc function
         * @name angular.isNumber
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a `Number`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `Number`.
         */
        function isNumber(value){return typeof value === 'number';}
    
    
        /**
         * @ngdoc function
         * @name angular.isDate
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a value is a date.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `Date`.
         */
        function isDate(value) {
            return toString.call(value) === '[object Date]';
        }
    
    
        /**
         * @ngdoc function
         * @name angular.isArray
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is an `Array`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is an `Array`.
         */
        var isArray = (function() {
            if (!isFunction(Array.isArray)) {
                return function(value) {
                    return toString.call(value) === '[object Array]';
                };
            }
            return Array.isArray;
        })();
    
        /**
         * @ngdoc function
         * @name angular.isFunction
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a `Function`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `Function`.
         */
        function isFunction(value){return typeof value === 'function';}
    
    
        /**
         * Determines if a value is a regular expression object.
         *
         * @private
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `RegExp`.
         */
        function isRegExp(value) {
            return toString.call(value) === '[object RegExp]';
        }
    
    
        /**
         * Checks if `obj` is a window object.
         *
         * @private
         * @param {*} obj Object to check
         * @returns {boolean} True if `obj` is a window obj.
         */
        function isWindow(obj) {
            return obj && obj.window === obj;
        }
    
    
        function isScope(obj) {
            return obj && obj.$evalAsync && obj.$watch;
        }
    
    
        function isFile(obj) {
            return toString.call(obj) === '[object File]';
        }
    
    
        function isBlob(obj) {
            return toString.call(obj) === '[object Blob]';
        }
    
    
        function isBoolean(value) {
            return typeof value === 'boolean';
        }
    
    
        var trim = (function() {
            // native trim is way faster: http://jsperf.com/angular-trim-test
            // but IE doesn't have it... :-(
            // TODO: we should move this into IE/ES5 polyfill
            if (!String.prototype.trim) {
                return function(value) {
                    return isString(value) ? value.replace(/^ss*/, '').replace(/ss*$/, '') : value;
                };
            }
            return function(value) {
                return isString(value) ? value.trim() : value;
            };
        })();
    
    
        /**
         * @ngdoc function
         * @name angular.isElement
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a DOM element (or wrapped jQuery element).
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
         */
        function isElement(node) {
            return !!(node &&
            (node.nodeName  // we are a direct element
            || (node.prop && node.attr && node.find)));  // we have an on and find method part of jQuery API
        }
    
        /**
         * @param str 'key1,key2,...'
         * @returns {object} in the form of {key1:true, key2:true, ...}
         */
        function makeMap(str) {
            var obj = {}, items = str.split(","), i;
            for ( i = 0; i < items.length; i++ )
                obj[ items[i] ] = true;
            return obj;
        }
    
    
        if (msie < 9) {
            nodeName_ = function(element) {
                element = element.nodeName ? element : element[0];
                return lowercase(
                    (element.scopeName && element.scopeName != 'HTML')
                        ? element.scopeName + ':' + element.nodeName : element.nodeName
                );
            };
        } else {
            nodeName_ = function(element) {
                return lowercase(element.nodeName ? element.nodeName : element[0].nodeName);
            };
        }
    
    
        function map(obj, iterator, context) {
            var results = [];
            forEach(obj, function(value, index, list) {
                results.push(iterator.call(context, value, index, list));
            });
            return results;
        }
    
    
        /**
         * @description
         * Determines the number of elements in an array, the number of properties an object has, or
         * the length of a string.
         *
         * Note: This function is used to augment the Object type in Angular expressions. See
         * {@link angular.Object} for more information about Angular arrays.
         *
         * @param {Object|Array|string} obj Object, array, or string to inspect.
         * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
         * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
         */
        function size(obj, ownPropsOnly) {
            var count = 0, key;
    
            if (isArray(obj) || isString(obj)) {
                return obj.length;
            } else if (isObject(obj)) {
                for (key in obj)
                    if (!ownPropsOnly || obj.hasOwnProperty(key))
                        count++;
            }
    
            return count;
        }
    
    
        function includes(array, obj) {
            return indexOf(array, obj) != -1;
        }
    
        function indexOf(array, obj) {
            if (array.indexOf) return array.indexOf(obj);
    
            for (var i = 0; i < array.length; i++) {
                if (obj === array[i]) return i;
            }
            return -1;
        }
    
        function arrayRemove(array, value) {
            var index = indexOf(array, value);
            if (index >=0)
                array.splice(index, 1);
            return value;
        }
    
        function isLeafNode (node) {
            if (node) {
                switch (nodeName_(node)) {
                    case "option":
                    case "pre":
                    case "title":
                        return true;
                }
            }
            return false;
        }
    
        /**
         * @ngdoc function
         * @name angular.copy
         * @module ng
         * @kind function
         *
         * @description
         * Creates a deep copy of `source`, which should be an object or an array.
         *创建一个深拷贝的源,它可以是一个对象或一个数组
         * * If no destination is supplied, a copy of the object or array is created
         * 如果未指定dest,对象或数组的一个副本会被创建
         * * If a destination is provided, all of its elements (for array) or properties (for objects)
         *   are deleted and then all elements/properties from the source are copied to it.
         *   如果指定了它的dest,则它的所有属性或元素将会被删除,并且它的所有元素或属性将会从源里面拷贝出来。
         * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
         * 如果源不是一个对象或数组,则操作会立即停止执行
         * * If `source` is identical to 'destination' an exception will be thrown.
         *如果源被标识为一个dest,则会抛出一个异常
         * @param {*} source The source that will be used to make a copy.//源,进行拷贝的参照对象,可以是任意类型
         *                   Can be any type, including primitives, `null`, and `undefined`.
         * @param {(Object|Array)=} destination Destination into which the source is copied. If
         *     provided, must be of the same type as `source`.//参数为一个对象或数组,如果被提供,则必须与源的类型相同
         * @returns {*} The copy or updated `destination`, if `destination` was specified.
         * 如果dest被指定的话,则更新dest;如果dest未被指定的话,是返回源的拷贝副本。
         *
         * @example
         <example module="copyExample">
         <file name="index.html">
         <div ng-controller="ExampleController">
         <form novalidate class="simple-form">
         Name: <input type="text" ng-model="user.name" /><br />
         E-mail: <input type="email" ng-model="user.email" /><br />
         Gender: <input type="radio" ng-model="user.gender" value="male" />male
         <input type="radio" ng-model="user.gender" value="female" />female<br />
         <button ng-click="reset()">RESET</button>
         <button ng-click="update(user)">SAVE</button>
         </form>
         <pre>form = {{user | json}}</pre>//user是一个json对象
         <pre>master = {{master | json}}</pre>master是一个json对象
         </div>
    
         <script>
         angular.module('copyExample')
         .controller('ExampleController', ['$scope', function($scope) {//$scope默认会被注入
          $scope.master= {};
    
          $scope.update = function(user) {
            // Example with 1 argument
            $scope.master= angular.copy(user);
          };
    
          $scope.reset = function() {
            // Example with 2 arguments
            angular.copy($scope.master, $scope.user);
          };
    
          $scope.reset();
        }]);
         </script>
         </file>
         </example>
         */
        function copy(source, destination, stackSource, stackDest) {
            if (isWindow(source) || isScope(source)) {
                throw ngMinErr('cpws',
                    "Can't copy! Making copies of Window or Scope instances is not supported.");
            }
    
            if (!destination) {//如果指定dest
                destination = source;
                if (source) {
                    if (isArray(source)) {
                        destination = copy(source, [], stackSource, stackDest);
                    } else if (isDate(source)) {
                        destination = new Date(source.getTime());
                    } else if (isRegExp(source)) {
                        destination = new RegExp(source.source);
                    } else if (isObject(source)) {
                        var emptyObject = Object.create(Object.getPrototypeOf(source));
                        destination = copy(source, emptyObject, stackSource, stackDest);
                    }
                }
            } else {//如果不指定dest
                if (source === destination) throw ngMinErr('cpi',
                    "Can't copy! Source and destination are identical.");
    
                stackSource = stackSource || [];
                stackDest = stackDest || [];
    
                if (isObject(source)) {
                    var index = indexOf(stackSource, source);
                    if (index !== -1) return stackDest[index];
    
                    stackSource.push(source);
                    stackDest.push(destination);
                }
    
                var result;
                if (isArray(source)) {
                    destination.length = 0;
                    for ( var i = 0; i < source.length; i++) {
                        result = copy(source[i], null, stackSource, stackDest);
                        if (isObject(source[i])) {
                            stackSource.push(source[i]);
                            stackDest.push(result);
                        }
                        destination.push(result);
                    }
                } else {
                    var h = destination.$$hashKey;
                    forEach(destination, function(value, key) {
                        delete destination[key];
                    });
                    for ( var key in source) {
                        if(source.hasOwnProperty(key)) {
                            result = copy(source[key], null, stackSource, stackDest);
                            if (isObject(source[key])) {
                                stackSource.push(source[key]);
                                stackDest.push(result);
                            }
                            destination[key] = result;
                        }
                    }
                    setHashKey(destination,h);
                }
    
            }
            return destination;
        }
    
        /**
         * Creates a shallow copy of an object, an array or a primitive
         * 创建一个浅副本,可以是一个数组或一个原始数据
         */
        function shallowCopy(src, dst) {
            var i = 0;
            if (isArray(src)) {
                dst = dst || [];
    
                for (; i < src.length; i++) {
                    dst[i] = src[i];
                }
            } else if (isObject(src)) {
                dst = dst || {};
    
                var keys = Object.keys(src);
    
                for (var l = keys.length; i < l; i++) {
                    var key = keys[i];
    
                    if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
                        dst[key] = src[key];
                    }
                }
            }
    
            return dst || src;
        }
    
    
        /**
         * @ngdoc function
         * @name angular.equals
         * @module ng
         * @kind function
         *
         * @description
         * Determines if two objects or two values are equivalent. Supports value types, regular
         * expressions, arrays and objects.
         *
         * Two objects or values are considered equivalent if at least one of the following is true:
         *
         * * Both objects or values pass `===` comparison.
         * * Both objects or values are of the same type and all of their properties are equal by
         *   comparing them with `angular.equals`.
         * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
         * * Both values represent the same regular expression (In JavaScript,
         *   /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
         *   representation matches).
         *
         * During a property comparison, properties of `function` type and properties with names
         * that begin with `$` are ignored.
         *
         * Scope and DOMWindow objects are being compared only by identify (`===`).
         *
         * @param {*} o1 Object or value to compare.
         * @param {*} o2 Object or value to compare.
         * @returns {boolean} True if arguments are equal.
         */
        function equals(o1, o2) {
            if (o1 === o2) return true;
            if (o1 === null || o2 === null) return false;
            if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
            var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
            if (t1 == t2) {
                if (t1 == 'object') {
                    if (isArray(o1)) {
                        if (!isArray(o2)) return false;
                        if ((length = o1.length) == o2.length) {
                            for(key=0; key<length; key++) {
                                if (!equals(o1[key], o2[key])) return false;
                            }
                            return true;
                        }
                    } else if (isDate(o1)) {
                        return isDate(o2) && o1.getTime() == o2.getTime();
                    } else if (isRegExp(o1) && isRegExp(o2)) {
                        return o1.toString() == o2.toString();
                    } else {
                        if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
                        keySet = {};
                        for(key in o1) {
                            if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
                            if (!equals(o1[key], o2[key])) return false;
                            keySet[key] = true;
                        }
                        for(key in o2) {
                            if (!keySet.hasOwnProperty(key) &&
                                key.charAt(0) !== '$' &&
                                o2[key] !== undefined &&
                                !isFunction(o2[key])) return false;
                        }
                        return true;
                    }
                }
            }
            return false;
        }
    
    
        function csp() {
            return (document.securityPolicy && document.securityPolicy.isActive) ||
                (document.querySelector &&
                !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]')));
        }
    
    
        function concat(array1, array2, index) {
            return array1.concat(slice.call(array2, index));//连接两个数组,call(obj,具体的参数)
        }
    
        //分割指定位置的数组数据
        function sliceArgs(args, startIndex) {
            return slice.call(args, startIndex || 0);//[].splice
        }
    
    
        /* jshint -W101 */
        /**
         * @ngdoc function
         * @name angular.bind
         * @module ng
         * @kind function
         *
         * @description
         * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
         * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
         * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
         * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
         *当调用fn函数时,bn会被绑定到self,其中,self相当于this的作用。可以指定args参数绑定到预先形成的函数中。
         * 返回一个函数的调用
         * @param {Object} self Context which `fn` should be evaluated in.//一个上下文对象
         * @param {function()} fn Function to be bound.//一个函数,它将会被绑定到self对象中
         * @param {...*} args Optional arguments to be prebound to the `fn` function call.//可选参数将会被绑定到fn函数中
         * @returns {function()} Function that wraps the `fn` with all the specified bindings.
         */
        /* jshint +W101 */
        function bind(self, fn) {
            //判断可选参数是否被提供,如果被提供则为sliceArg(argumentts,2),如果未被提供则为空数组,[]
            var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
            if (isFunction(fn) && !(fn instanceof RegExp)) {//如果fn是一个函数
                return curryArgs.length//判断可选参数是否被提供
                    ? function() {
                    return arguments.length
                        ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))//如果可选参数被提供,则把可选参数绑定到self中
                        : fn.apply(self, curryArgs);//如果可选参数未被提供,则返回self本身
                }
                    : function() {
                    return arguments.length
                        ? fn.apply(self, arguments)
                        : fn.call(self);
                };
            } else {
                // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
                return fn;//在IE里面没有这些函数,所以绑定不了,直接返回fn函数
            }
        }
    
    
        function toJsonReplacer(key, value) {
            var val = value;
    
            if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
                val = undefined;
            } else if (isWindow(value)) {
                val = '$WINDOW';
            } else if (value &&  document === value) {
                val = '$DOCUMENT';
            } else if (isScope(value)) {
                val = '$SCOPE';
            }
            return val;
        }
    
    
        /**
         * @ngdoc function
         * @name angular.toJson
         * @module ng
         * @kind function
         *
         * @description
         * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
         * stripped since angular uses this notation internally.
         *
         * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
         * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
         * @returns {string|undefined} JSON-ified string representing `obj`.
         */
        //把指定的对象转换成json字符串,并把json字符串里面的值 替换成与之相对应的对象,如:$$scope,$widnow,$document
        function toJson(obj, pretty) {
            if (typeof obj === 'undefined') return undefined;
            return JSON.stringify(obj, toJsonReplacer, pretty ? '  ' : null);
        }
    
    
        /**
         * @ngdoc function
         * @name angular.fromJson
         * @module ng
         * @kind function
         *
         * @description
         * Deserializes a JSON string.
         *
         * @param {string} json JSON string to deserialize.//一个json字符串
         * @returns {Object|Array|string|number} Deserialized thingy.,返回一个数组,对象
         */
        function fromJson(json) {//
            return isString(json)
                ? JSON.parse(json)
                : json;
        }
    
    
        /**
         * @returns {string} Returns the string representation of the element.
         */
        function startingTag(element) {//查找angular的ng指令,如ngapp,ngrepeat,ngswitch etc.
            element = jqLite(element).clone();//克隆element元素
            try {
                // turns out IE does not let you set .html() on elements which
                // are not allowed to have children. So we just ignore it.
                element.empty();//IE不允许用设置.html()设置元素,也就是说不允许有子元素,因此置为空
            } catch(e) {}
            // As Per DOM Standards
            var TEXT_NODE = 3;//标准的文节点
            var elemHtml = jqLite('<div>').append(element).html();//把div标签添加到新克隆出来的element上,并获取现在的值
            try {
                return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
                    elemHtml.
                        match(/^(<[^>]+>)/)[1].//正则表达式表示的示例内容如,<div ng-app='myModule'>
                        //正则:/^<([w-]+)/,表示的示例内容,如:<div ng-app='myModule'>,
                        // 正则:([w-]+),表示的示例内容如,ng-app,由此看来,angular在html里面写的内容必须是:标准的Html文内容
                        replace(/^<([w-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });//<ng-app
            } catch(e) {
                return lowercase(elemHtml);//如果不是上面的情况,慢把新取出的值直接返回
            }
    
        }
    
    
    /////////////////////////////////////////////////
    
        /**
         * Tries to decode the URI component without throwing an exception.
         *调试解码URI组件,不抛出异常
         * @private 为私有方法
         * @param str value potential URI component to check.//str是URI组件需要检查的内容
         * @returns {boolean} True if `value` can be decoded,如果str被正常的解析,则返回true
         * with the decodeURIComponent function.
         */
        function tryDecodeURIComponent(value) {
            try {
                return decodeURIComponent(value);
            } catch(e) {
                // Ignore any invalid uri component//忽略任何无效的URI组件内容
            }
        }
    
    
        /**
         * Parses an escaped url query string into key-value pairs.//
         * 解析一个转义的url查询字符串为键值对形式
         * @returns {Object.<string,boolean|Array>}//返回Object.字符串/boolean,数组
         */
        function parseKeyValue(/**string*/keyValue) {
            var obj = {}, key_value, key;
            //keyvalue的内容示例如,http://www.baidu.com?search=angular&date=20141215,结果为:search:angular,date:20141215
            forEach((keyValue || "").split('&'), function(keyValue) {
                if ( keyValue ) {
                    key_value = keyValue.split('=');
                    key = tryDecodeURIComponent(key_value[0]);
                    if ( isDefined(key) ) {
                        var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
                        if (!hasOwnProperty.call(obj, key)) {
                            obj[key] = val;
                        } else if(isArray(obj[key])) {
                            obj[key].push(val);
                        } else {
                            obj[key] = [obj[key],val];
                        }
                    }
                }
            });
            return obj;
        }
    
    查看代码

    /**
     * @license AngularJS v1.3.0-beta.15
     * (c) 2010-2014 Google, Inc. http://angularjs.org
     * License: MIT
     */
    (function(window, document, undefined) {'use strict';

        /**
         * @description
         *
         * This object provides a utility for producing rich Error messages within
         * Angular. It can be called as follows:
         *当Angular在发生错误的时候,这个对象提供一个处理错误的工具类,它的调用方式如下:
         * 例如:
         * var exampleMinErr = minErr('example');
         * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
         *
         * The above creates an instance of minErr in the example namespace. The
         * resulting error will have a namespaced error code of example.one.  The
         * resulting error will replace {0} with the value of foo, and {1} with the
         * value of bar. The object is not restricted in the number of arguments it can
         * take.
         *在上面的代码中,在example命名空间中创建了一个minErr的实例。错误的结果会在example,
         *里面生成一个错误码的example.one命名空间。这个错误的结果会用foo的值和bar的值,
         *来进行替换前面的点位符{0},{1}.这个对象不严格必须给定错误参数,因此错误参数可有可无。
         *
         * If fewer arguments are specified than necessary for interpolation, the extra
         * interpolation markers will be preserved in the final string.
         *如果指定的错误对象中的错误参数,则它会保留在最终生成的字符串中。
         *
         * Since data will be parsed statically during a build step, some restrictions
         * are applied with respect to how minErr instances are created and called.
         * Instances should have names of the form namespaceMinErr for a minErr created
         * using minErr('namespace') . Error codes, namespaces and template strings
         * should all be static strings, not variables or general expressions.
         *在编译阶段,错误参数中的数据会自己生成与解析,错误对象中的一些限制将会监视错误对象的
         实例该如何被创建与如何被调用。
         *
         * @param {string} module The namespace to use for the new minErr instance.
         * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
         */

    //module:模板名称,将会用于新的错误对象的实例中
        function minErr(module) {
            return function () {//直接进行返回操作
                //获取错误参数中的错误标识符,如,上面示例中的one
                var code = arguments[0],
                //获取错误对象模板的前缀
                    prefix = '[' + (module ? module + ':' : '') + code + '] ',
                    template = arguments[1],
                    templateArgs = arguments,
                    //这个属于偏置函数
                    stringify = function (obj) {
                        if (typeof obj === 'function') {//如果参数是一个函数
                            //返回的结果,如:{'item in items '}-->''
                            return obj.toString().replace(/ {[sS]*$/, '');
                        } else if (typeof obj === 'undefined') {
                            return 'undefined';
                        } else if (typeof obj !== 'string') {
                            return JSON.stringify(obj);//强制参数转换为Json的字符串形式
                        }
                        return obj;
                    },
                    message, i;


                message = prefix + template.replace(/{d+}/g, function (match) {
                    var index = +match.slice(1, -1), arg;

                    if (index + 2 < templateArgs.length) {
                        arg = templateArgs[index + 2];
                        if (typeof arg === 'function') {
                            return arg.toString().replace(/ ?{[sS]*$/, '');
                        } else if (typeof arg === 'undefined') {
                            return 'undefined';
                        } else if (typeof arg !== 'string') {
                            return toJson(arg);
                        }
                        return arg;
                    }
                    return match;
                });

                message = message + ' http://errors.angularjs.org/1.3.0-beta.15/' +
                (module ? module + '/' : '') + code;
                for (i = 2; i < arguments.length; i++) {
                    message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' +
                    encodeURIComponent(stringify(arguments[i]));
                }

                return new Error(message);
            };
        }

        /* We need to tell jshint what variables are being exported */
        /* global angular: true,
         msie: true,
         jqLite: true,
         jQuery: true,
         slice: true,
         push: true,
         toString: true,
         ngMinErr: true,
         angularModule: true,
         nodeName_: true,
         uid: true,
         REGEX_STRING_REGEXP: true,
         VALIDITY_STATE_PROPERTY: true,

         lowercase: true,
         uppercase: true,
         manualLowercase: true,
         manualUppercase: true,
         nodeName_: true,
         isArrayLike: true,
         forEach: true,
         sortedKeys: true,
         forEachSorted: true,
         reverseParams: true,
         nextUid: true,
         setHashKey: true,
         extend: true,
         int: true,
         inherit: true,
         noop: true,
         identity: true,
         valueFn: true,
         isUndefined: true,
         isDefined: true,
         isObject: true,
         isString: true,
         isNumber: true,
         isDate: true,
         isArray: true,
         isFunction: true,
         isRegExp: true,
         isWindow: true,
         isScope: true,
         isFile: true,
         isBlob: true,
         isBoolean: true,
         trim: true,
         isElement: true,
         makeMap: true,
         map: true,
         size: true,
         includes: true,
         indexOf: true,
         arrayRemove: true,
         isLeafNode: true,
         copy: true,
         shallowCopy: true,
         equals: true,
         csp: true,
         concat: true,
         sliceArgs: true,
         bind: true,
         toJsonReplacer: true,
         toJson: true,
         fromJson: true,
         startingTag: true,
         tryDecodeURIComponent: true,
         parseKeyValue: true,
         toKeyValue: true,
         encodeUriSegment: true,
         encodeUriQuery: true,
         angularInit: true,
         bootstrap: true,
         snake_case: true,
         bindJQuery: true,
         assertArg: true,
         assertArgFn: true,
         assertNotHasOwnProperty: true,
         getter: true,
         getBlockElements: true,
         hasOwnProperty: true,
         */

    ////////////////////////////////////

        /**
         * @ngdoc module
         * @name ng
         * @module ng
         * @description
         *
         * # ng (core module)
         * The ng module is loaded by default when an AngularJS application is started. The module itself
         * contains the essential components for an AngularJS application to function. The table below
         * lists a high level breakdown of each of the services/factories, filters, directives and testing
         * components available within this core module.
         *
         * <div doc-module-components="ng"></div>
         *
         * ng模板默认在Angular应用程序启动的时候会被自动加载。这个模板包含了Angular应用程序运行所必须的组件。
         *  下表列出了在这个核心的模块中所包含的服务/工厂,过滤器,指令和一些测试的组件
         */

        var REGEX_STRING_REGEXP = /^/(.+)/([a-z]*)$/;

    // The name of a form control's ValidityState property.
    // This is used so that it's possible for internal tests to create mock ValidityStates.
        var VALIDITY_STATE_PROPERTY = 'validity';

        /**
         * @ngdoc function
         * @name angular.lowercase
         * @module ng
         * @kind function
         *
         * @description Converts the specified string to lowercase.
         * @param {string} string String to be converted to lowercase.
         * @returns {string} Lowercased string.
         */
            //转换指定的字符串为小写
        var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
        var hasOwnProperty = Object.prototype.hasOwnProperty;

        /**
         * @ngdoc function
         * @name angular.uppercase
         * @module ng
         * @kind function
         *
         * @description Converts the specified string to uppercase.
         * @param {string} string String to be converted to uppercase.
         * @returns {string} Uppercased string.
         */
            //转换指定的字符串为大写
        var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};


        var manualLowercase = function(s) {
            /* jshint bitwise: false */
            return isString(s)
                ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);})
                : s;
        };
        var manualUppercase = function(s) {
            /* jshint bitwise: false */
            return isString(s)
                ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);})
                : s;
        };


    // String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish
    // locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
    // with correct but slower alternatives.
        if ('i' !== 'I'.toLowerCase()) {
            lowercase = manualLowercase;
            uppercase = manualUppercase;
        }


        var /** holds major version number for IE or NaN for real browsers */
            msie,
            jqLite,           // delay binding since jQuery could be loaded after us.
            jQuery,           // delay binding
            slice             = [].slice,//取出指定位置的数组数据
            push              = [].push,//把指定的数据放到数据里面
            toString          = Object.prototype.toString,//重写Object原型中的toString方法
            ngMinErr          = minErr('ng'),//定义一个全局的错误对象,对应的错误参数为,ng

            /** @name angular */
                //判断系统是否注册了angular应用程序,如果没有注册则注册angular应用程序的为空对象
            angular           = window.angular || (window.angular = {}),
            angularModule,
            nodeName_,
            uid               = 0;

        /**
         * IE 11 changed the format of the UserAgent string.
         * See http://msdn.microsoft.com/en-us/library/ms537503.aspx
         */
        //判断浏览器的类型
        msie = int((/msie (d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
        if (isNaN(msie)) {
            msie = int((/trident/.*; rv:(d+)/.exec(lowercase(navigator.userAgent)) || [])[1]);
        }


        /**
         * @private
         * @param {*} obj
         * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments,
         *                   String ...)
         */
        //判断传入的对象obj是否是数组或有类似数组的行为,它可以是json字符串或数组
        function isArrayLike(obj) {
            if (obj == null || isWindow(obj)) {//为空或window对象,则返回false
                return false;
            }

            var length = obj.length;//获取数组的长度

            if (obj.nodeType === 1 && length) {//判断obj的节点类型,如果为元素,则返回true
                return true;
            }

            return isString(obj) || isArray(obj) || length === 0 ||
                typeof length === 'number' && length > 0 && (length - 1) in obj;
        }

        /**
         * @ngdoc function
         * @name angular.forEach
         * @module ng
         * @kind function
         *
         * @description
         * Invokes the `iterator` function once for each item in `obj` collection, which can be either an
         * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value`
         * is the value of an object property or an array element and `key` is the object property key or
         * array element index. Specifying a `context` for the function is optional.
         *
         * It is worth noting that `.forEach` does not iterate over inherited properties because it filters
         * using the `hasOwnProperty` method.
         *
         ```js
         var values = {name: 'misko', gender: 'male'};
         var log = [];
         angular.forEach(values, function(value, key) {
           this.push(key + ': ' + value);
         }, log);
         expect(log).toEqual(['name: misko', 'gender: male']);
         ```
         *
         * @param {Object|Array} obj Object to iterate over.//可以是一个对象或一个数组
         * @param {Function} iterator Iterator function.//迭代器是一个函数
         * @param {Object=} context Object to become context (`this`) for the iterator function.//这个对象是一个上下文对象充当this的角色
         * @returns {Object|Array} Reference to `obj`.
         */
        //其中context可选参数,iterator是一个迭代器,它是一个函数
        //它不能获取的继承的属性,因为程序中用hasOwnProperty来过滤了
        function forEach(obj, iterator, context) {
            var key, length;
            if (obj) {
                if (isFunction(obj)) {
                    for (key in obj) {
                        // Need to check if hasOwnProperty exists,
                        // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
                        if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) {
                            iterator.call(context, obj[key], key);
                        }
                    }
                } else if (isArray(obj) || isArrayLike(obj)) {
                    for (key = 0, length = obj.length; key < length; key++) {
                        iterator.call(context, obj[key], key);
                    }
                } else if (obj.forEach && obj.forEach !== forEach) {
                    obj.forEach(iterator, context);
                } else {
                    for (key in obj) {
                        if (obj.hasOwnProperty(key)) {
                            iterator.call(context, obj[key], key);
                        }
                    }
                }
            }
            return obj;
        }

        function sortedKeys(obj) {//按照键来排序
            var keys = [];
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    keys.push(key);
                }
            }
            return keys.sort();//采用数组默认的排序方式
        }

        function forEachSorted(obj, iterator, context) {//先按照键排序,然后再遍历|迭代
            var keys = sortedKeys(obj);
            for ( var i = 0; i < keys.length; i++) {
                iterator.call(context, obj[keys[i]], keys[i]);
            }
            return keys;
        }


        /**
         * when using forEach the params are value, key, but it is often useful to have key, value.
         * @param {function(string, *)} iteratorFn
         * @returns {function(*, string)}
         */
        function reverseParams(iteratorFn) {
            return function(value, key) { iteratorFn(key, value); };
        }

        /**
         * A consistent way of creating unique IDs in angular.
         *
         * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before
         * we hit number precision issues in JavaScript.
         *
         * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M
         *
         * @returns {number} an unique alpha-numeric string
         */
        function nextUid() {//生成一个常量,它在andular里面是唯一的
            return ++uid;
        }


        /**
         * Set or clear the hashkey for an object.
         * @param obj object//参数是一个对象
         * @param h the hashkey (!truthy to delete the hashkey)
         */
        function setHashKey(obj, h) {
            if (h) {//如果存在就设置
                obj.$$hashKey = h;
            }
            else {//如果不存在则删除
                delete obj.$$hashKey;
            }
        }

        /**
         * @ngdoc function
         * @name angular.extend
         * @module ng
         * @kind function
         *
         * @description
         * Extends the destination object `dst` by copying all of the properties from the `src` object(s)
         * to `dst`. You can specify multiple `src` objects.
         *
         * @param {Object} dst Destination object.//最终的对象
         * @param {...Object} src Source object(s).//源对象
         * @returns {Object} Reference to `dst`.//返回最终的对象
         */
        function extend(dst) {//在javascript中实现继承
            var h = dst.$$hashKey;
            forEach(arguments, function(obj) {//这里采用拷贝属性的方式实现继承
                if (obj !== dst) {
                    forEach(obj, function(value, key) {
                        dst[key] = value;
                    });
                }
            });

            setHashKey(dst,h);
            return dst;
        }

        function int(str) {//转换为整形
            return parseInt(str, 10);
        }


        function inherit(parent, extra) {//继承
            return extend(new (extend(function() {}, {prototype:parent}))(), extra);
        }

        /**
         * @ngdoc function
         * @name angular.noop
         * @module ng
         * @kind function
         *
         * @description
         * A function that performs no operations. This function can be useful when writing code in the
         * functional style.
         ```js
         function foo(callback) {
           var result = calculateResult();
           (callback || angular.noop)(result);
         }
         ```
         */
        function noop() {}//在写样式代码的时候会被用到
        noop.$inject = [];


        /**
         * @ngdoc function
         * @name angular.identity
         * @module ng
         * @kind function
         *
         * @description
         * A function that returns its first argument. This function is useful when writing code in the
         * functional style.
         *
         ```js
         function transformer(transformationFn, value) {
           return (transformationFn || angular.identity)(value);
         };
         ```
         */
        function identity($) {return $;}//在写样式代码的时候会被用到//定义angular的标识
        identity.$inject = [];


        function valueFn(value) {return function() {return value;};}

        /**
         * @ngdoc function
         * @name angular.isUndefined
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is undefined.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is undefined.
         */
        function isUndefined(value){return typeof value === 'undefined';}


        /**
         * @ngdoc function
         * @name angular.isDefined
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is defined.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is defined.
         */
        function isDefined(value){return typeof value !== 'undefined';}


        /**
         * @ngdoc function
         * @name angular.isObject
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
         * considered to be objects. Note that JavaScript arrays are objects.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is an `Object` but not `null`.
         */
        function isObject(value){return value != null && typeof value === 'object';}


        /**
         * @ngdoc function
         * @name angular.isString
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a `String`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `String`.
         */
        function isString(value){return typeof value === 'string';}


        /**
         * @ngdoc function
         * @name angular.isNumber
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a `Number`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `Number`.
         */
        function isNumber(value){return typeof value === 'number';}


        /**
         * @ngdoc function
         * @name angular.isDate
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a value is a date.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `Date`.
         */
        function isDate(value) {
            return toString.call(value) === '[object Date]';
        }


        /**
         * @ngdoc function
         * @name angular.isArray
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is an `Array`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is an `Array`.
         */
        var isArray = (function() {
            if (!isFunction(Array.isArray)) {
                return function(value) {
                    return toString.call(value) === '[object Array]';
                };
            }
            return Array.isArray;
        })();

        /**
         * @ngdoc function
         * @name angular.isFunction
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a `Function`.
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `Function`.
         */
        function isFunction(value){return typeof value === 'function';}


        /**
         * Determines if a value is a regular expression object.
         *
         * @private
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a `RegExp`.
         */
        function isRegExp(value) {
            return toString.call(value) === '[object RegExp]';
        }


        /**
         * Checks if `obj` is a window object.
         *
         * @private
         * @param {*} obj Object to check
         * @returns {boolean} True if `obj` is a window obj.
         */
        function isWindow(obj) {
            return obj && obj.window === obj;
        }


        function isScope(obj) {
            return obj && obj.$evalAsync && obj.$watch;
        }


        function isFile(obj) {
            return toString.call(obj) === '[object File]';
        }


        function isBlob(obj) {
            return toString.call(obj) === '[object Blob]';
        }


        function isBoolean(value) {
            return typeof value === 'boolean';
        }


        var trim = (function() {
            // native trim is way faster: http://jsperf.com/angular-trim-test
            // but IE doesn't have it... :-(
            // TODO: we should move this into IE/ES5 polyfill
            if (!String.prototype.trim) {
                return function(value) {
                    return isString(value) ? value.replace(/^ss*/, '').replace(/ss*$/, '') : value;
                };
            }
            return function(value) {
                return isString(value) ? value.trim() : value;
            };
        })();


        /**
         * @ngdoc function
         * @name angular.isElement
         * @module ng
         * @kind function
         *
         * @description
         * Determines if a reference is a DOM element (or wrapped jQuery element).
         *
         * @param {*} value Reference to check.
         * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element).
         */
        function isElement(node) {
            return !!(node &&
            (node.nodeName  // we are a direct element
            || (node.prop && node.attr && node.find)));  // we have an on and find method part of jQuery API
        }

        /**
         * @param str 'key1,key2,...'
         * @returns {object} in the form of {key1:true, key2:true, ...}
         */
        function makeMap(str) {
            var obj = {}, items = str.split(","), i;
            for ( i = 0; i < items.length; i++ )
                obj[ items[i] ] = true;
            return obj;
        }


        if (msie < 9) {
            nodeName_ = function(element) {
                element = element.nodeName ? element : element[0];
                return lowercase(
                    (element.scopeName && element.scopeName != 'HTML')
                        ? element.scopeName + ':' + element.nodeName : element.nodeName
                );
            };
        } else {
            nodeName_ = function(element) {
                return lowercase(element.nodeName ? element.nodeName : element[0].nodeName);
            };
        }


        function map(obj, iterator, context) {
            var results = [];
            forEach(obj, function(value, index, list) {
                results.push(iterator.call(context, value, index, list));
            });
            return results;
        }


        /**
         * @description
         * Determines the number of elements in an array, the number of properties an object has, or
         * the length of a string.
         *
         * Note: This function is used to augment the Object type in Angular expressions. See
         * {@link angular.Object} for more information about Angular arrays.
         *
         * @param {Object|Array|string} obj Object, array, or string to inspect.
         * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object
         * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array.
         */
        function size(obj, ownPropsOnly) {
            var count = 0, key;

            if (isArray(obj) || isString(obj)) {
                return obj.length;
            } else if (isObject(obj)) {
                for (key in obj)
                    if (!ownPropsOnly || obj.hasOwnProperty(key))
                        count++;
            }

            return count;
        }


        function includes(array, obj) {
            return indexOf(array, obj) != -1;
        }

        function indexOf(array, obj) {
            if (array.indexOf) return array.indexOf(obj);

            for (var i = 0; i < array.length; i++) {
                if (obj === array[i]) return i;
            }
            return -1;
        }

        function arrayRemove(array, value) {
            var index = indexOf(array, value);
            if (index >=0)
                array.splice(index, 1);
            return value;
        }

        function isLeafNode (node) {
            if (node) {
                switch (nodeName_(node)) {
                    case "option":
                    case "pre":
                    case "title":
                        return true;
                }
            }
            return false;
        }

        /**
         * @ngdoc function
         * @name angular.copy
         * @module ng
         * @kind function
         *
         * @description
         * Creates a deep copy of `source`, which should be an object or an array.
         *创建一个深拷贝的源,它可以是一个对象或一个数组
         * * If no destination is supplied, a copy of the object or array is created
         * 如果未指定dest,对象或数组的一个副本会被创建
         * * If a destination is provided, all of its elements (for array) or properties (for objects)
         *   are deleted and then all elements/properties from the source are copied to it.
         *   如果指定了它的dest,则它的所有属性或元素将会被删除,并且它的所有元素或属性将会从源里面拷贝出来。
         * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
         * 如果源不是一个对象或数组,则操作会立即停止执行
         * * If `source` is identical to 'destination' an exception will be thrown.
         *如果源被标识为一个dest,则会抛出一个异常
         * @param {*} source The source that will be used to make a copy.//源,进行拷贝的参照对象,可以是任意类型
         *                   Can be any type, including primitives, `null`, and `undefined`.
         * @param {(Object|Array)=} destination Destination into which the source is copied. If
         *     provided, must be of the same type as `source`.//参数为一个对象或数组,如果被提供,则必须与源的类型相同
         * @returns {*} The copy or updated `destination`, if `destination` was specified.
         * 如果dest被指定的话,则更新dest;如果dest未被指定的话,是返回源的拷贝副本。
         *
         * @example
         <example module="copyExample">
         <file name="index.html">
         <div ng-controller="ExampleController">
         <form novalidate class="simple-form">
         Name: <input type="text" ng-model="user.name" /><br />
         E-mail: <input type="email" ng-model="user.email" /><br />
         Gender: <input type="radio" ng-model="user.gender" value="male" />male
         <input type="radio" ng-model="user.gender" value="female" />female<br />
         <button ng-click="reset()">RESET</button>
         <button ng-click="update(user)">SAVE</button>
         </form>
         <pre>form = {{user | json}}</pre>//user是一个json对象
         <pre>master = {{master | json}}</pre>master是一个json对象
         </div>

         <script>
         angular.module('copyExample')
         .controller('ExampleController', ['$scope', function($scope) {//$scope默认会被注入
          $scope.master= {};

          $scope.update = function(user) {
            // Example with 1 argument
            $scope.master= angular.copy(user);
          };

          $scope.reset = function() {
            // Example with 2 arguments
            angular.copy($scope.master, $scope.user);
          };

          $scope.reset();
        }]);
         </script>
         </file>
         </example>
         */
        function copy(source, destination, stackSource, stackDest) {
            if (isWindow(source) || isScope(source)) {
                throw ngMinErr('cpws',
                    "Can't copy! Making copies of Window or Scope instances is not supported.");
            }

            if (!destination) {//如果指定dest
                destination = source;
                if (source) {
                    if (isArray(source)) {
                        destination = copy(source, [], stackSource, stackDest);
                    } else if (isDate(source)) {
                        destination = new Date(source.getTime());
                    } else if (isRegExp(source)) {
                        destination = new RegExp(source.source);
                    } else if (isObject(source)) {
                        var emptyObject = Object.create(Object.getPrototypeOf(source));
                        destination = copy(source, emptyObject, stackSource, stackDest);
                    }
                }
            } else {//如果不指定dest
                if (source === destination) throw ngMinErr('cpi',
                    "Can't copy! Source and destination are identical.");

                stackSource = stackSource || [];
                stackDest = stackDest || [];

                if (isObject(source)) {
                    var index = indexOf(stackSource, source);
                    if (index !== -1) return stackDest[index];

                    stackSource.push(source);
                    stackDest.push(destination);
                }

                var result;
                if (isArray(source)) {
                    destination.length = 0;
                    for ( var i = 0; i < source.length; i++) {
                        result = copy(source[i], null, stackSource, stackDest);
                        if (isObject(source[i])) {
                            stackSource.push(source[i]);
                            stackDest.push(result);
                        }
                        destination.push(result);
                    }
                } else {
                    var h = destination.$$hashKey;
                    forEach(destination, function(value, key) {
                        delete destination[key];
                    });
                    for ( var key in source) {
                        if(source.hasOwnProperty(key)) {
                            result = copy(source[key], null, stackSource, stackDest);
                            if (isObject(source[key])) {
                                stackSource.push(source[key]);
                                stackDest.push(result);
                            }
                            destination[key] = result;
                        }
                    }
                    setHashKey(destination,h);
                }

            }
            return destination;
        }

        /**
         * Creates a shallow copy of an object, an array or a primitive
         * 创建一个浅副本,可以是一个数组或一个原始数据
         */
        function shallowCopy(src, dst) {
            var i = 0;
            if (isArray(src)) {
                dst = dst || [];

                for (; i < src.length; i++) {
                    dst[i] = src[i];
                }
            } else if (isObject(src)) {
                dst = dst || {};

                var keys = Object.keys(src);

                for (var l = keys.length; i < l; i++) {
                    var key = keys[i];

                    if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
                        dst[key] = src[key];
                    }
                }
            }

            return dst || src;
        }


        /**
         * @ngdoc function
         * @name angular.equals
         * @module ng
         * @kind function
         *
         * @description
         * Determines if two objects or two values are equivalent. Supports value types, regular
         * expressions, arrays and objects.
         *
         * Two objects or values are considered equivalent if at least one of the following is true:
         *
         * * Both objects or values pass `===` comparison.
         * * Both objects or values are of the same type and all of their properties are equal by
         *   comparing them with `angular.equals`.
         * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
         * * Both values represent the same regular expression (In JavaScript,
         *   /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
         *   representation matches).
         *
         * During a property comparison, properties of `function` type and properties with names
         * that begin with `$` are ignored.
         *
         * Scope and DOMWindow objects are being compared only by identify (`===`).
         *
         * @param {*} o1 Object or value to compare.
         * @param {*} o2 Object or value to compare.
         * @returns {boolean} True if arguments are equal.
         */
        function equals(o1, o2) {
            if (o1 === o2) return true;
            if (o1 === null || o2 === null) return false;
            if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN
            var t1 = typeof o1, t2 = typeof o2, length, key, keySet;
            if (t1 == t2) {
                if (t1 == 'object') {
                    if (isArray(o1)) {
                        if (!isArray(o2)) return false;
                        if ((length = o1.length) == o2.length) {
                            for(key=0; key<length; key++) {
                                if (!equals(o1[key], o2[key])) return false;
                            }
                            return true;
                        }
                    } else if (isDate(o1)) {
                        return isDate(o2) && o1.getTime() == o2.getTime();
                    } else if (isRegExp(o1) && isRegExp(o2)) {
                        return o1.toString() == o2.toString();
                    } else {
                        if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
                        keySet = {};
                        for(key in o1) {
                            if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
                            if (!equals(o1[key], o2[key])) return false;
                            keySet[key] = true;
                        }
                        for(key in o2) {
                            if (!keySet.hasOwnProperty(key) &&
                                key.charAt(0) !== '$' &&
                                o2[key] !== undefined &&
                                !isFunction(o2[key])) return false;
                        }
                        return true;
                    }
                }
            }
            return false;
        }


        function csp() {
            return (document.securityPolicy && document.securityPolicy.isActive) ||
                (document.querySelector &&
                !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]')));
        }


        function concat(array1, array2, index) {
            return array1.concat(slice.call(array2, index));//连接两个数组,call(obj,具体的参数)
        }

        //分割指定位置的数组数据
        function sliceArgs(args, startIndex) {
            return slice.call(args, startIndex || 0);//[].splice
        }


        /* jshint -W101 */
        /**
         * @ngdoc function
         * @name angular.bind
         * @module ng
         * @kind function
         *
         * @description
         * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
         * `fn`). You can supply optional `args` that are prebound to the function. This feature is also
         * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as
         * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application).
         *当调用fn函数时,bn会被绑定到self,其中,self相当于this的作用。可以指定args参数绑定到预先形成的函数中。
         * 返回一个函数的调用
         * @param {Object} self Context which `fn` should be evaluated in.//一个上下文对象
         * @param {function()} fn Function to be bound.//一个函数,它将会被绑定到self对象中
         * @param {...*} args Optional arguments to be prebound to the `fn` function call.//可选参数将会被绑定到fn函数中
         * @returns {function()} Function that wraps the `fn` with all the specified bindings.
         */
        /* jshint +W101 */
        function bind(self, fn) {
            //判断可选参数是否被提供,如果被提供则为sliceArg(argumentts,2),如果未被提供则为空数组,[]
            var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : [];
            if (isFunction(fn) && !(fn instanceof RegExp)) {//如果fn是一个函数
                return curryArgs.length//判断可选参数是否被提供
                    ? function() {
                    return arguments.length
                        ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0)))//如果可选参数被提供,则把可选参数绑定到self中
                        : fn.apply(self, curryArgs);//如果可选参数未被提供,则返回self本身
                }
                    : function() {
                    return arguments.length
                        ? fn.apply(self, arguments)
                        : fn.call(self);
                };
            } else {
                // in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
                return fn;//在IE里面没有这些函数,所以绑定不了,直接返回fn函数
            }
        }


        function toJsonReplacer(key, value) {
            var val = value;

            if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') {
                val = undefined;
            } else if (isWindow(value)) {
                val = '$WINDOW';
            } else if (value &&  document === value) {
                val = '$DOCUMENT';
            } else if (isScope(value)) {
                val = '$SCOPE';
            }
            return val;
        }


        /**
         * @ngdoc function
         * @name angular.toJson
         * @module ng
         * @kind function
         *
         * @description
         * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be
         * stripped since angular uses this notation internally.
         *
         * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
         * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace.
         * @returns {string|undefined} JSON-ified string representing `obj`.
         */
        //把指定的对象转换成json字符串,并把json字符串里面的值 替换成与之相对应的对象,如:$$scope,$widnow,$document
        function toJson(obj, pretty) {
            if (typeof obj === 'undefined') return undefined;
            return JSON.stringify(obj, toJsonReplacer, pretty ? '  ' : null);
        }


        /**
         * @ngdoc function
         * @name angular.fromJson
         * @module ng
         * @kind function
         *
         * @description
         * Deserializes a JSON string.
         *
         * @param {string} json JSON string to deserialize.//一个json字符串
         * @returns {Object|Array|string|number} Deserialized thingy.,返回一个数组,对象
         */
        function fromJson(json) {//
            return isString(json)
                ? JSON.parse(json)
                : json;
        }


        /**
         * @returns {string} Returns the string representation of the element.
         */
        function startingTag(element) {//查找angular的ng指令,如ngapp,ngrepeat,ngswitch etc.
            element = jqLite(element).clone();//克隆element元素
            try {
                // turns out IE does not let you set .html() on elements which
                // are not allowed to have children. So we just ignore it.
                element.empty();//IE不允许用设置.html()设置元素,也就是说不允许有子元素,因此置为空
            } catch(e) {}
            // As Per DOM Standards
            var TEXT_NODE = 3;//标准的文节点
            var elemHtml = jqLite('<div>').append(element).html();//把div标签添加到新克隆出来的element上,并获取现在的值
            try {
                return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) :
                    elemHtml.
                        match(/^(<[^>]+>)/)[1].//正则表达式表示的示例内容如,<div ng-app='myModule'>
                        //正则:/^<([w-]+)/,表示的示例内容,如:<div ng-app='myModule'>,
                        // 正则:([w-]+),表示的示例内容如,ng-app,由此看来,angular在html里面写的内容必须是:标准的Html文内容
                        replace(/^<([w-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); });//<ng-app
            } catch(e) {
                return lowercase(elemHtml);//如果不是上面的情况,慢把新取出的值直接返回
            }

        }


    /////////////////////////////////////////////////

        /**
         * Tries to decode the URI component without throwing an exception.
         *调试解码URI组件,不抛出异常
         * @private 为私有方法
         * @param str value potential URI component to check.//str是URI组件需要检查的内容
         * @returns {boolean} True if `value` can be decoded,如果str被正常的解析,则返回true
         * with the decodeURIComponent function.
         */
        function tryDecodeURIComponent(value) {
            try {
                return decodeURIComponent(value);
            } catch(e) {
                // Ignore any invalid uri component//忽略任何无效的URI组件内容
            }
        }


        /**
         * Parses an escaped url query string into key-value pairs.//
         * 解析一个转义的url查询字符串为键值对形式
         * @returns {Object.<string,boolean|Array>}//返回Object.字符串/boolean,数组
         */
        function parseKeyValue(/**string*/keyValue) {
            var obj = {}, key_value, key;
            //keyvalue的内容示例如,http://www.baidu.com?search=angular&date=20141215,结果为:search:angular,date:20141215
            forEach((keyValue || "").split('&'), function(keyValue) {
                if ( keyValue ) {
                    key_value = keyValue.split('=');
                    key = tryDecodeURIComponent(key_value[0]);
                    if ( isDefined(key) ) {
                        var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true;
                        if (!hasOwnProperty.call(obj, key)) {
                            obj[key] = val;
                        } else if(isArray(obj[key])) {
                            obj[key].push(val);
                        } else {
                            obj[key] = [obj[key],val];
                        }
                    }
                }
            });
            return obj;
        }

    查看代码

  • 相关阅读:
    Linux指令集
    [日记]贝鲁特煎熬第32天
    2019南京区域赛ABCHJK题解 & KMbfs(O(n^3))板子
    欧拉筛质数以及四大积性数论函数(欧拉函数、莫比乌斯函数、约数个数函数、约数和函数)
    简短的自我介绍
    BSTTreap名次树数组&指针实现板子 Ver1.0
    2019银川区域赛BDFGHIKN题解
    安装vs2010
    SPEmailEventReceiver 之导入附件EXCEL
    为incoming mail绑定事件,SPEmailEventReceiver
  • 原文地址:https://www.cnblogs.com/sxz2008/p/6423871.html
Copyright © 2011-2022 走看看