AngularJS 1.x 思维索引
我们在这里不讨论Angular2和Angular4,因为其完全重写,其实已经不叫AngularJS了。
AngularJS的缺陷:
- 性能问题:通过检查脏值进行数据更新,当数据不断增加时,检查的效率就不断降低。页面加载速度也会变慢。
- 落后于当前web发展理念(如组件式的开发)
- 对手机端的支持不是太友好
1. 执行时机
angularJS定义是一个立即执行的匿名函数,那么其执行时机为引入angularJS的位置决定。
(function(window) {
...
})(window);
2. 源码结构
基于1.6.9
源码的上部分基本在定义方法,其主要执行开始在源码的最后,主要函数如下:
bindJQuery(); publishExternalAPI(angular); jqLite(function() {
angularInit(window.document, bootstrap);
});
2.1 bindJQuery:
尝试绑定jQuery对象,如果没有则采用内置的jqLite
2.2 publishExternalAPI:
- 绑定一些公共的方法到angular,如copy,bind等。
function publishExternalAPI(angular) {
extend(angular, {
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'bind': bind,
...
});
angularModule = setupModuleLoader(window);
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
...
}
...
}
- setupModuleLoader:在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性,所以一般我们创建模块都是用
angular.module
方法。
- setupModuleLoader:在window下面创建全局的angular对象,并且返回一个高阶函数,赋值给了angular.module属性,所以一般我们创建模块都是用
2.3 angularInit:
找到带angular项目标识的元素,然后调用bootstrap方法。
function angularInit(element, bootstrap) {
var appElement,
module,
config = {};
forEach(ngAttrPrefixes, function(prefix) {
var name = prefix + 'app';
if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
appElement = element;
module = element.getAttribute(name);
}
});
...
if (appElement) {
...
bootstrap(appElement, module ? [module] : [], config);
}
}
bootstrap函数主要功能:创建注入器和用注入器进行调用
function bootstrap(element, modules, config) {
...
var injector = createInjector(modules, config.strictDi);
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function bootstrapApply(scope, element, compile, injector) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
...
}
createInjector:其内部代码复杂,DI及provider在此不做展开。在这里模块加载完成后,运行模块,生成实例。
injector.invoke:生成编译实例,通过编译实例去编译项目起始页,编译的核心是生成指令对应的link函数,有点类似后端的编译,先词法分析,用lex,然后语法分析,用parse,最后链接,生成link函数。具体分析如下
2.4 RootScope核心
在publishExternalAPI函数中我们定义了Provider,这个provider实现了AngularJS的核心方法。
$rootScope: $RootScopeProvider
接下来看看该provider的源码,下一章我们将分析其核心实现细节。
function $RootScopeProvider() {
var TTL = 10;
this.digestTtl = function(value) {
...
}; this.$get = ['$exceptionHandler', '$parse', '$browser',
function($exceptionHandler, $parse, $browser) { function Scope() {
...
} Scope.prototype = {
constructor: Scope,
$new: function,
$watch: function,
$watchGroup: function,
$watchCollection: function,
$digest: function,
$destroy: function,
$eval: function,
$evalAsync: function,
$$postDigest: function,
$apply: function,
$applyAsync: function,
$on: function,
$emit: function,
$broadcast: function,
}; var $rootScope = new Scope();
}
];
}
3. 核心实现
3.1 监控对象属性
$watch和$digest是相辅相成的。两者一起,构成了Angular作用域的核心:数据变化的响应。
3.2 $watch
作用为在Scope上添加一个监听器。当Scope上发生变更时,监听器会收到提示。监听器包括下面二个函数:
- 一个监控函数,用于指定所关注的那部分数据。
- 一个监听函数,用于在数据变更的时候接受提示。
通常来说这里的监听器是监控一个表达式。监控表达式是一个字符串,比如说“{{user.firstName}}”,通常在数据绑定,指令的属性,或者JavaScript代码中指定,它被Angular解析和编译成一个监控函数。
Angular框架中,双美元符前缀$$表示这个变量被当作私有的来考虑,不应当在外部代码中调用。
$watch源码如下:
$watch: function(watchExp, listener, objectEquality, prettyPrintExpression) {
array = scope.$$watchers,
watcher = {
fn: fn,
last: initWatchVal,
get: get,
exp: prettyPrintExpression || watchExp,
eq: !!objectEquality
}; array.unshift(watcher); return function deregisterWatch() {
var index = arrayRemove(array, watcher);
if (index >= 0) {
incrementWatchersCount(scope, -1);
if (index < array.$$digestWatchIndex) {
array.$$digestWatchIndex--;
}
}
lastDirtyWatch = null;
};
},
该函数定义在原型上,为所有scope实例共用。
该方法主要接受两个函数作参数(表达式和监听函数),把它们存储在$$watchers数组中 - array.unshift(watcher);
Wather数组中的监听器将会被下面的digest函数调用。
3.3 $digest
Digest函数是angularjs的核心方法之一,进行脏值检查,代码如下:
do { // "traverse the scopes" loop
if ((watchers = current.$$watchers)) {
watchers.$$digestWatchIndex = watchers.length;
while (watchers.$$digestWatchIndex--) {
try {
watch = watchers[watchers.$$digestWatchIndex];
// Most common watches are on primitives, in which case we can short
// circuit it with === operator, only when === fails do we use .equals
if (watch) {
get = watch.get;
if ((value = get(current)) !== (last = watch.last) &&
!(watch.eq
? equals(value, last)
: (isNumberNaN(value) && isNumberNaN(last)))) {
dirty = true;
lastDirtyWatch = watch;
watch.last = watch.eq ? copy(value, null) : value;
fn = watch.fn;
fn(value, ((last === initWatchVal) ? value : last), current);
if (ttl < 5) {
logIdx = 4 - ttl;
if (!watchLog[logIdx]) watchLog[logIdx] = [];
watchLog[logIdx].push({
msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp,
newVal: value,
oldVal: last
});
}
} else if (watch === lastDirtyWatch) {
// If the most recently dirty watcher is now clean, short circuit since the remaining watchers
// have already been tested.
dirty = false;
break traverseScopesLoop;
}
}
} catch (e) {
$exceptionHandler(e);
}
}
}
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $broadcast
if (!(next = ((current.$$watchersCount && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
while (current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
}
}
} while ((current = next));
Loop所有的watcher,通过get方法取得当前值,与 记录的上次值(watch.last)比较,如果不同则值为脏值。
更新watch.last为当前新值,调用watcher上的监听函数,并把新值作为参数。
注意:在监听函数中有可能会再次更改scope中的值,在最后一部分有个疯狂的深度优先遍历。这里会有个问题,如果一直有脏值怎么办,注意代码中有个TTL最大try的次数。
以上实现了实现了Angular作用域的本质:添加监听器,在digest里运行它们。
特性:
在作用域上添加数据本身并不会有性能折扣。如果没有监听器在监控某个属性,它在不在作用域上都无所谓。Angular并不会遍历作用域的属性,它遍历的是监听器。
$digest里会调用每个监控函数,因此,最好关注监听器的数量,还有每个独立的监控函数或者表达式的性能。
3.4 $apply
作用:集成外部代码与digest循环
$apply使用函数作参数,它用$eval执行这个函数,然后通过$digest触发digest循环。
$apply: function(expr) {
try {
beginPhase('$apply');
try {
return this.$eval(expr);
} finally {
clearPhase();
}
} catch (e) {
$exceptionHandler(e);
} finally {
try {
$rootScope.$digest();
} catch (e) {
$exceptionHandler(e);
// eslint-disable-next-line no-unsafe-finally
throw e;
}
}
},
apply的源码非常简单,如上所示,通过eval调用指定的方法。并且保证执行后调用一次digest以进行脏值检查。
apply使用时机:DOM事件、setTimeout、XHR或其他第三方的库,特别是异步方法对scope中数据的改变不会被watcher监控到,需要显示调用apply告诉angularjs。
以上就是angularJS核心思想及其源码实现,有点懒没有展开的说,也有可能理解不够透彻,毕竟是过时的框架了,作为前端编程思想进行学习而已。
refers:
https://www.cnblogs.com/leo_wl/p/3446075.html
https://www.cnblogs.com/wuya16/p/3769032.html
https://blog.csdn.net/u013510614/article/details/50703811
https://blog.csdn.net/u013510614/article/details/50703813
https://blog.csdn.net/u013510614/article/details/50703815
https://blog.csdn.net/cteng/article/details/72823190
https://github.com/xufei/Make-Your-Own-AngularJS/blob/master/01.md
AngularJS 1.x 思维索引的更多相关文章
- 对照jQuery和AngularJS的不同思维模
对照jQuery和AngularJS的不同思维模 Question 如果我已经熟悉了怎样使用jQuery来开发client应用.我如今打算使用AngularJS.请描写叙述一下有那些思维模式方面的东西 ...
- AngularJS应用开发思维之3:依赖注入
找不到的API? AngularJS提供了一些功能的封装,但是当你试图通过全局对象angular去 访问这些功能时,却发现与以往遇到的库大不相同. $http 比如,在jQuery中,我们知道它的AP ...
- AngularJS应用开发思维之1:声明式界面
这篇博客之前承接上一篇:http://www.cnblogs.com/xuema/p/4335180.html 重写示例:模板.指令和视图 AngularJS最显著的特点是用静态的HTML文档,就可以 ...
- 对比jQuery和AngularJS的不同思维模式
jQuery是dom驱动,AngularJS是数据驱动,这里有一篇文章阐述的非常好,建议看看 本文来自StackOverFlow上How do I “think in AngularJS” if I ...
- 转载:Think in AngularJS:对比jQuery和AngularJS的不同思维模式(大漠穷秋)
导言 stackoverflow上有一个人问了一个问题:如果我有jQuery背景,我应该如何切换到AngularJS的思维模式? 有一个回复非常经典,获得了两千多票. 为了让国内开发者也能领略到其中的 ...
- AngularJS应用开发思维之2:数据绑定
在声明式模板中显示数据 因为不能像jQuery一样将DOM操作混在模板里,声明式模板很快让我们变得束手束脚. 一个典型的问题:在声明式模板里怎么显示数据? 假设我们有某人的基本信息,保存在一个json ...
- React 思维索引
关于分析React源码的文章已经有比较多,我就不献丑了. 根据分析的结果把React的大致流程和思维导图做了一点总结,图片如下: 源码在: https://github.com/laryosbert/ ...
- 模拟AngularJS之依赖注入
一.概述 AngularJS有一经典之处就是依赖注入,对于什么是依赖注入,熟悉spring的同学应该都非常了解了,但,对于前端而言,还是比较新颖的. 依赖注入,简而言之,就是解除硬编码,达到解偶的目的 ...
- AngularJS移动开发中的各种坑
捂脸,辛酸泪ing...... 本文主要涉及部分在移动设备上特有的问题. 相对来说,Jquery侧重DOM操作,AngularJS是以视图模型和双向绑定为核心的. DOM操作的问题 避免使用 jQue ...
随机推荐
- java并发中的Semaphore
什么是Semaphore Semaphore可以控制某个资源可被同时访问的个数(locks和synchronized锁,在任何时刻只允许一个任务访问一个资源),通过acquire()获取一个许可,如果 ...
- springboot application.properties 常用完整版配置信息
从springboot官方文档中扒出来的,留存一下以后应该会用到 # ================================================================= ...
- Python_在Ubuntu中搭建科学计算环境
本文针对 Ubuntu 下搭建 Python 科学计算的环境,罗列了关键词和部分链接,最后附上了自己的一点分享. 1.升级 关键词: python ubuntu 升级 推荐: ubuntu16.04下 ...
- 剑指Offer 62. 二叉搜索树的第k个结点 (二叉搜索树)
题目描述 给定一棵二叉搜索树,请找出其中的第k小的结点.例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4. 例如, 5 / \ 3 7 / \ / \ 2 4 6 ...
- CentOS 7 下使用yum安装MySQL5.7.20 最简单图文详解
CentOS7默认数据库是mariadb, 但是 好多用的都是mysql ,但是CentOS7的yum源中默认好像是没有mysql的. 上一篇安装的是5.6的但是我想安装5.7的 yum安装是最简单 ...
- 2018-计算机系机试-A
#include<stdio.h> #include<cstdio> #include<cmath> #include<cstring> #includ ...
- 部署Qt应用时候报错0xc000007b
情况: 在开发环境可以运行,部署到其他电脑无法运行: 排错:百度.谷歌了很多方法不行,后来发现添加了Qt\5.11.0\mingw53_32\bin环境变量,程序执行正常,去掉就报错:猜测估计是dll ...
- [C# 基础知识系列]专题二:委托的本质论 (转载)
引言: 上一个专题已经和大家分享了我理解的——C#中为什么需要委托,专题中简单介绍了下委托是什么以及委托简单的应用的,在这个专题中将对委托做进一步的介绍的,本专题主要对委本质和委托链进行讨论. 一.委 ...
- 神州数码NAT地址转换配置
实验要求:熟练掌握NAT地址转换的配置方法 拓扑如下 R1 enable 进入特权模式 config 进入全局模式 hostname R1 修改名称 interface s0/1 进入端口 ip ad ...
- Mac 安装Python3 facewap环境
参考网上大神的方法 1 官网下载安装 2 下载指定版本的源码cmake安装 3 Mac上使用homebrew进行安装(强烈推荐,主要是前两种的openssl模块我没有搞定链接什么的一直报错,一个个下载 ...