手写AngularJS脏检查机制
2017-10-31 22:52
246 查看
什么是脏检查
View -> Model
浏览器提供有
User Event触发事件的
API,例如,
click,
change等
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 listener和
I 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: undefined和
new2: 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,简单来说就是将页面上的
DOM和
JS里面的
虚拟DOM进行对比,然后将不一样的地方渲染到页面上去,这个思想就是
AngularJS的脏检查机制,只不过
AngularJS是检查的数据,
React是检查的
DOM而已。
相关文章推荐
- AngularJS 脏检查机制
- 详解AngularJS脏检查机制及$timeout的妙用
- 详解AngularJS脏检查机制及$timeout的妙用
- angularJS内部的脏检查机制与$apply()、$watch()、$digest
- AngularJS的核心机制--脏检查
- AngularJS之scope中的Dirty Checking(脏数据检查)
- tensorflow学习(4):保存模型Saver.save()的参数命名机制以及restore并创建手写字体识别引擎
- 解析 Linux 内核可装载模块的版本检查机制
- 类型检查 与Java 多态相结合及反射机制相结合
- 浅谈AngularJs 双向绑定原理(数据绑定机制)
- [深入分析BREW机制]:动手写BREW扩展接口
- 走进AngularJs(八) ng的路由机制
- XML 语法检查机制
- 走进AngularJs(八) ng的路由机制
- 解析 Linux 内核可装载模块的版本检查机制
- AngularJs事件机制
- 转载 《AngularJS》5个实例详解Directive(指令)机制
- AngularJS数据绑定中数据监控的机制说明
- AngularJs中模块的依赖注入,ng-model、ng-bind和{{}}的区别,路由机制