knockout的监控数组实现
knockout应该是博客园群体中使用最广的MVVM框架,但鲜有介绍其监控数组的实现。最近试图升级avalon的监控数组,决定好好研究它一番,看有没有可借鉴之处。
ko.observableArray = function(initialValues) {
initialValues = initialValues || []; if (typeof initialValues != 'object' || !('length' in initialValues))
throw new Error("The argument passed when initializing an observable array must be an array, or null, or undefined."); var result = ko.observable(initialValues);
ko.utils.extend(result, ko.observableArray['fn']);
return result.extend({'trackArrayChanges': true});
};
这是knockout监控数组的工厂方法,不需要使用new关键字,直接转换一个普通数组为一个监控数组。你也可以什么也不会,得到一个空的监控数组。
var myObservableArray = ko.observableArray(); // Initially an empty array
myObservableArray.push('Some value'); // Adds the value and notifies obs // This observable array initially contains three objects
var anotherObservableArray = ko.observableArray([
{ name: "Bungle", type: "Bear" },
{ name: "George", type: "Hippo" },
{ name: "Zippy", type: "Unknown" }
]);
console.log(typeof anotherObservableArray)//function
虽说是监控数组,但它的类型其实是一个函数。这正是knockout令人不爽的地方,将原本是字符串,数字,布尔,数组等东西都转换为函数才行使用。
这里有一个ko.utils.extend方法,比不上jQuery的同名方法,只是一个浅拷贝,将一个对象的属性循环复制到另一个之上。
extend: function(target, source) {
if (source) {
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
return target;
},
result 是要返回的函数,它会被挂上许多方法与属性。首先是 ko.observableArray['fn']扩展包,第二个扩展其实可以简化为
result.trackArrayChanges = true
我们来看一下 ko.observableArray['fn']扩展包,其中最难的是pop,push,shift等方法的实现
ko.observableArray['fn'] = {
'remove': function(valueOrPredicate) {//值可以是原始数组或一个监控函数
var underlyingArray = this.peek();//得到原始数组
var removedValues = [];
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function(value) {
return value === valueOrPredicate;
};//确保转换为一个函数
for (var i = 0; i = 0;
});
},
'destroy': function(valueOrPredicate) {//remove方法的优化版,不立即移除元素,只是标记一下
var underlyingArray = this.peek();
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function(value) {
return value === valueOrPredicate;
};
this.valueWillMutate();
for (var i = underlyingArray.length - 1; i >= 0; i--) {
var value = underlyingArray[i];
if (predicate(value))
underlyingArray[i]["_destroy"] = true;
}
this.valueHasMutated();
},
'destroyAll': function(arrayOfValues) {//removeAll方法的优化版,不立即移除元素,只是标记一下
if (arrayOfValues === undefined)//不传就全部标记为destroy
return this['destroy'](function() {
return true
}); // If you passed an arg, we interpret it as an array of entries to destroy
if (!arrayOfValues)
return [];
return this['destroy'](function(value) {
return ko.utils.arrayIndexOf(arrayOfValues, value) >= 0;
});
},
'indexOf': function(item) {//返回索引值
var underlyingArray = this();
return ko.utils.arrayIndexOf(underlyingArray, item);
},
'replace': function(oldItem, newItem) {//替换某一位置的元素
var index = this['indexOf'](oldItem);
if (index >= 0) {
this.valueWillMutate();
this.peek()[index] = newItem;
this.valueHasMutated();
}
}
}; //添加一系列与原生数组同名的方法
ko.utils.arrayForEach(["pop", "push", "reverse", "shift", "sort", "splice", "unshift"], function(methodName) {
ko.observableArray['fn'][methodName] = function() {
var underlyingArray = this.peek();
this.valueWillMutate();
this.cacheDiffForKnownOperation(underlyingArray, methodName, arguments);
var methodCallResult = underlyingArray[methodName].apply(underlyingArray, arguments);
this.valueHasMutated();
return methodCallResult;
};
}); //返回一个真正的数组
ko.utils.arrayForEach(["slice"], function(methodName) {
ko.observableArray['fn'][methodName] = function() {
var underlyingArray = this();
return underlyingArray[methodName].apply(underlyingArray, arguments);
};
});
cacheDiffForKnownOperation 会记录如何对元素进行操作
target.cacheDiffForKnownOperation = function(rawArray, operationName, args) {
// Only run if we're currently tracking changes for this observable array
// and there aren't any pending deferred notifications.
if (!trackingChanges || pendingNotifications) {
return;
}
var diff = [],
arrayLength = rawArray.length,
argsLength = args.length,
offset = 0; function pushDiff(status, value, index) {
return diff[diff.length] = {'status': status, 'value': value, 'index': index};
}
switch (operationName) {
case 'push':
offset = arrayLength;
case 'unshift':
for (var index = 0; index 但这里没有sort, reverse方法的处理,并且它是如何操作DOM呢?由于它很早就转换为监控函数,但用户调用这些方法时,它就会在内部调用一个叫getChanges的方法function getChanges(previousContents, currentContents) {
// We try to re-use cached diffs.
// The scenarios where pendingNotifications > 1 are when using rate-limiting or the Deferred Updates
// plugin, which without this check would not be compatible with arrayChange notifications. Normally,
// notifications are issued immediately so we wouldn't be queueing up more than one.
if (!cachedDiff || pendingNotifications > 1) {
cachedDiff = ko.utils.compareArrays(previousContents, currentContents, {'sparse': true});
} return cachedDiff;
}里面有一个compareArrays方法,会计算出如何用最少的步骤实现DOM的改动,从而减少reflow。
ko.utils.compareArrays = (function() {
var statusNotInOld = 'added', statusNotInNew = 'deleted'; // Simple calculation based on Levenshtein distance.
function compareArrays(oldArray, newArray, options) {
// For backward compatibility, if the third arg is actually a bool, interpret
// it as the old parameter 'dontLimitMoves'. Newer code should use { dontLimitMoves: true }.
options = (typeof options === 'boolean') ? {'dontLimitMoves': options} : (options || {});
oldArray = oldArray || [];
newArray = newArray || []; if (oldArray.length 最后会跑到setDomNodeChildrenFromArrayMapping 里面执行相关的操作for (var i = 0, editScriptItem, movedIndex; editScriptItem = editScript[i]; i++) {
movedIndex = editScriptItem['moved'];
switch (editScriptItem['status']) {
case "deleted":
if (movedIndex === undefined) {
mapData = lastMappingResult[lastMappingResultIndex]; // Stop tracking changes to the mapping for these nodes
if (mapData.dependentObservable)
mapData.dependentObservable.dispose(); // Queue these nodes for later removal
nodesToDelete.push.apply(nodesToDelete, ko.utils.fixUpContinuousNodeArray(mapData.mappedNodes, domNode));
if (options['beforeRemove']) {
itemsForBeforeRemoveCallbacks[i] = mapData;
itemsToProcess.push(mapData);
}
}
lastMappingResultIndex++;
break; case "retained":
itemMovedOrRetained(i, lastMappingResultIndex++);
break; case "added":
if (movedIndex !== undefined) {
itemMovedOrRetained(i, movedIndex);
} else {
mapData = {arrayEntry: editScriptItem['value'], indexObservable: ko.observable(newMappingResultIndex++)};
newMappingResult.push(mapData);
itemsToProcess.push(mapData);
if (!isFirstExecution)
itemsForAfterAddCallbacks[i] = mapData;
}
break;
}
}
//下面是各种回调操作整个实现比avalon复杂得不是一点半点啊,这是太迷信算法的下场。其实像shift, unshift, pop, push, splice等方法,我们一开始就能确定如何增删,不用跑到compareArrays 里面,最麻烦的sort, reverse方法,也可以通过将父节点移出DOM树,排好再插回去,就能避免reflow了。
knockout的监控数组实现的更多相关文章
- knockout 监控数组的缺点
knockout的监控数组没有想象中的强大,只能监控数组元素的位置变化,或个数的增减,然后对视图对应区域进行同步. <!DOCTYPE html> <html> <hea ...
- Knockout学习之监控数组
监控数组 单个监控属性.组合属性虽然可以解决大部分的问题,但是还有很多是他们无法做到的,比如在一组数据中进行移除添加,所以这节我们将要学习监控数组. 由于监控属性是由ko的observable构造,那 ...
- 监控数组与foreach绑定-Knockout.js
html: <h2>Your seat reservations</h2> <table> <thead> <tr> ...
- Knockout v3.4.0 中文版教程-4-通过监控数组工作
2.通过监控数组工作 1. 监控数组 如果你想检测或者响应一个对象的改变,你用observables.如果你想检测和响应一个集合的改变,使用observableArray.这个在很多情况下都非常有用, ...
- knockout Observable Array(监控数组)
Observable Array(监控数组)的作用 列表操作是经常会遇到的一个场景,使用监控数组,你可以: 保存列表对象,并且使用Ko提供的丰富的API操作列表元素(支持内建js Array的方法,以 ...
- Knockout 监控数组对象属性
代码: function Product(ProductID,ProductName,ProductNum,Result,Price) { this.ProductID = ko.observable ...
- Knockout.js(二):监控数组属性(Observables Arrays)
如果想发现并响应一个对象的变化,就应该使用监控属性(observables),如果想发现并响应一个集合的变化,就需要使用监控属性数组(observableArray).在很多情况下,它都非常有用,比如 ...
- knockout更新列表中的某条数据,knockout.js绑定数组时更新其中一条数据
knockout是一款前端实现MVVM的JS框架,仅knockout.js一个47kb的文件,相当实用,做前端无刷新页面,快速实现JS与HTML数据交互. knockout目前最新版:knockout ...
- Knockout开发中文API系列4–监控属性数组
PS:这个翻译系列好久都没有更新了,实在是不应该,一方面是由于时间不多,另一方面也由于自身惰性太大,从今天起接着更新,会在最近的一月内把这个系列中文API文档翻译完整. 如果你想侦测并响应一个对象的变 ...
随机推荐
- BZOJ1878 SDOI2009 HH的项链 【莫队】
BZOJ1878 SDOI2009 HH的项链 Description HH有一串由各种漂亮的贝壳组成的项链.HH相信不同的贝壳会带来好运,所以每次散步 完后,他都会随意取出一段贝壳,思考它们所表达的 ...
- CODEVS3013 单词背诵 【Hash】【MAP】
CODEVS3013 单词背诵 题目描述 Description 灵梦有n个单词想要背,但她想通过一篇文章中的一段来记住这些单词. 文章由m个单词构成,她想在文章中找出连续的一段,其中包含最多的她想要 ...
- POJ2728 Desert King 【最优比率生成树】
POJ2728 Desert King Description David the Great has just become the king of a desert country. To win ...
- BZOJ5297 CQOI2018 社交网络 【矩阵树定理Matrix-Tree】
BZOJ5297 CQOI2018 社交网络 Description 当今社会,在社交网络上看朋友的消息已经成为许多人生活的一部分.通常,一个用户在社交网络上发布一条消息(例如微博.状态.Tweet等 ...
- [CF895E]Eyes Closed
luogu description 一个序列\(a_i\),支持一下两种操作. \(1\ \ l_1\ \ r_1\ \ l_2\ \ r_2\): 随机交换区间\([l_1,r_1]\)和\([l_ ...
- 操作系统CPU上下文切换
关于CPU,有3个重要的概念:上下文切换(context switchs),运行队列(Run queue)和使用率(utilization). 上下文切换: 目前流行的CPU在同一时间内只能运行一个线 ...
- STM32学习笔记之__attribute__ ((at())绝对定位分析
STM32也会遇到这样的绝对定位的问题如下: uint8_t UART_RX_BUF[1024] __attribute__ ((at(0X20001000))); //就是将串口接收的数 ...
- ACM-世界岛旅行
[问题描述] 某旅游公司组团去迪拜世界岛旅游.世界岛由n个岛屿组成,岛屿序号为1-n,这些岛屿都直接或间接相连.岛屿之间用桥梁连接.现从1号岛屿开始游览,并约定按如下方式游览: 1) 每游览完一个岛屿 ...
- CSV 参数化
配置CSV Data Set Config 图 3 配置CSV Data Set Config Filename: 指保存信息的文件目录,可以相对或者绝对 ...
- Zabbix 添加 WEB 监控
添加 WEB Monitorings Web Monitoring是用来监控web程序的,可以监控到web程序的下载速度.返回码及响应时间,还支持把一组连续的web动作作为一个整体来监控. 下面我们以 ...