avalon之所以能在页面处理1W个绑定(angular对应的数字是2000),出于两个重要设计——基于事件驱动的双向绑定链及智能CG回收机制。

avalon的双向绑定链是通过Object.definePropertiesVBScript,将要操作VM属性变成一种访问器属性。访问器属性是一种特殊的属性,需要我们为它指定setter、getter方法(当然,这也是框架内部生成的,只有计算属性可以做一些干预),当用户对此属性进行赋值操作时,就会调用setter方法,对它进行读取时,就会进行getter方法。我们通过hack进这两个方法,做各种各样的事,如依赖收集及事件广播、触发$watch回调,其中最重要一点,就是将关联在它上面的订阅数组的对象逐个触发,从而更新视图,这也是双向绑定链的原理。比如说

vm.me = 111

<div ms-text="me"></div>

me就会有一个订阅数组,里面放着一个对象,里面包含如此操作这个div的信息,如果有两个绑定属性的值存在me这个变量,这数组就有两个对象。触发是用户修改vm.me 时立即发生,不需要像angular那样调用$apply或$digest方法,也不像angular那样将所有绑定对象都检测一次。angular之所以会卡死,因为页面一旦绑定对象多,这检测时间也恐怖了,并且这检测可能是深遍历对象的属性进行比较的。而avalon每次只会检测其一个属性上对应的小数组,因此检测压力会相对少许多。

由于绑定属性会转换绑对象,并且绑定对象包含要操作的元素节点或文本特点这种惯例的存在,就会引发第二个问题,如果回收这些绑定对象呢?angular虽然为$scope对象添加了一个$destroy,但没有针对绑定对象有更精细的操作。avalon在1.36之前在notifySubscribers进行绑定对象的element进行是否在DOM树的检测,不在就将绑定对象的所有属性都置为null,并从当前订阅数组移除。

avalon1.36/1.4引入全新的CG回收机制,页面上的{{}}插值表达式ms-*属性,经过扫描后,变成一个个绑定对象,对象包含name、value、type、param、vmodels、priority、args、vmodels、evaluator、handler等属性与方法,有些绑定对象还会多出template、 group、$repeat、proxies等属性(1.36前更多,现在都大幅精简了),因此绑定对象也算一个比较大的JS对象,页面上的绑定属性越多,这些绑定对象自然也越多,占用着大用的内存。如果页面发生一些移除节点操作,涉及这些绑定属性原来所在的元素节点,那么这些绑定对象也应该销毁,我们就必须将binding.element 置为null,才会方便CG回收。之前是位于notifySubscribers方法里进行,但它处理的目标是一个很小的数组,不会检测所有绑定对象,因此总有漏网之鱼,这样积沙成塔,在移动端上就是一个很大的问题,会弄崩手机浏览器。

在1.36/1.4中,内部定义一个全局的$$subscribers数组,所有生成的绑定对象都放到里面,然后每当我让VM的属性发生变化时,就一定会经过notifySubscribers方法,这时对$$subscribers数组的对象进行检测。并且这检测也有技巧,为了减少检测频率对浏览器造成压力,每次检测至少经过333ms才会进行一次。

检测手段,是判定binding.element是否位于DOM树上,由于element可能是元素节点,文本节点或注释节点,IE的原生contains方法也有BUG,于是检测也是多种多样的。

var $$subscribers = []
function removeSubscribers() {
for (var i = $$subscribers.length, obj; obj = $$subscribers[--i]; ) {
var data = obj.data
var el = data.element
var remove = el === null ? 1 : (el.nodeType === 1 ? typeof el.sourceIndex === "number" ?
el.sourceIndex === 0 : !root.contains(el) : !avalon.contains(root, el))
if (remove) { //如果它没有在DOM树
$$subscribers.splice(i, 1)
avalon.Array.remove(obj.list, data)
// log("debug: remove " + data.type)
obj.data = obj.list = data.evaluator = data.element = data.vmodels = null
}
}
}
var beginTime = new Date(), removeID
function notifySubscribers(accessor) { //通知依赖于这个访问器的订阅者更新自身
var currentTime = new Date()
clearTimeout(removeID)
if (currentTime - beginTime > 333) {
removeSubscribers()
beginTime = currentTime
} else {
removeID = setTimeout(removeSubscribers, 333)
}
var list = accessor[subscribers]
if (list && list.length) {
var args = aslice.call(arguments, 1)
for (var i = list.length, fn; fn = list[--i]; ) {
var el = fn.element
if (typeof fn === "function") {
fn.apply(0, args) //强制重新计算自身
} else if (fn.$repeat) {
fn.handler.apply(fn, args) //处理监控数组的方法
} else if (fn.element) {
var fun = fn.evaluator || noop
fn.handler(fun.apply(0, fn.args || []), el, fn)
}
}
}
}

当然这是两个要点,为了提高性能,avalon也像一些大型应用程序那样,善于利用缓存机制。之前是使用FIFO缓存算法,1.4后改为LRU。

<!DOCTYPE html>
<html>
<head>
<title>监控函数</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="avalon.js"></script>
<style> .odd{
background: green;
}
</style>
<script type="text/javascript">
var model = avalon.define({
$id: "test",
array: [1, 2, 3, 4, 5],
more: 10,
isOdd: function(el) {
return (el + model.more) % 2 === 1
},
add: function() {
model.array.push(1)
},
remove: function() {
model.array.pop()
},
change: function(){
model.more = 11
} }) </script>
</head>
<body ms-controller="test" >
<ul>
<li ms-repeat="array" ms-class="odd:isOdd(el)">{{el}}</li>
</ul>
<p><button ms-click="add">add</button><button ms-click="remove">remove</button><button ms-click="change">change</button></p>
</body>
</html>

最新文章

  1. # TypeScript 中如何确保 this 的正确性
  2. Jena Fuseki 102
  3. Google Map API key 获取方法
  4. Servlet的response输出到页面时乱码的解决方法
  5. [xUnit]尝试单元测试
  6. java中抽象类是否可以继承实体类?
  7. Spring Boot实现文件下载功能
  8. SQL年月日格式化
  9. 小学生都看得懂的C语言入门(4): 数组与函数
  10. 暂时刷完leetcode的一点小体会
  11. gitlab4.0备份还原
  12. C++进阶--静态多态
  13. OpenGL中移动单位中的‘单位’指什么
  14. unidbgrid显示列的合计值
  15. Sql递归关联情况,With作为开头条件。
  16. 升级MAC OX上的Python到3.4
  17. docker 概念初识
  18. 【WPF】设置TextBox内容为空时的提示文字
  19. IntelliJ IDEA 2017版开发SpringBoot之fastJsonHttpMessageConvert使用
  20. 软件工程第二周PSP

热门文章

  1. android调节音量——AudioManager的应用
  2. 关闭IE 对剪切板访问的提示
  3. FPGA该如何应对ASIC的大爆发?
  4. php让页面记住表单提交后的信息方法
  5. html + php 框架
  6. jmeter监控服务器的方法
  7. COMMON INTERVIEW QUESTIONS
  8. centos 安装卸载软件命令 &amp; yum安装LAMP环境
  9. move操作
  10. Spark分析之BlockManager