什么是脏检查

View -> Model

浏览器提供有User Event触发事件的API,例如,clickchange

Model -> View

浏览器没有数据监测API

AngularJS 提供了 $apply()$digest()$watch()

其他数据双向绑定介绍

VUE

{{}} Object.defineProperty() 中使用 setter / getter 钩子实现。

Angular

[()] 事件绑定加上属性绑定构成双向绑定。

怎么手写

大家先看运行效果,运行后,点增加,数字会+1,点减少,数字会-1,就是这么一个简单的页面,视图到底为何会自动更新数据呢?

我先把最粗糙的源码放出来,大家先看看,有看不懂得地方再议。

老规矩,初始化页面

<!DOCTYPE html>
<html> <head>
<meta charset="utf-8">
<script src="./test_01.js" charset="utf-8"></script>
<title>手写脏检查</title>
<style type="text/css">
button {
height: 60px;
width: 100px;
} p {
margin-left: 20px;
}
</style>
</head> <body>
<div>
<button type="button" ng-click="increase">增加</button>
<button type="button" ng-click="decrease">减少</button> 数量:
<span ng-bind="data"></span>
</div>
<br>
<!-- 合计 = <span ng-bind="sum"></span> -->
</body> </html>

下面是JS源码:1.0版本

window.onload = function() {
'use strict';
var scope = { // 相当于$scope
"increase": function() {
this.data++;
},
"decrease": function() {
this.data--;
},
data: 0
} function bind() {
var list = document.querySelectorAll('[ng-click]');
for (var i = 0, l = list.length; i < l; i++) {
list[i].onclick = (function(index) {
return function() {
var func = this.getAttribute('ng-click');
scope[func](scope);
apply();
}
})(i)
}
} function apply() {
var list = document.querySelectorAll('[ng-bind]');
for (var i = 0, l = list.length; i < l; i++) {
var bindData = list[i].getAttribute('ng-bind');
list[i].innerHTML = scope[bindData];
}
} bind();
apply();
}

没错,我只是我偷懒实现的……其中还有很多bug,虽然实现了页面效果,但是仍然有很多缺陷,比如方法我直接就定义在了scope里面,可以说,这一套代码是我为了实现双向绑定而实现的双向绑定。

回到主题,这段代码中我有用到脏检查吗?

完全没有。

这段代码的意思就是bind()方法绑定click事件,apply()方法显示到了页面上去而已。

OK,抛开这段代码,先看2.0版本的代码

window.onload = function() {
function getNewValue(scope) {
return scope[this.name];
} function $scope() {
// AngularJS里,$$表示其为内部私有成员
this.$$watchList = [];
} // 脏检查监测变化的一个方法
$scope.prototype.$watch = function(name, getNewValue, listener) {
var watch = {
// 标明watch对象
name: name,
// 获取watch监测对象的值
getNewValue: getNewValue,
// 监听器,值发生改变时的操作
listener: listener
}; this.$$watchList.push(watch);
} $scope.prototype.$digest = function() {
var list = this.$$watchList;
for (var i = 0; i < list.length; i++) {
list[i].listener();
}
} // 下面是实例化内容 var scope = new $scope;
scope.$watch('first', function() {
console.log("I have got newValue");
}, function() {
console.log("I am the listener");
}) scope.$watch('second', function() {
console.log("I have got newValue =====2");
}, function() {
console.log("I am the listener =====2");
}) scope.$digest();
}

这个版本中,没有数据双向绑定的影子,这只是一个脏检查的原理。

引入2.0版本,看看在控制台发生了什么。

控制台打印出了 I am the listenerI am the listener =====2 这就说明,我们的观察成功了。

不过,仅此而已。

我们光打印出来有用吗?

明显是没有作用的。

接下来要来改写这一段的方法。

首先,我们要使 listener 起到观察的作用。

先将 listener() 方法输出内容改变,仿照 AngularJS$watch 方法,只传两个参数:

scope.$watch('first', function(newValue, oldValue) {
console.log("new: " + newValue + "=========" + "old: " + oldValue);
}) scope.$watch('second', function(newValue, oldValue) {
console.log("new2: " + newValue + "=========" + "old2: " + oldValue);
})

再将 $digest 方法进行修改

$scope.prototype.$digest = function() {
var list = this.$$watchList;
for (var i = 0; i < list.length; i++) {
// 获取watch对应的对象
var watch = list[i];
// 获取new和old的值
var newValue = watch.getNewValue(this);
var oldValue = watch.last; // 进行脏检查
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
watch.last = newValue;
} // list[i].listener();
}
}

最后将 getNewValue 方法绑定到 $scope 的原型上,修改 watch 方法所传的参数:

$scope.prototype.getNewValue = function(scope) {
return scope[this.name];
} // 脏检查监测变化的一个方法
$scope.prototype.$watch = function(name, listener) {
var watch = {
// 标明watch对象
name: name,
// 获取watch监测对象的值
getNewValue: this.getNewValue,
// 监听器,值发生改变时的操作
listener: listener
}; this.$$watchList.push(watch);
}

