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文档翻译完整. 如果你想侦测并响应一个对象的变 ...
随机推荐
- BZOJ3930 [CQOI2015]选数【莫比乌斯反演】
Description 我们知道,从区间[L,H](L和H为整数)中选取N个整数,总共有(H-L+1)^N种方案.小z很好奇这样选出的数的最大公约数的规律,他决定对每种方案选出的N个整数都求一次最大公 ...
- centos6.5 ifconfig没有ipv4地址
进入/etc/sysconfig/network-scripts/目录中,可以看到ifcfg-eth0文件, vi ifcfg-eth0编辑文件, 将“ONBOOT=no” 选项改成“ONBOOT=y ...
- k8s helm 私服chartmuseum minio s3 存储配置
1. 安装minio 使用docker 安装 参考项目 https://github.com/rongfengliang/mino-thumbor-openresty 备注: 因为是一个集成项目可能会 ...
- jQuery的deferred对象详解(转载)
jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本.(由于无法转载,复制原文 .原文链接——原作者:阮一峰) 每个版本都会引入一些新功能.今天我想介绍的,就是从jQuery 1.5. ...
- python 有关引用的一些问题
python 有关引用的一些问题 print id.__doc__ id(object) -> integer Return the identity of an object. This ...
- 什么是spark(二) RDD
其实你会发现很多概念都是基于RDD提出来的,比如分区,缓存这些操作的对象其实都是RDD:所以不要讲spark的分区,这其实很不专业,分区其实是属于RDD的概念(只有pair RDD才有分区概念) RD ...
- Web 漏洞分析与防御之 XSS(一)
原文地址:Web 漏洞分析与防御之 XSS(一) 博客地址:http://www.extlight.com 一.全称 跨站脚本攻击(Cross Site Scripting) 二.原理 通过在网站中的 ...
- 编译Lichee(FridenlyARM NanoPi-M1)碰到的问题及解决办法
1. 提示libz.so.1找不到 需要在ubuntu上安装下面两个包: sudo apt-get install lib32ncurses5 ia32-libs 2. 提示xt_hl.o没有make ...
- 基于标准库的string类实现简单的字符串替换
感觉基本功还是不扎实,虽然能做些程序但是现在看来我还是个初学者(primer),试着完成习题结果还得修修改改. 废话不多说,实现功能很简单,<C++ Primer>9.5.2节习题. // ...
- litePal用法
1.依赖:在app/build.gradle文件中的depenencies{compile 'org.litepal.android:core:1.3.2'} 2.配置litePal.xml:右击ap ...