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的监控数组实现的更多相关文章

  1. knockout 监控数组的缺点

    knockout的监控数组没有想象中的强大,只能监控数组元素的位置变化,或个数的增减,然后对视图对应区域进行同步. <!DOCTYPE html> <html> <hea ...

  2. Knockout学习之监控数组

    监控数组 单个监控属性.组合属性虽然可以解决大部分的问题,但是还有很多是他们无法做到的,比如在一组数据中进行移除添加,所以这节我们将要学习监控数组. 由于监控属性是由ko的observable构造,那 ...

  3. 监控数组与foreach绑定-Knockout.js

    html: <h2>Your seat reservations</h2> <table>    <thead>  <tr>         ...

  4. Knockout v3.4.0 中文版教程-4-通过监控数组工作

    2.通过监控数组工作 1. 监控数组 如果你想检测或者响应一个对象的改变,你用observables.如果你想检测和响应一个集合的改变,使用observableArray.这个在很多情况下都非常有用, ...

  5. knockout Observable Array(监控数组)

    Observable Array(监控数组)的作用 列表操作是经常会遇到的一个场景,使用监控数组,你可以: 保存列表对象,并且使用Ko提供的丰富的API操作列表元素(支持内建js Array的方法,以 ...

  6. Knockout 监控数组对象属性

    代码: function Product(ProductID,ProductName,ProductNum,Result,Price) { this.ProductID = ko.observable ...

  7. Knockout.js(二):监控数组属性(Observables Arrays)

    如果想发现并响应一个对象的变化,就应该使用监控属性(observables),如果想发现并响应一个集合的变化,就需要使用监控属性数组(observableArray).在很多情况下,它都非常有用,比如 ...

  8. knockout更新列表中的某条数据,knockout.js绑定数组时更新其中一条数据

    knockout是一款前端实现MVVM的JS框架,仅knockout.js一个47kb的文件,相当实用,做前端无刷新页面,快速实现JS与HTML数据交互. knockout目前最新版:knockout ...

  9. Knockout开发中文API系列4–监控属性数组

    PS:这个翻译系列好久都没有更新了,实在是不应该,一方面是由于时间不多,另一方面也由于自身惰性太大,从今天起接着更新,会在最近的一月内把这个系列中文API文档翻译完整. 如果你想侦测并响应一个对象的变 ...

随机推荐

  1. BZOJ2002 Hnoi2010 Bounce 弹飞绵羊 【LCT】【分块】

    BZOJ2002 Hnoi2010 Bounce 弹飞绵羊 Description 某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏.游戏一开始, ...

  2. Byte.parseByte(String s,int radix)的解释

    1. 由 基本数据型态转换成 String String 类别中已经提供了将基本数据型态转换成 String 的 static 方法 也就是 String.valueOf() 这个参数多载的方法 有下 ...

  3. .NET/C# 使窗口永不获得焦点

    有些窗口天生就是为了辅助其它程序而使用的,典型的如“输入法窗口”.这些窗口不希望抢夺其它窗口的焦点. 有 Win32 方法来解决这样的问题,WS_EX_NOACTIVATE 便是关键. 具体来说,是给 ...

  4. 关于json格式字符串解析并用mybatis存入数据库

    园子里面找了很多关于json解析后存入数据库的方法,不是太乱,就是没有写完,我下面的主题代码多是受下面两位的启发,请按顺序查看 http://www.cnblogs.com/tian830937/p/ ...

  5. ‘close’ was not declared in this scope(转)

    ‘close’ was not declared in this scope 没有包含头文件 unistd.h 造成的. 加上' #include <unistd.h>

  6. kotlin与fastjson的异常

    出现这个原因是因为kotlin的非空特性. 如果一个类中声明了一个字段(kotlin的特性,该字段默认是非空的), 使用fastjson进行转化的时候,如果json数据中没有该字段的数据,则会出现转换 ...

  7. 3、MR开发入门

    1.预先准备2个文件file1.txt和file2.txt.文件内容为网页上摘下,不具有代表性,只为举例. file1.txt内容为: With this setup, whenever you ch ...

  8. proxool 连接池

    今天配置proxool 连接池,发现可配置属性非常多,以前也只是用,没总结过,今天查了下网上的资料,总结一下 方便你我.其实网上很多英文资料都很全,网上很多人就是考翻译老外的文章赚些流量,其实也没啥意 ...

  9. Unit01: Ajax介绍

    Unit01: Ajax 1. ajax是什么? (asynchronous javascript and xml) ajax是一种用来改善用户体验的技术,本质是利用浏览器提供的一个 特殊对象(XML ...

  10. JVM内存管理之GC算法精解(复制算法与标记/整理算法)

    本次LZ和各位分享GC最后两种算法,复制算法以及标记/整理算法.上一章在讲解标记/清除算法时已经提到过,这两种算法都是在此基础上演化而来的,究竟这两种算法优化了之前标记/清除算法的哪些问题呢? 复制算 ...