zoukankan      html  css  js  c++  java
  • Angular的启动与双向绑定

     Angular(1.2,不同版本代码结构会有差别)的启动过程

    angular.js是一个自执行函数,实际上angular是一个对象。当angular.js被引入之后,执行如下代码:

    //try to bind to jquery now so that one can write angular.element().read()
    //but we will rebind on bootstrap again.
    bindJQuery();
    
    publishExternalAPI(angular);
    
    jqLite(document).ready(function() {
      angularInit(document, bootstrap);
    });

    1. 绑定jQuery,如果外部引入了jQuery文件,则使用外部jQuery,如果没有引入外部jQuery文件,AngularJs内部定义了简化版的jQuery:JQLite

    2. 绑定jQuery之后,执行函数publishExternalAPI

      2.1 首先扩展angular对象,给对象添加一些内置的方法函数(文档中列出的angular.***方法函数)

      2.2 然后定义angular.module方法,用于定义模块

      2.3 使用angular.module方法定义ngLocale模块、ng模块,并注册内置的指令(directive)和服务(provider)(这些就是文档中列出的指令和服务)

    3. 等待document ready之后,执行函数angularInit

      3.1 首先寻找包含ng-app(或者其他几种写法)的HTML元素,如果找到,则自动执行bootstrap函数启动Angular,如果没有找到,那么需要我们在外部手动调用angular.bootstrap启动Angular

      3.2 bootstrap中会生成一个注入器injector,然后执行以下代码,并最后返回一个注入器实例

    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
       function bootstrapApply(scope, element, compile, injector) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );

    以上是Angular的启动过程,其中最后一步(以上代码)留到后面详细说明。

    双向绑定

    以上代码中,injector.invoke的作用是执行注入,第一个注入的是$rootScope(源码中对应的是$RootScopeProvider,在以上启动过程说明2.3中注册的服务之一)

    我们知道,在Angular中,控制器中都会注入$scope,$scope上定义的属性和方法,可以直接写在页面中,Angular会自动识别。$scope相当于视图与控制器之间的连接桥梁,视图改变会更新$scope中的数据,在控制器中对$scope上的属性进行修改,也会反映到视图上,这就是我们说的双向数据绑定。 

    compile是Angular编译指令的过程,在这个过程中,Angular会扫描整个ng-app文档,为$scope上的属性和文档中的变量名建立观察对象,并将该观察对象加入当前scope的$$watchers数组中,观察对象如下所示,其中watchFn表示观察函数,当watchFn的返回值发生变化时,需要执行的函数为listenerFn,那么当前scope中的所有需要观察的对象都记录在了$$watchers数组中。

    var watch = {
        watchFn: watchFn,
        listenerFn: listenerFn
    };

    我们看到compile函数放在scope.apply()中执行,那么,在编译完成之后,Angular会为我们调用$digest循环,$digest循环会遍历$$watchers数组,对于数组中的每一个对象,如果watchFn的返回的新值与旧值比较发生了变化,那么就会执行listenerFn,如果listenerFn中对$scope上的数据有改变,那么$digest会再执行一次,直到数据没有改变为止(Angular默认的最大循环次数是10次)。这样就实现了数据到视图的绑定。

    而对于视图到数据的过程,Angular绑定了input、textarea等的input、change事件来实现,例如,当通过键盘输入使得input的value发生改变时,会触发input事件,该事件回调中实现了对$scope上数据进行更改的操作,并调用$scope.apply()来使得$scope上的数据更改之后进入$digest循环。

    下面是例子:

    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <meta charset="UTF-8">
        <title>双向绑定</title>
        <script src="jquery.js"></script>
        <script src="two-way-binding.js"></script>
    </head>
    <body>
    <input id="input" type="text">
    <span id="content"></span>
    
    </body>
    </html>
    //two-way-binding.js
    
    function Scope() {
        this.$$watchers = []; //记录所有观察者
    }
    
    /**
     * Scope原型
     */
    Scope.prototype = {
        constructor: Scope,
        /**
         * 添加观察者,向$$watchers数组中添加一个对象
         * @param watchFn
         * @param listenerFn
         */
        $watch: function (watchFn, listenerFn) {
            var watch = {
                watchFn: watchFn,
                listenerFn: listenerFn
            };
            this.$$watchers.push(watch);
        },
        /**
         * 脏值检测
         */
        $digest: function () {
            var ttl = 10;
            var dirty;
            do {
                dirty = this.$$digestOnce();
                if(dirty && !(ttl--)) {
                    throw '10 digest iterations reached';
                }
            } while (dirty)
        },
        $$digestOnce: function () {var dirty;
            this.$$watchers.forEach(function (watch) {
                var newValue = watch.watchFn();
                var oldValue = watch.last;
                if (newValue !== oldValue) {
                    watch.listenerFn(newValue, oldValue);
                    dirty = true;
                    watch.last = newValue;
                }
            })
            return dirty;
        }
    }
    
    
    /**
     * 例子
     */
    window.onload = function () {
        var $input = $('#input');
        var $content = $('#content');
    
        //Scope实例
        var $scope = new Scope();
        $scope.name = 'foo';
    
    
        /**
         * 添加一个观察者(Angular内部在指令编译过程中完成)
         */
        $scope.$watch(function () {
            return $scope.name;
        }, function (newValue, oldValue) {
            $input.val(newValue);
            $content.text(newValue);
            console.log(newValue)
        })
    
        /**
         * 执行脏值检测,视图更新
         * (Angular内部,当DOM ready之后,Angular启动,然后进行指令编译过程,指令编译完成之后执行$digest循环)
         */
        $scope.$digest();
    
        /**
         * 视图上更改input的值,更新数据并执行脏值检测
         */
        $input.on('input', listener);
        $input.on('change', listener);
        function listener(e) {
            $scope.name = $(e.target).val();
            $scope.$digest();
        }
    }
  • 相关阅读:
    springboot 项目使用阿里云短信服务发送手机验证码
    Vue中el-form标签中的自定义el-select下拉框标签
    解决jQuery中input 失去焦点之后,不能再获取到焦点
    Java操作Jxl实现数据交互。三部曲——《第三篇》
    Java操作Jxl实现数据交互。三部曲——《第二篇》
    springboot搭建项目,实现Java生成随机图片验证码。
    echarts 配置属性参考
    最近用到echarts tab 切换遇到问题 可以参考下
    单位px和em,rem的区别
    关于定位
  • 原文地址:https://www.cnblogs.com/zmiaozzz/p/6529745.html
Copyright © 2011-2022 走看看