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();
    }
}

 

posted on 2017-03-10 11:26  zmiaozzz  阅读(451)  评论(0编辑  收藏  举报

导航