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清理缓存时 ...
随机推荐
- 洛谷 P1220 关路灯 区间DP
题目描述 某一村庄在一条路线上安装了 n 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少).老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯. 为了 ...
- Error: error getting chaincode bytes: failed to calculate dependencies报错解决办法
Error: error getting chaincode bytes: failed to calculate dependencies: incomplete package: github.c ...
- django框架效率
1. django ORM模式提供食物处理类:transaction.Django默认的事务处理方式时改动就提交,每执行一次就立即提交,这就会花费大量的时间用于IO.Django也支持所有工作都完成后 ...
- 一、kafka 安装配置
Kafka是什么 Kafka最初是由LinkedIn公司采用Scala语言开发的一个分布式.多分区.多副本且基于ZooKeeper协调的内部基础设置,现已捐献给Apache基金会.Kafka是一个流平 ...
- C#联合WINCC之数据通信
[公众号dotNet工控上位机:thinger_swj] 在工控领域中,WINCC仍然占有很大的市场份额.很多时候我们说学习C#开发上位机可以取代传统的组态软件,两者就像冤家一样,然而,即使是冤家,也 ...
- MYSQL 之 JDBC(十): JDBC的元数据
可以从Connection对象中获得有关数据库管理系统的各种信息 获取这些信息的方法都是在DatabaseMetaData类中. DatabaseMetaData:描述数据库的元数据对象 Result ...
- web 部署专题(五):nginx 安装(一) 树莓派
1.安装Nginx sudo apt-get install nginx 2.启动Nginx sudo /etc/init.d/nginx start 3.测试安装是否成功(nginx默认是80端口) ...
- mongodb(一):数据库安装,部署(windows)
全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/10885038.html,多谢~=.= 安装MongoDB 双击运行msi文件,一直“Next”. ...
- Flink之对时间的处理
window+trigger+watermark处理全局乱序数据,指定窗口上的allowedLateness可以处理特定窗口操作的局部事件时间乱序数据 1.流处理系统中的微批 Flink内部也使用了某 ...
- python+requests实现接口自动化
1. 前言 今年2月调去支持项目接口测试,测试过程中使用过postman.jmeter工具,基本能满足使用,但是部分情况下使用较为麻烦.比如:部分字段存在唯一性校验或字段间有业务性校验,每次请求均需手 ...