AngularJS核心原理解析
parser
##双向数据绑定 ##依赖注入
AngularJS启动过程分析
- 概述:
1.publishExternalAPI(angular):给angular全局对象加上公共工具函数(调用外部api)
2.setupModuleLoader(window):建立模块机制
3.注册内核provider(最重要的provider(服务):$parse(解析服务)与$rootScope(根服务,属性结构))
4.angularInit:防止多次初始化ng-app
5.bootstrap(angularJS自动的启动方法):创建injector,拉起内核和启动模块,调用compile服务
6.概述图:->用自执行函数形式让代码加载完立即执行(在window上暴露唯一的全局对象angular)
->检查是不是多次导入window.angular.bootstrap(通过检查指定元素是否存在injector进行判断(监测过程和实例化))
->尝试绑定jQuery(bindJquery())(如果有导入jQuery,则使用,否则使用Angular自己封装的JQLite)jq判断方法:
var hasImportJQuery = jQuery && jQuery.fn.on ? true : false;
->publishExternalAPI(发布ng提供的api):
1.工具函数拷贝angular全局对象
2.调用setupModuleLoader创建模块定义和加载工具(挂在window.angular上) 3.构建内置模块ng
4.创建ng内置的director和provider;
5.两个重要的provider: $parse和$rootScope;
->查找ng-app(angularInit(document, bootstrap));->找到bootstrap(element, modules, config)开始启动;->
1.此处bootstrap是ng的启动函数;
2.调用此函数则手动启动app,页面上不可出现ng-app
3.此函数会创建并返回一个注射器(injector),此注射器就是根注射器createInjector(modulesToLoad,strictDi)->
1.注射器有两种:providerInjector,instanceInjector
调用compile服务查找并编译指令
(function(doc, win){
if(window.angular.bootstrap) return;
bindJQuery();
publicExternalAPI(angular);
jqLite(document).ready(function(){
angularInit(document, bootstrap);
});
})(document, window)
- 方式:
1.自动启动:设置ng-app
2.手动启动var myModule = angular.module('myModule', []); myModule.controller('myCtrl', ['$scope', function($scope){ $scope.name = 'handleStart'; }]); angular.element.ready(function(){ angular.bootstrap(document, ['myModule']); });
3.多个ng-app:第二个、三个更多需要手动启动,angular会自动启动第一个ng-app
ps:如果多次启动(自动启动后再手动启动),会报错
- 绑定jquery:
- 全局对象angular(injector方法):
依赖注入原理分析:Provider与Injector
- 为什么需要依赖注入
- 复习依赖注入(实际上是内联注入)
- ng三种注入方式
1.推断注入:函数参数的名称必须和被注入的对象相同
var myModule = angular.module('myModule', []); myModule.controller('myCtrl', function($scope){ $scope.name = 'handleStart'; });
2.标注式注入:($injector.annotate(function(arg0,arg1){}))->分析函数参数
var myModule = angular.module('myModule', []); myModule.factory('game', function(){ return { name: 'handleStart' } }); myModule.controller('myCtrl', ['$scope', '$injector', function($scope, $injector){ $injector.$invoke(function(game){ $scope.name = game.name; }); }]);
3.内联注入:
var myModule = angular.module('myModule', []); myModule.controller('myCtrl', ['$scope', function($scope){ $scope.name = 'handleStart'; }]);
- 直接使用$injector(很少使用)
- provider模式与ng实现
1.provider是策略模式和工厂模式综合体
2.核心是为了让接口和实现分离
在ng中,所有provider都可以用来注入
provider/factory/service/constant/value
3.以下类型函数都可以接收注入: controller/directive/filter/service/factory等
4.ng中的”依赖注入”是通过provider和injector这两个机制联合实现的 - provider/factory/service/constant/value/decorator
1.provider是基础,其余都是基于provider实现
2.从左向右,灵活性越来越差1.provider实现方式:
var myModule = angular.module('myModule', []); // 通过provider定义的,都是可以注入的 myModule.provider('helloAngular', function(){ return { $get: function(){ var name = '慕课', getName = function(){ return name; }; return { getName: getName } } } }); myModule.controller('myCtrl', ['$scope', 'helloAngular', function($scope, helloAngular){ $scope.name = helloAngular.getName(); }]);
2.factory实现方式:
var myModule = angular.module('myModule', []); // 通过provider定义的,都是可以注入的 myModule.factory('helloAngular', function(){ var name = '慕课', getName = function(){ return name; }; return { getName: getName } }); myModule.controller('myCtrl', ['$scope', 'helloAngular', function($scope, helloAngular){ $scope.name = helloAngular.getName(); }]);
3.service实现方式:
var myModule = angular.module('myModule', []); // 通过provider定义的,都是可以注入的 myModule.service('helloAngular', function(){ this.name = '慕课'; this.getName = function(){ return this.name; }; }); myModule.controller('myCtrl', ['$scope', 'helloAngular', function($scope, helloAngular){ $scope.name = helloAngular.getName(); }]);
- 内置provider($ControllerProvider L7357)
- injector源码分析(创建、注册、调用)
指令的执行过程分析
- 自定义compile和link函数
1.compile函数:(若再定义link函数,则link函数无效)
```` var myModule = angular.module(‘myModule’, []); myModule.directive(‘autoHello’, function(){ return { restrict: ‘E’, compile: function(el, attrs, transclude){ // 从这里开始对标签元素自身进行一些变换 var tpl = el.children().clone(); for(var i = 0;i < attrs.autoHello-1;i++){ el.append(tpl.clone()); } // 实际上是link函数 return function(scope, el, attrs, controller){ console.log(‘指令链接’); } } } });
>2.link函数:
>
var myModule = angular.module(‘myModule’, []); myModule.directive(‘hello’, function(){ return { restrict: ‘E’, replace: true, template: ‘<div ng-bind="welcomeWord"></div>’, link: function(scope, el, attrs, controller){ scope.welcomeWord = ‘Hello, angularJS’; } } });
- compile和link区别
>1.compile函数的作用是对指令模板进行转换<br/>
>2.link的作用是在模型和视图间建立联系,包括在元素上注册监听事件<br/>
>3.scope在链接阶段才会被绑定到元素上,因此compile操作scope会报错<br/>
>4.对于同一个指令的多个示例,compile会执行一次,link函数会对指令的每个实例都会执行一次<br/>
>5.一般情况下,只需要编写link函数就行了<br/>
>6.若编写自定义compile函数,则link函数无效,因为自定义compile函数应该返回link函数后续处理
- 从最简单的<hello>分析指令源代码(compile)
>1.从ng-app开始,递归子层dom结构,收集指令<br/>
>2.如果有需要,为指令生成childScope,childScope绑定到元素的data属性上<br/>
>3.调用每个指令为自己的compile函数生成compositeLinkFn函数<br/>
>4.编译结果是返回一个publicLinkFn函数<br/>
>5.编译完成之后,立即调用生成的publicLinkFn函数<br/>
>>ps:外部逻辑用module的directive方法注册指令
>
var myModule = angular.module(‘myModule’, []); myModule.directive(‘hello’, function(){ return { restrict: ‘EA’, replace: true, template: ‘<div>hello</div>’, link: function(scope, el, attrs, controller){ // link } } });
### $scope与双向绑定解析
- 数据双向绑定实例
>Scope
var myModule = angular.module(‘myModule’, []); myModule.controller(‘myCtrl’, [‘$scope’, function($scop){ $scope.hello = ‘hello’; });
````
- ng是如何发现数据发生变化的
- 关于scope
- 被绑定对象的结构
1.由于ng的$digest机制和”对象比较”机制,ng在处理Tree型结构方面性能非常差
2.建议不对Tree型结构使用双向数据绑定
- 绑定过程中使用表达式
- ng支持哪些形式的表达式
1.数学运算:+,-,/,*,%
2.比较运算:==,!=,>,<,>=,<=
3.布尔运算:&&,||,!
4.位运算:^,&,|
5.对象和数组字面值:[],{}
ps:不支持if/for/while等逻辑控制;Parse:专门解析内联表达式
- 自己动手实现数据双向绑定
1.如何把一个Model绑定到多个View?(观察者模式)
2.如何才能知道Model发生了变化?(脏值检测$watch与$digest)
3.如果Model是深层次的嵌套结构,如何知道某个值是不是变了?(对象深比较)
4.深层次问题:A和B两个方法相互watch的时候,如何避免发生“震荡”?(TTL机制)
5.绑定过程中如何支持表达式?($parser与$eval自制js版编译器…) 参考链接