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. java运行jar命令提示没有主清单属性和找不到主类

    推荐一个java运行jar命令提示没有主清单属性的百度经验的链接:https://jingyan.baidu.com/article/db55b60990f6084ba30a2fb8.html jav ...

  2. BZOJ2330 SCOI2011 糖果 【差分约束】

    BZOJ2330 SCOI2011 糖果 Description 幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果.但是小朋友们也有嫉妒心,总是会提出一 ...

  3. Roslyn 入门:使用 .NET Core 版本的 Roslyn 编译并执行跨平台的静态的源码

    Roslyn 是微软为 C# 设计的一套分析器,它具有很强的扩展性.以至于我们只需要编写很少量的代码便能够编译并执行我们的代码. 作为 Roslyn 入门篇文章之一,你将可以通过本文学习如何开始编写一 ...

  4. Git 更安全的强制推送,--force-with-lease

    由于 git rebase 命令的存在,强制将提交推送到远端仓库似乎也有些必要.不过都知道 git push --force 是不安全的,这让 git rebase 命令显得有些鸡肋. 本文将推荐 - ...

  5. Hexo+GitHub+Netlify一站式搭建属于自己的博客网站

    喜欢的话请关注我的个人博客我在马路边https://hhongwen.cn/,此文为博主原创,转载请标明出处. 更好的阅读体验请点击查看:Hexo+GitHub+Netlify一站式搭建属于自己的博客 ...

  6. Clairewd’s message(哈希模板+)

    个人心得:一开始就是知道用哈希,但是无从下手,很明显是对哈希不太了解和思维不太好. 先来看一下这一题涉及到的哈希吧和这题的思路吧,思路就是对所给的密文用原文和翻译后进行hash处理,那么必然存在后面那 ...

  7. 在Eclipse中导入dtd和xsd文件,使XML自动提示

    DTD 类型约束文件    1. Window->Preferences->XML->XML Catalog->User Specified Entries窗口中,选择Add ...

  8. Javascript实现简单的选项卡

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u011043843/article/details/30723933 在线演示:http://jsf ...

  9. Oracle12c之 CDB数据库中数据字典架构

    数据字典就是元数据的集合,比如创建的表,列,约束,触发器等等这些都是元数据,需要保存到数据库中.除此之外,Oracle自身的一些数据库对象,如目录,PL/SQL代码等等这些都是元数据,都需要存放在数据 ...

  10. Unit02: JDBC核心API

    Unit02: JDBC核心API db.properties 注意:如果使用连接池,可以在这个文件中增加对连接池的相关设置: 连接池参数,常用参数有: 初始连接数 最大连接数 最小连接数 每次增加的 ...