最后定义这两个对象:

    scope.first = 1;
scope.second = 2;

这个时候再运行一遍代码,会发现控制台输出了 new: 1=========old: undefinednew2: 2=========old2: undefined

OK,代码到这一步,我们实现了watch观察到了新值和老值。

这段代码的 watch 我是手动触发的,那个该如何进行自动触发呢?

    $scope.prototype.$digest = function() {
var list = this.$$watchList;
// 判断是否脏了
var dirty = true;
while (dirty) {
dirty = false;
for (var i = 0; i < list.length; i++) {
// 获取watch对应的对象
var watch = list[i];
// 获取new和old的值
var newValue = watch.getNewValue(this);
var oldValue = watch.last; // 关键来了,进行脏检查
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
watch.last = newValue;
dirty = true;
} // list[i].listener();
}
} }

那我问一个问题,为什么我要写两个 watch 对象?

很简单,如果我在 first 中改变了 second 的值,在 second 中改变了 first 的值,这个时候,会出现无限循环调用。

那么,AngularJS 是如何避免的呢?

    $scope.prototype.$digest = function() {
var list = this.$$watchList;
// 判断是否脏了
var dirty = true;
// 执行次数限制
var checkTime = 0;
while (dirty) {
dirty = false;
for (var i = 0; i < list.length; i++) {
// 获取watch对应的对象
var watch = list[i];
// 获取new和old的值
var newValue = watch.getNewValue(this);
var oldValue = watch.last; // 关键来了,进行脏检查
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
watch.last = newValue;
dirty = true;
} // list[i].listener();
}
checkTime++;
if (checkTime > 10 && checkTime) {
throw new Error("次数过多!")
}
} }
scope.$watch('first', function(newValue, oldValue) {
scope.second++;
console.log("new: " + newValue + "=========" + "old: " + oldValue);
}) scope.$watch('second', function(newValue, oldValue) {
scope.first++;
console.log("new2: " + newValue + "=========" + "old2: " + oldValue);
})

这个时候我们查看控制台,发现循环了10次之后,抛出了异常。

这个时候,脏检查机制已经实现,是时候将这个与第一段代码进行合并了,3.0 代码横空出世。

window.onload = function() {
'use strict';
function Scope() {
this.$$watchList = [];
} Scope.prototype.getNewValue = function() {
return $scope[this.name];
} Scope.prototype.$watch = function(name, listener) {
var watch = {
name: name,
getNewValue: this.getNewValue,
listener: listener || function() {}
}; this.$$watchList.push(watch);
} Scope.prototype.$digest = function() {
var dirty = true;
var checkTimes = 0;
while (dirty) {
dirty = this.$$digestOnce();
checkTimes++;
if (checkTimes > 10 && dirty) {
throw new Error("循环过多");
}
}
} Scope.prototype.$$digestOnce = function() {
var dirty;
var list = this.$$watchList;
for (var i = 0; i < list.length; i++) {
var watch = list[i];
var newValue = watch.getNewValue();
var oldValue = watch.last;
if (newValue !== oldValue) {
watch.listener(newValue, oldValue);
dirty = true;
} else {
dirty = false;
} watch.last = newValue;
}
return dirty;
} var $scope = new Scope();
$scope.sum = 0;
$scope.data = 0;
$scope.increase = function() {
this.data++;
};
$scope.decrease = function() {
this.data--;
};
$scope.equal = function() { };
$scope.faciend = 3
$scope.$watch('data', function(newValue, oldValue) {
$scope.sum = newValue * $scope.faciend;
console.log("new: " + newValue + "=========" + "old: " + oldValue);
}); function bind() {
var list = document.querySelectorAll('[ng-click]');
for (var i = 0, l = list.length; i < l; i++) {
list[i].onclick = (function(index) {
return function() {
var func = this.getAttribute('ng-click');
$scope[func]($scope);
$scope.$digest();
apply();
}
})(i)
}
} function apply() {
var list = document.querySelectorAll('[ng-bind]');
for (var i = 0, l = list.length; i < l; i++) {
var bindData = list[i].getAttribute('ng-bind');
list[i].innerHTML = $scope[bindData];
}
} bind();
$scope.$digest();
apply();
}

页面上将 合计 放开,看看会有什么变化。

这就是 AngularJS脏检查机制的实现,当然,Angular 里面肯定比我要复杂的多,但是肯定是基于这个进行功能的增加,比如 $watch 传的第三个参数。

技术发展

现在 Angular 已经发展到了 Angular5,但是谷歌仍然在维护 AngularJS,而且,并不一定框架越新技术就一定越先进,要看具体的项目是否适合。

比如说目前最火的 React ,它采用的是虚拟DOM,简单来说就是将页面上的DOMJS里面的虚拟DOM进行对比,然后将不一样的地方渲染到页面上去,这个思想就是AngularJS的脏检查机制,只不过AngularJS是检查的数据,React是检查的DOM而已。

