angularjs脏检查
angularjs实现了双向绑定,与vue的defineProperty不同,它的原理在于它的脏检查机制,以下做了一些总结;
angular.js介绍
- AngularJs是mvvm框架,它的组件是vm组件,scope是vm组件的数据集合
- AngularJs通过directive来声明vm的行为,它实现为一个watcher,监听scope的属性的变化,把最新的属性更新UI
- AngularJs的双向绑定:如:一个是将$scope属性值绑定到HTML结构中,当$scope属性值发生变化的时候界面也发生变化;另一个是,当用户在界面上进行操作,例如点击、输入、选择时,自动触发$scope属性的变化(界面也可能跟着变)
- 监听scope的属性变更:脏检查(dirty check )
脏检查
- angular根本不监听数据的变动,而是在恰当的时机($watch)从$rootScope开始遍历所有$scope,
- 检查它们上面的属性值是否有变化,如果有变化,就用一个变量dirty记录为true,再次进行遍历($digest),
- 如此往复,直到某一个遍历完成时,这些$scope的属性值都没有变化时,结束遍历。
- 由于使用了一个dirty变量作为记录,因此被称为脏检查机制。
简而言之: 当作用域创建时,angular会去解析模板,将绑定值和事件调用找出来并用$watch绑定;
$scope.$watch(string|function, listener, objectEquality, prettyPrintExpression)
// string: 验证值或者function执行后return的string
// listener: 如果验证值不同,则执行该监听函数
// objectEquality:执行深检查
- 完成绑定后,就会自动检测这些属性的变化,执行$watch, 那么对应的信息被绑定到angular内部的一个$$watchers中,
- 它是一个队列(数组),而当$digest被触发时,angular就会去遍历这个数组,
- 并且用一个dirty变量记录$$watchers里面记录的那些$scope属性是否有变化
接下来的流程:
- 判断dirty是否为true,如果为false,则不进行$digest递归。(dirty默认为true)
- 遍历$$watchers,取出对应的属性值的老值和新值根据objectEquality进行新老值的对比。
- 如果两个值不同,则继续往下执行。如果两个值相同,则设置dirty为false。
- 检查完所有的watcher之后,如果dirty还为true, 设置dirty为true用新值代替老值;
- 这样,在下一轮递归的时候,老值就是这一轮的新值再次调用$digest(简单说就是执行两次递归遍历检查新旧值变化)
- 将变化后的$scope重新渲染到界面
$apply触发$digest
- 一般不调用$digest, 调用$apply,它内部会触发$digest递归遍历
- angular的内部指令封装了$apply,比如ng-click,所以一般不用手动调用apply
手动调用apply
- 部分时候需要手动触发
function($timeout) {
// 当我们通过on('click')的方式触发某些更新的时候,可以这样做
$timeout(() => { // 内置语法糖 $http, $timeout已经包含了apply
$scope.name = 'lily'
})
// 也可以这样做
$element.on('click', () => {
$scope.name = 'david'
$scope.$apply()
})
}
注意:在递归过程中,绝对不能手动调用$apply,比如在ng-click的函数中,比如在$watch的回调函数中。
最后,实现一个简易脏检查机制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>angularjs脏检查实现</title>
</head>
<style type="text/css">
button {
height: 60px;
width: 100px;
}
p {
margin-left: 20px;
}
</style>
<body>
<div>
<button type="button" ng-click="increase">增加</button>
<button type="button" ng-click="decrease">减少</button>
数量:<span ng-bind="data">0</span>
</div>
<br>
</body>
<script>
window.onload = function () {
/**
* 声明构造函数
*/
function Scope() {
this.$$watchList = []; // angular内部会声明一个数组(包含监听的对象),在digest执行时去遍历
}
/**
* 属性赋值给$scope
* 类似angular解析controller的模板,把模板中的属性解析出来,属性赋值给$scope
*/
Scope.prototype.getNewValue = function () {
return $scope[this.name];
}
/**
* 实现监听
*/
Scope.prototype.$watch = function (name, listener) {
let watch = {
name: name,
getNewValue: this.getNewValue,
listener: listener || function () { }
};
// 当作用域创建时,angular会去解析模板,$watch用来绑定监听值和监听函数
this.$$watchList.push(watch);
}
/**
* 检查dirty,循环更新scope上的最新值
*/
Scope.prototype.$digest = function () {
console.log('$digest');
let dirty = true; // 默认dirty变量为true
let checkTimes = 0;
while (dirty) {
dirty = this.$valScope();
checkTimes++;
if (checkTimes > 10 && dirty) {
throw new Error("循环过多");
}
}
}
/**
* 验证值是否有变化
*/
Scope.prototype.$valScope = function () {
let dirty;
let list = this.$$watchList;
for (let i = 0; i < list.length; i++) {
let watch = list[i];
let newValue = watch.getNewValue();
let oldValue = watch.last || undefined;
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
dirty = true; // 如果新旧值不同,则继续遍历
} else {
dirty = false;
}
watch.last = newValue;
}
return dirty;
}
/**
* 刷新scope
*/
Scope.prototype.$apply = function (params) {
let list = document.querySelectorAll('[ng-bind]');
console.log('list', list)
for (let i = 0, l = list.length; i < l; i++) {
let bindData = list[i].getAttribute('ng-bind');
console.log('bindData', bindData)
console.log('list[i]', list[i])
list[i].innerHTML = $scope[bindData];
}
}
let $scope = new Scope(); // 实例化,声明$scope对象集合
$scope.data = 0;
$scope.increase = function () {
this.data++;
};
$scope.decrease = function () {
this.data--;
};
$scope.$watch('data', function(newValue, oldValue) { // 监听
console.log("new: " + newValue + "=========" + "old: " + oldValue);
});
// 手动为button按钮添加onclick事件,并为通过闭包为其绑定了全局scope对象,绑定apply方法
// 类似angular内部实现
function startBind() {
let list = document.querySelectorAll('[ng-click]');
for (let i = 0, l = list.length; i < l; i++) {
list[i].onclick = (function (index) {
return function () {
let func = this.getAttribute('ng-click');
$scope[func]($scope);
$scope.$digest();
$scope.$apply()
}
})(i)
}
}
// 初始化
startBind();
}
</script>
</html>
angularjs脏检查的更多相关文章
- 手写AngularJS脏检查机制
什么是脏检查 View -> Model 浏览器提供有User Event触发事件的API,例如,click,change等 Model -> View 浏览器没有数据监测API. Ang ...
- AngularJS 脏检查深入分析
写在开头 关于Angular脏检查,之前没有仔细学习,只是旁听道说,Angular 会定时的进行周期性数据检查,将前台和后台数据进行比较,所以非常损耗性能. 这是大错而特错的.我甚至在新浪前端面试的时 ...
- AngularJS 脏检查机制
脏检查是AngularJS的核心机制之一,它是实现双向绑定.MVVM模式的重要基础. 一.digest循环 AngularJS将双向绑定转换为一个堆watch表达式,然后递归检查这些watch表达式的 ...
- Angularjs 脏值检测
文章转自:http://www.ituring.com.cn/article/39865 构建自己的AngularJS,第一部分:Scope和Digest 原文链接:http://teropa.inf ...
- Hibernate延迟加载、三种状态、脏检查 缓存
一.持久化对象的唯一标识 java中按内存地址不同区分同一个类的不同对象,关系数据库用主键区分同一条记录,Hibernate使用OID来建立内存中的对象和数据库中记录的对应关系 什么是OID? 解析: ...
- 脏检查and刷新机构
---恢复内容开始--- 脏检查:当食物提交时,Hiberante会对Session中持久状态的对象进行加测,判断对象的数据是否发生了变化 为什么要进行脏检查?, 解析:如果对象发生了改变,就需要将改 ...
- Angular:手动脏检查/$apply/$digest和监控对象/$watch
声明:借鉴好多chm资料.视频.PDF总结如下: 一.$apply的引入 View <div ng-app=""> <div ng-controller=&quo ...
- OID,主键生成策略,PO VO DTO,get和load区别,脏检查,快照,java对象的三种状态
主键生成策略 sequence 数据库端 native 数据库端 uuid 程序端 自动赋值 生成的是一个32位的16进制数 实体类需把ID改成String 类型 assigned 程序端 需手 ...
- 关于Hibernate脏检查与快照
脏检查 Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会 为Customer对象的值类型的属性复制一份快照.当Session清理缓存时 ...
随机推荐
- Python 之父说 Python 历史
前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:鸿影洲冷 这篇文章主要内容来源于 Python 编程语言的最初设计者 ...
- 洛谷 P6082 [JSOI2015]salesman
题意 给定一棵\(n\)个点的树,有点权,你从\(1\)号点开始一次旅行,最后回到\(1\)号点.每到达一个点,你就能获得等于该点点权的收益, 但每个点都有进入该点的次数限制,且每个点的收益只能获得一 ...
- BUUCTF-BJD(更新V1.0)
CTF-Day1 (PS:第一次写博客,就是想记录自己的一一步一步) Misc: 问卷调查 | SOLVED |题最简单的misc-y1ng | SOLVED |Real_EasyBaBa | SOL ...
- Linux使用pyinstaller 编译py成可执行程序
0x00 pyinstall 安装 命令行pip install pyinstaller安装 0x01 编译时使用命令pyinstaller -F -w /root/pyfile/图片GPS位置获取 ...
- PE文件格式详解(二)
0x00 前言 上一篇讲到了PE文件头的中IMAGE_FILE_HEADER结构的第二个结构,今天从IMAGE_FILE_HEADER中第三个结构sizeOfOptionalHeader讲起.这个字段 ...
- SpringBoot日志功能
三.SpringBoot日志功能 1.日志框架 市面上的日志框架: JUL.JCL.Jboss-logging.Logback.Log4j.Log4j.SLF4J... 日志门面(日志的抽象层) 日志 ...
- 08 Flask源码剖析之flask拓展点
08 Flask源码剖析之flask拓展点 1. 信号(源码) 信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作. pip3 install blinker 2. 根据flas ...
- Python之介绍、基本语法、流程控制
本节内容 Python介绍 发展史 Python 2 or 3? 安装 Hello World程序 变量 用户输入 模块初识 .pyc是个什么鬼? 数据类型初识 数据运算 表达式if ...else语 ...
- Ethical Hacking - Web Penetration Testing(13)
OWASP ZAP(ZED ATTACK PROXY) Automatically find vulnerabilities in web applications. Free and easy to ...
- webpack源码-loader的原理
版本 webpack :"version": "3.12.0", webpack配置中的loaders配置是如何传递的 webpack/lib/NormalMo ...