Open chiyan-lin opened 3 years ago
<div> <button type="button" ng-click="increase">增加</button> <button type="button" ng-click="decrease">减少</button> 数量: <span ng-bind="data"></span> </div> // angular 脏检查基本实现 window.onload = function () { function Scope () { this.$$watchList = []; } // 获取最新的 scope 的值 Scope.prototype.getNewValue = function () { return $scope[this.name]; } // 设置一个订阅方式 Scope.prototype.$watch = function (name, listener) { var watch = { name: name, getNewValue: this.getNewValue, listener: listener || function () { } }; // 将需要被观察的对象推进入 wl 中 this.$$watchList.push(watch); } // 触发脏检查的方法 Scope.prototype.$digest = function () { var dirty = true; var checkTimes = 0; while (dirty) { dirty = this.$$digestOnce(); checkTimes++; // 设置一个limit防止因为 scope 的相互改变触发无限循环 if (checkTimes > 10 && dirty) { throw new Error("循环过多"); } } } // 遍历的核心是值的对比 // 每次触发 digest 会对所有订阅进行遍历【所以数据太多太大性能上就有问题】 // 在发现数据已经变化之后,会返回 true,然后会再次进行一次遍历直到确认没有改变了 // 这里如果是引用类型对比就会有问题 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; } // 初始化下这个 Scope var $scope = new Scope(); $scope.sum = 0; $scope.data = 0; $scope.increase = function () { this.data++; }; $scope.decrease = function () { this.data--; }; $scope.equal = function () { }; // 页面进行 ng-bind 的变量会被 watch $scope.$watch('data', function (newValue, oldValue) { $scope.sum = newValue * 666; console.log("new: " + newValue + "old: " + oldValue); }); // 绑定的方法 function bind () { const list = document.querySelectorAll('[ng-click]'); for (let i = 0, l = list.length; i < l; i++) { list[i].onclick = function (index) { return function () { const func = this.getAttribute('ng-click'); $scope[func](); // 在每次点击的时候就会执行一次脏检查,再进行一次页面更新 $scope.$digest(); apply(); } } } } // 渲染函数 function apply () { const list = document.querySelectorAll('[ng-bind]'); for (var i = 0, l = list.length; i < l; i++) { const bindData = list[i].getAttribute('ng-bind'); list[i].innerHTML = $scope[bindData]; } } bind(); // 手动触发一次脏检测 // $scope.$digest(); // apply(); }
直接上代码,基本上注释已经把代码的基本功能解释清楚了,
想要实现双向绑定,就在输入框绑定一个 input ,然后每次执行完执行一次脏检查处理最新值
React ,它采用的是虚拟DOM,简单来说就是将页面上的DOM和JS里面的虚拟DOM进行对比,然后将不一样的地方渲染到页面上去。
其实这个思想就是AngularJS的脏检查机制,只不过AngularJS是检查的数据,React是检查的虚拟DOM而已。
vue 是使用 Object.defineProperty 来劫持对象属性的 getter/setter ,从而进行视图更新等功能。
那么这两种检查数据是否变化的方式哪种比较好呢
还是要看场景~~~
对于数据变更比较频繁的场景,比如
function Ctrl($scope){ var list = []; $scope.checkedItemsNumber = 0; for(var i = 0;i<1000;i++){ list.push(false); } $scope.toggleChecked = function(flag){ for(var i = 0,l= list.length;i++){ list[i] = flag; $scope.checkedItemsNumber++; } } }
脏检查是数据完全改变完成再进行 diff 的,
但是使用 getter/setter 会在每次数据都变化的时候触发
AngularJS双向绑定之脏检查机制
直接上代码,基本上注释已经把代码的基本功能解释清楚了,
想要实现双向绑定,就在输入框绑定一个 input ,然后每次执行完执行一次脏检查处理最新值
其他框架的比较
React ,它采用的是虚拟DOM,简单来说就是将页面上的DOM和JS里面的虚拟DOM进行对比,然后将不一样的地方渲染到页面上去。
其实这个思想就是AngularJS的脏检查机制,只不过AngularJS是检查的数据,React是检查的虚拟DOM而已。
vue 是使用 Object.defineProperty 来劫持对象属性的 getter/setter ,从而进行视图更新等功能。
那么这两种检查数据是否变化的方式哪种比较好呢
还是要看场景~~~
对于数据变更比较频繁的场景,比如
脏检查是数据完全改变完成再进行 diff 的,
但是使用 getter/setter 会在每次数据都变化的时候触发
AngularJS双向绑定之脏检查机制