手写AngularJS脏检查机制的更多相关文章

  1. AngularJS 脏检查机制

    脏检查是AngularJS的核心机制之一,它是实现双向绑定.MVVM模式的重要基础. 一.digest循环 AngularJS将双向绑定转换为一个堆watch表达式,然后递归检查这些watch表达式的 ...

  2. angularjs脏检查

    angularjs实现了双向绑定,与vue的defineProperty不同,它的原理在于它的脏检查机制,以下做了一些总结: angular.js介绍 AngularJs是mvvm框架,它的组件是vm ...

  3. AngularJS 脏检查深入分析

    写在开头 关于Angular脏检查,之前没有仔细学习,只是旁听道说,Angular 会定时的进行周期性数据检查,将前台和后台数据进行比较,所以非常损耗性能. 这是大错而特错的.我甚至在新浪前端面试的时 ...

  4. angular何时触发脏检查机制

    ng只有在指定事件触发后,才进入$digest cycle: DOM事件,譬如用户输入文本,点击按钮等.(ng-click) XHR响应事件 ($http) 浏览器Location变更事件 ($loc ...

  5. angular2 脏检查机制

    https://www.waitig.com/angular2-%E8%84%8F%E6%A3%80%E6%9F%A5%E8%BF%87%E7%A8%8B.html https://zhuanlan. ...

  6. AngularJs 脏值检查及其相关

    今天突然就想写写$digest和$apply,这些都是脏值检查的主体内容. 先以普通js来做一个简单的监控例子吧: var div = ducoment.getElementById("my ...

  7. 4.redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现?

    作者:中华石杉 面试题 redis 的过期策略都有哪些?内存淘汰机制都有哪些?手写一下 LRU 代码实现? 面试官心理分析 如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当 ...

  8. redis的过期策略都有哪些?内存淘汰机制都有哪些?手写一下LRU代码实现?

    redis的过期策略都有哪些? 设置过期时间: set key 的时候,使用expire time,就是过期时间.指定这个key比如说只能存活一个小时?10分钟?指定缓存到期就会失效. redis的过 ...

  9. Android Binder机制详解:手写IPC通信

    想要掌握一样东西,最好的方式就是阅读理解它的源码.想要掌握Android Binder,最好的方式就是写一个AIDL文件,然后查看其生成的代码.本文的思路也是来自于此. 简介 Binder是Andro ...

随机推荐

  1. 微软工具Sysinternals Suite

    工具:Sysinternals Suite 一个可以看进程的工具.

  2. 【Luogu】U16325小奇的花园(树链剖分)

    题目链接 学了学动态开点的树链剖分,其实跟动态开点的线段树差不多啦 查询的时候别ssbb地动态开点,如果没这个点果断返回0就行 只要注意花的种类能到intmax就行qwq!!!! #include&l ...

  3. fish shell安装和配置

    sudo apt-get install fish whereis fish chsh -s /usr/bin/fish 重启:

  4. P2258 子矩阵 (搜索,动态规划)

    题目链接 Solution 搜索+DP. 刚好把搜索卡死的数据范围... 然后应该可以很容易想到枚举行的情况,然后分列去DP. 行的情况直接全排列即可,复杂度最高 \(O(C_{16}^{8})\). ...

  5. input标签不能设置height

    首先input是内联标签(inline) inline元素设置width.height属性无效 可以通过设置display:inline-block ,则内联标签可以设置width和height,但是 ...

  6. 最新版浏览器报错net::ERR_INSECURE_RESPONSE原因

    访问的网址与接口请求的域名不一致,新版的chrome浏览器出于安全的考虑会将请求进行拦截,并报错net::ERR_INSECURE_RESPONSE

  7. bzoj 3625小朋友和二叉树 多项式求逆+多项式开根 好题

    题目大意 给定n种权值 给定m \(F_i表示权值和为i的二叉树个数\) 求\(F_1,F_2...F_m\) 分析 安利博客 \(F_d=F_L*F_R*C_{mid},L+mid+R=d\) \( ...

  8. 关于PHP xss 和 SQL 注入的问题

    漏洞无非这么几类,XSS.sql注入.命令执行.上传漏洞.本地包含.远程包含.权限绕过.信息泄露.cookie伪造.CSRF(跨站请求)等.这些漏洞不仅仅是针对PHP语言的,PHP如何有效防止这些漏洞 ...

  9. <编程精粹:编写高质量C语言代码> 读书笔记

    0.规则<The Elements of Programming Style><The Elements of Style> 1.假想的编译程序(1)使用编译器提供的所有的可选 ...

  10. GridView主键列不让编辑时应该修改属性DataKeyNames

    原文发布时间为:2008-08-02 -- 来源于本人的百度文章 [由搬家工具导入] 为了防止GridView主键被编辑,应该在GridView属性DataKeyNames里面写上主键