服务
1.Constant
2.Value
3.Service
4.Factory
5.Provider
除了Constant,所有类型服务背后都通过Provider实现
angular.module('lulu.app').provider('greeting', function(){
var _name = '66';
this.setName = function(name){
_name = name;
};
this.$get = function(){
return _name;
}
});
angular.module('lulu.app').controller('SomeCtrl', function($scope, greeting){
$scope.message = greeting; //值为66
});
//provider可配置
angular.module('lulu.app').config(function(greetingProvider){
greetingProvider.setName('lulu');
});
//value
angular.module('lulu.app').value('greeting', '66');
//service
angular.module('lulu.app').service('greeting', function(){
this.say = function(name){
return name;
}
});
//等价于
angular.module('lulu.app').provider('greeting', function(){
this.$get = function(){
var Greeting = function(){
this.say = function(name){
return name;
};
};
//service用new的方式创建
return new Greeting();
};
});
//factory
angular.module('lulu.app').factory('greeting', function(){
return 66;
});
//等价于
angular.module("lulu.app").provider('greeting', function(){
this.$get = function(){
var greeting = function(){
return 66;
}
return greeting();
}
});
***service和factory区别在于内部创建时一个是new一个直接返回
//constant
angular.module('lulu.app').constant('greeting', '66');
Constant时机非常早,可以在Config中使用
MVVM
View:专注显示,视图模板
ViewModel:负责给View提供显示数据,以及供View操作Model途径,$scope对象充当了这个角色
Model:领域对象,业务相关数据
Controller:负责ViewModel对象初始化
Angular启动过程
1.浏览器下载HTML/CSS/Javascript
2.浏览器开始构建DOM
3.Jquery初始化
4.Angular初始化:创建各种模块,在模块中注册各种Angular对象
5.Jquery启动
6.AngularI启动,查找第一个带有ng-app的节点
7.加载子模块,关联DOM和模块,使DOM变活(展示数据,响应事件)
8.启动子模块,执行run回调
9.渲染页面
10.数据绑定与digest循环
依赖注入DI
只要指出我需要哪些对象,让后就会有人(框架)把这个对象给我
Javascript中实现DI
函数对象的toString(),返回函数源码,解析源码参数
Angular中的DI
所有主要编程元素都要通过某种方式注册
注册表Module,Angular跨Controller共享数据或通讯,可以创建Service/Value/Constant分别注入,共享同一对象
provider通过$get函数注入,provider('test', function(/只能注入constant, provider/))
循环依赖不能使用依赖注入,可以在代码内部调用
var http = $injector.get('$http');
Digest
Angular将双向绑定转换成一堆watch,递归检查watch表达式结果是否改变,等到Model值不再变化,不会触发watcher函数,一个完整的digest循环结束
Angular拓展了浏览器事件模型,建立了自己上下文,ngClick, ngChange会将浏览器事件转化为$scope的响应函数,响应函数中改变Model,触发脏检查机制,并不存在定时的脏检查
遍历一遍所有watcher函数称为一轮脏检查,执行完一轮,如果任何一个watcher监听值改变,再进行一轮脏检查,直到所有watchers函数都报告值不变了,$digest循环结束,才能把变化更新到DOM
何时进入脏检查?
每一个进入Angular上下文环境的事件,都会执行一次$digest
$watch函数返回一个反注册函数
$scope.$apply(function(){}); //手动触发digest循环
$rootScopt是所有$scope基础
指令生命周期
Inject, Compile, Controller加载, pre-link, post-link
angular.module('lulu.app').directive('test', function(){
console.log('Inject'); //只发生一次
return {
restrict:'EA',
transclude:true,
replace:true,
template:'<div>{{count}}</div>',
scope:{
count:'='
},
//每个指令实例化时执行一次,传入elm还未被link,无法访问$scope
compile:function(elm, iAttrs){
console.log('compile' + iAttrs.count);
//controll初始化$scope后,进入正式解析过程,对每个实例只执行一次
return {
//从父节点到子节点触发,子节点DOM不稳定,不适合在其上加DOM监听
pre:function(scope, elm, iAttrs){
console.log('pre-link' + iAttrs.count + ' scope' + scope.count);
},
//从子节点到父节点触发
post:function(scope, elm, iAttrs){
console.log('post-link' + iAttrs.count + ' scope' + scope.count);
}
};
},
//初始化$scope
controller:function($scope){
console.log('controller');
}
};
});
$observe监听DOM中属性值变化
$watch监听scope属性变化
指令scope绑定策略
@绑定{{}},单向绑定,返回String
=绑定一个对象,双向绑定,返回Object
&绑定一个函数
指令scope作用域
false:直接使用父级scope
true:继承父级scope,创建后copy父scope,与父级无关
{}:创建一个新的隔离scope
controller as vm
好处:
1.$scope注入不再是必须的(除非用到$watch, $emit, $on等)
2.避免this指针坑
3.避免原型链继承对于值类型的坑(视图模板所有字段都限制于vm别名应用的属性,会导致整个页面刷新)
<div>{{vm.name}}</div>
(function(){
angular.module('lulu.app')
.controller('HomeCtrl', HomeCtrl);
function HomeCtrl(){
var vm = this;
vm.name = 66;
};
});
性能
1.移除不必要的$watch
2.::语法,实现one-time绑定
3.滚屏加载数据,分部加载数据
4.$compileProvider.debugInfoEnabled(true);
5.慎用filter,$digest中,filter至少执行2次,应避免filter执行耗时操作,或在controller中预先处理完数据在视图中直接绑定
6.ng-repeat添加track by可以避免$scope.tasks = data;移除所有DOM后重新渲染,track by可以对应原DOM进行更新
angular不会大范围更新DOM,每次更新区域小,超过2000个watcher需要好好考虑优化
拦截器(AOP机制)
实现Ajax请求拦截切入
防闪烁
{{}}替换为ng-bind
ngCloak通过样式切换实现隐藏显示
父子$scope嵌套
默认使用原型链继承来至父级的$scope
<div ng-controller='ParentCtrl'>
{{greeting}}
<input type="text" ng-model="greet"/>
<div ng-controller='ChildCtrl'>
{{greeting}}
<input type="text" ng-model="greet"/>
</div>
</div>
//ParentCtrl
$scope.greeting = "66";
导致的问题:
1.开始全部显示66
2.改变父input值,全部同步数据
3.改变子input值,子组件同步数据,父组件不能同步,再改变父input,子组件也不会同步数据
原因:
子组件开始自己没有greeting,会向上查找使用父的greeting属性
当子组件input值变更,子组件创建自己的greeting属性,后就与父级greeting属性隔离
使用controller as vm语法绑定vm.greeting可避免
路由
(function(){
angular.module('lulu.routes')
.config(routesConfig);
function routesConfig($stateProvide, $urlRouterProvider){
$stateProvide
.state('book', {
cache:true,
url:'/book',
templateUrl:'templates/product/book.html',
controller: 'BookCtrl',
controllerAs: 'vm',
resolve:{
//返回一个promise,如promise状态reject,会挡住路由
//如不是promise, 对象会被注入到controller
loggedIn:function(mkAuth){
return mkAuth.checkLoggedIn();
}
}
})
};
});
ng-show/ng-hide/ng-if
ng-show/ng-hide通过css的display实现隐藏显示,ng-if通过移除添加DOM实现
$rootScope和$scope
$rootScope是页面所有$scope父级
1.angular解析ng-app创建$rootScope
2.解析{{}}为变量
3.解析ng-controller创建$scope
{{}}原理
使用$interpolation服务查看文本节点是否有{{}}(插补标记),有则注册watches,成为digest检查的一部分
$timeout.cancel();
ng-repeat迭代
track by $index解决绑定数据相同(唯一表示数据和DOM关系)
页面{{}},ng-click中可以用js原生方法么?
不能,因为$scope上下文不存在那些原生方法,如用as vm模式可以吧?
SPA缺点
1.SEO,可通过Prerender解决部分
2.前进,后退等要程序管理
SPA SEO解决方案:
1.接入prerender.io等预渲染服务
2.腾出一台服务器搭建phantomjs搞预渲染
3.将爬虫请求引导到后台定时生成或抓取的静态HTML页
4.SSR
项目
微信登录授权
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<a href="https://open.weixin.qq.com/connect/oauth2/authorize?appid={3}&redirect_uri={2}&response_type=code&scope=snsapi_userinfo&state={0}#wechat_redirect">微信登录</a>
function freshConfig() {
var url = $location.absUrl();
url = url.split('#')[0];
//传入当前url
weixin.jssdkConfig({url: url}).then(function (data) {
if (data.code == 0) {
//配置wx
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: mkGlobalVal.weixin_appid, // 必填,公众号的唯一标识
timestamp: parseInt(data.result.timestamp), // 必填,生成签名的时间戳
nonceStr: data.result.noncestr, // 必填,生成签名的随机串
signature: data.result.signature,// 必填,签名,见附录1
jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQQ', 'onMenuShareWeibo', 'onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
}
});
};
function onShare(share) {
//分享到朋友圈
wx.onMenuShareTimeline(share);
//分享给朋友
wx.onMenuShareAppMessage(share);
//分享到QQ
wx.onMenuShareQQ(share);
//分享到腾讯微博
wx.onMenuShareWeibo(share);
//分享到QQ空间
wx.onMenuShareQZone(share);
}
//微信支付
if ($scope.mkTools.agentIsWeichat) {
orderInfo = {
"openid": $scope.currentUser.authData.weixin.openid,
"no": data.no,
"total_fee": data.total_fee,
"body": data.product_snapshot.title
};
mkPayTools.weixinPay(orderInfo).then(angular.noop, error);
}
//创建订单成功后,呼出微信支付界面
function weixinBridge(data, defer) {
function onBridgeReady() {
WeixinJSBridge.invoke('getBrandWCPayRequest', data, function (res) {
//支付成功
if (res.err_msg == "get_brand_wcpay_request:ok") {
defer.resolve();
window.location.href = "/#/payresult";
}
else {
defer.reject();
}
});
};
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
}
else {
onBridgeReady();
}
};
return defer.promise;
};