JavaScript 是否会受益于字典对象?






4.89/5 (2投票s)
JavaScript 是否会受益于字典对象?
引言
在我最后一个项目中,我被指派创建了一个浏览器客户端应用程序,该应用程序需要读取数万行数据,然后对数据进行分组和汇总,以便在网格中显示以及进行图表绘制。目标技术是 HTML 5、CSS 3 和 EMCS 5(2013 年 6 月的现代浏览器)。由于不需要考虑旧版浏览器的兼容性,因此外部库仅限于 D3(没有 JQuery)。
我需要构建一个数据模型。我之前在 C# 中构建过一个,并依赖自定义字典对象来快速访问数据、分组和汇总。我多年没用过 JavaScript 了,于是开始寻找字典。我发现 JavaScript 仍然没有真正的原生字典。我找到了一些示例实现,但没有一个真正符合我的期望。于是我自己实现了一个。
正如我提到的,我多年没用过 JavaScript 了。它的进步(或者也许只是信息的网络可用性)令人印象深刻。我之前的所有工作都是基于类语言,所以基于原型语言需要一些时间来适应(我还有很长的路要走)。
这个项目,像大多数项目一样,比开始时就注定了要赶工,所以我一边学一边做,犯了很多新手错误,这在从基于类语言过渡到基于原型语言时是可以预见的。我创建的字典是可用的,但过了一段时间,我意识到可以通过让它不那么新手化来做一些改进。项目在我有时间重构字典之前就缺乏资金了。哦,我的职位也同时失去了资金(真是令人惊奇)。所以我决定用我学到的东西重新实现字典,并确定字典是否真的比数组有性能提升。
/*
* Dictionary Factory Object
* Holds common object functions. similar to V-Table
* this.New() used to create new dictionary objects
* Uses Object.defineProperties so won't work on older browsers.
* Browser Compatibility
* (https://mdn.org.cn/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties)
* Firefox (Gecko) 4.0 (2), Chrome 5, IE 9, Opera 11.60, Safari 5
*/
function Dict() {
/*
* Create a new Dictionary
*/
this.New = function () {
return new dict();
};
/*
* Return argument f if it is a function otherwise return undefined
*/
function ensureF(f) {
if (isFunct(f)) {
return f;
}
}
function isFunct(f) {
return (typeof f == "function");
}
/*
* Add a "_" as first character just to be sure valid property name
*/
function makeKey(k) {
return "_" + k;
};
/*
* Key Value Pair object - held in array
*/
function newkvp(key, value) {
return {
key: key,
value: value,
toString: function () { return this.key; },
valueOf: function () { return this.key; }
};
};
/*
* Return the current set of keys.
*/
function keys(a) {
// remove the leading "-" character from the keys
return a.map(function (e) { return e.key.substr(1); });
// Alternative: Requires Opera 12 vs. 11.60
// -- Must pass the internal object instead of the array
// -- Still need to remove the leading "-" to return user key values
// Object.keys(o).map(function (e) { return e.key.substr(1); });
};
/*
* Return the current set of values.
*/
function values(a) {
return a.map(function(e) { return e.value; } );
};
/*
* Return the current set of key value pairs.
*/
function kvPs(a) {
// remove the leading "-" character from the keys
return a.map(function (e) { return newkvp(e.key.substr(1), e.value); });
}
/*
* Returns true if key exists in the dictionary.
* k - Key to check (with the leading "_" character)
*/
function exists(k, o) {
return o.hasOwnProperty(k);
}
/*
* Array Map implementation
*/
function map(a, f) {
if (!isFunct(f)) { return; }
return a.map(function (e, i) { return f(e.value, i); });
}
/*
* Array Every implementation
*/
function every(a, f) {
if (!isFunct(f)) { return; }
return a.every(function (e, i) { return f(e.value, i) });
}
/*
* Returns subset of "values" where function "f" returns true for the "value"
*/
function filter(a, f) {
if (!isFunct(f)) {return; }
var ret = a.filter(function (e, i) { return f(e.value, i); });
// if anything returned by array.filter, then get the "values" from the key value pairs
if (ret && ret.length > 0) {
ret = values(ret);
}
return ret;
}
/*
* Array Reverse implementation
*/
function reverse(a, o) {
a.reverse();
reindex(a, o, 0);
}
/**
* Randomize array element order in-place.
* Using Fisher-Yates shuffle algorithm.
*/
function shuffle(a, o) {
var j, t;
for (var i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
t = a[i];
a[i] = a[j];
a[j] = t;
}
reindex(a, o, 0);
return a;
}
/*
* Array Some implementation
*/
function some(a, f) {
if (!isFunct(f)) { return; }
return a.some(function (e, i) { return f(e.value, i) });
}
/*
* Sort the dictionary. Sorts the array and reindexes the object.
* a - dictionary array
* o - dictionary object
* sf - dictionary default sort function (can be undefined)
* f - sort method sort function argument (can be undefined)
*/
function sort(a, o, sf, f) {
var sf1 = f || sf; // sort function method used if not undefined
// if there is a customer sort function, use it
if (isFunct(sf1)) {
a.sort(function (e1, e2) { return sf1(e1.value, e2.value); });
}
else {
// sort by key values
a.sort();
}
// reindex - adds O(n) to perf
reindex(a, o, 0);
// return sorted values (not entire array)
// adds O(n) to perf
return values(a);
};
/*
* forEach iteration of "values"
* uses "for" loop to allow exiting iteration when function returns true
*/
function forEach(a, f) {
if (!isFunct(f)) { return; }
// use for loop to allow exiting early and not iterating all items
for(var i = 0; i < a.length; i++) {
if (f(a[i].value, i)) { break; }
}
};
/*
* forEachR iteration of "values" in reverse order
* uses "for" loop to allow exiting iteration when function returns true
*/
function forEachR(a, f) {
if (!isFunct(f)) { return; }
// use for loop to allow exiting early and not iterating all items
for (var i = a.length - 1; i > -1; i--) {
if (f(a[i].value, i)) { break; }
}
}
/*
* Add a new Key Value Pair, or update the value of an existing key value pair
*/
function add(key, value, a, o, resort, sf) {
var k = makeKey(key);
// Update value if key exists
if (exists(k, o)) {
a[o[k]].value = value;
}
else {
// Add a new Key value Pair
var kvp = newkvp(k, value);
o[kvp.key] = a.length;
a.push(kvp);
}
// resort if requested
if (resort) { sort(a, o, sf); }
};
/*
* Removes an existing key value pair and returns the
* "value" If the key does not exists, returns undefined
*/
function remove(key, a, o) {
var k = makeKey(key);
// return undefined if the key does not exist
if (!exists(k, o)) { return; }
// get the array index
var i = o[k];
// get the key value pair
var ret = a[i];
// remove the array element
a.splice(i, 1);
// remove the object property
delete o[k];
// reindex the object properties from the remove element to end of the array
reindex(a, o, i);
// return the removed value
return ret.value;
};
/*
* Returns true if key exists in the dictionary.
* k - Key to check (without the leading "_" character)
*/
function keyExists(k, o) {
return exists(makeKey(k), o);
};
/*
* Returns value assocated with "key". Returns undefined if key not found
*/
function item(key, a, o) {
var k = makeKey(key);
if (exists(k, o)) {
return a[o[k]].value;
}
}
/*
* changes index values held by object properties to match the array index location
* Called after sorting or removing
*/
function reindex(a, o, i){
for (var j = i; j < a.length; j++) {
o[a[j].key] = j;
}
}
/*
* The "real dictionary"
*/
function dict() {
var _a = [];
var _o = {};
var _sortF;
Object.defineProperties(this, {
"length": { get: function () { return _a.length; }, enumerable: true },
"keys": { get: function() { return keys(_a); }, enumerable: true },
"values": { get: function() { return values(_a); }, enumerable: true },
"keyValuePairs": { get: function() { return kvPs(_a); }, enumerable: true},
"sortFunction": { get: function() { return _sortF; },
set: function(funct) { _sortF = ensureF(funct); }, enumerable: true }
});
// Array Methods - Only modification to not
// pass the actual array to the callback function
this.map = function(funct) { return map(_a, funct); };
this.every = function(funct) { return every(_a, funct); };
this.filter = function(funct) { return filter(_a, funct); };
this.reverse = function() { reverse(_a, _o); };
this.shuffle = function () { return shuffle(_a, _o); };
this.some = function(funct) { return some(_a, funct); };
this.sort = function(funct) { return sort(_a, _o, _sortF, funct); };
// Array Methods - Modified aborts when funct returns true.
this.forEach = function (funct) { forEach(_a, funct) };
// forEach in reverse order
this.forEachRev = function (funct) { forEachR(_a, funct) };
// Dictionary Methods
this.addOrUpdate = function(key, value, resort)
{ return add(key, value, _a, _o, resort, _sortF); };
this.remove = function(key) { return remove(key, _a, _o); };
this.exists = function(key) { return keyExists(key, _o); };
this.item = function(key) { return item(key, _a, _o); };
this.get = function (index)
{ if (index > -1 && index < _a.length) { return _a[index].value; } } ,
this.clear = function() { _a = []; _o = {}; };
return this;
}
return this;
}
我在试图在概念上协调类对象和原型对象时,有了一个顿悟,那就是原型基本上是创建对象的 v-table。此外,闭包中的函数也可以充当 v-table 条目。随着项目的进展,我开始使用对象工厂,其中一个顶层对象包含对象类型的通用函数,并包含一个“this.New(args)
”方法,用于创建解决方案中使用的实际对象。这就是我为字典使用的风格。
字典的核心是一个数组、一个对象和一个 KeyValuePair
对象。“addOrUpdate
”方法接收一个键和一个值,然后
- 创建一个
KeyValuePair
- 使用键作为属性名,数组长度作为属性值,向对象添加一个新属性
- 将
KeyValuePair
添加到数组中,使对象的新属性值成为数组中的索引
注意:我了解到对象属性名可以以“几乎任何”Unicode 字符开头。该项目将处理客户数据,客户数据可以以“任何”Unicode 字符开头。为了确保字典不会因为无效的属性名而崩溃,我在键前加上下划线(_),并在将键返回到字典外部时去掉该下划线。
为了使字典可用,内部数组和对象必须保持同步。为了确保这一点,数组和对象都不会暴露给外部。我希望避免意外更改,例如当“If
”测试只有一个等于号,并且错误地设置了左侧值时可能发生的更改。
示例
If(dict.KeyObj[“SomeKey”] = “oops”) { alert(“good luck tracing this down:-)”); }
这种典型的字典错误在计算、显示等方面开始出现 bug(症状)时,可能非常难以追踪。因此,“this
”属性将无法访问其中任何一个。这种保护机制是我没有更深入研究原型的原因之一。我曾想过使用一个内部对象,暴露数组和对象,并在使用“call
”或“apply
”方法时传递这个内部对象,我以后可能会考虑这一点,因为我不确定我是否不会不得不暴露这个内部对象,那样就会破坏保护核心数组和对象的目的。
我修正了一些我在创建第一个字典对象时犯的新手错误。
- “
Dict()
”函数包含了每个字典对象的大部分工作代码。我用来判断是使用一个封闭函数还是在实际字典对象中使用功能性的标准是- 一行以上的代码
- 被其他封闭函数使用
- 可能会发生变化,随着我发现 bug/问题的出现而增长
- 在合理的情况下使用了
Array
方法和属性名。从 C# 过来的我,做了一些事情,使得我的字典可用性降低,例如使用“Count
”而不是“length
”,或者使用“ForEach
”而不是“forEach
”。通过使用Array
名称,字典现在在大多数情况下可以像Array
一样使用。不幸的是,我找不到一种方法来创建方括号访问器(例如val = dict[key]
),这也许是件好事。仔细想想,我很难确定像val = dict[12]
这样的调用是否能正确工作。数字12
很容易被用作键,所以我无法想出一种好的方法来确定这种调用的“意图”。 - 完全封装了下划线前缀的处理。在我工作的项目中,我把这些分散并重复在各种数据模型对象中。这很丑陋!
现在我已经改进了字典的功能,它真的有助于提高性能吗?为了回答这个问题,我创建了几个测试对象。第一个测试对象使用 string
作为字典值。为了简单起见,键和值都使用相同的 string
。
function DictPerf() {
this.testPerf = function (dict, size) {
var testData = makeTestData(size);
dict.clear();
dict.sortFunction = undefined;
var arr = [];
var results = [];
results.push("Perf Testing: Count= " + size);
results.push(loadTestingDict(dict, testData));
results.push(loadTestingArr(arr, testData));
results.push(accessTestingDict(dict, testData));
results.push(accessTestingArr(arr, testData));
results.push(mapTestingDict(dict, testData));
results.push(mapTestingArr(arr, testData));
results.push(forEachTestingDict(dict, testData));
results.push(forEachTestingArr(arr, testData));
results.push(reverseTestingDict(dict, testData));
results.push(reverseTestingArr(arr, testData));
results.push(sortTestingDict(dict, testData));
results.push(sortTestingArr(arr, testData));
results.push(sortTestingDictRevCmp(dict, testData));
results.push(sortTestingArrRevCmp(arr, testData));
results.push(sortTestingDictFwdCmp(dict, testData));
results.push(sortTestingArrFwdCmp(arr, testData));
return results;
}
function testResult(name, start, end) {
return {
Name: name,
Start: start,
End: end,
toString: function () { return this.Name +
" milliseconds:" + (this.End - this.Start) }
};
}
function loadTestingDict(dict, testData) {
var name = "Dictionary Load Testing";
var start = new Date();
for (var i = 0; i < testData.length; i++) {
dict.addOrUpdate(testData[i], testData[i]);
};
var end = new Date();
return testResult(name, start, end);
}
function loadTestingArr(arr, testData) {
var name = "Array Load Testing";
var start = new Date();
for (var i = 0; i < testData.length; i++) {
arr.push(testData[i]);
};
var end = new Date();
return testResult(name, start, end);
}
function accessTestingDict(dict, testData) {
var name = "Dictionary Access Testing";
var start = new Date();
var val;
for (var i = 0; i < testData.length; i++) {
val = dict.item(testData[i]);
if (val != testData[i]) {
throw new Error("Dict: val != testData[i]");
}
};
var end = new Date();
return testResult(name, start, end);
}
function accessTestingArr(arr, testData) {
var name = "Array Access Testing";
var start = new Date();
var val;
for (var i = 0; i < testData.length; i++) {
val = arr[arr.indexOf(testData[i])];
if (val != testData[i]) {
throw new Error("Array: val != testData[i]");
}
};
var end = new Date();
return testResult(name, start, end);
}
function forEachTestingDict(dict, testData) {
var name = "Dict ForEach Testing";
var start = new Date();
var data = []
dict.forEach(function(e) { data.push(e); });
var end = new Date();
return testResult(name, start, end);
}
function forEachTestingArr(arr, testData) {
var name = "Array ForEach Testing";
var start = new Date();
var data = []
arr.forEach(function(e) { data.push(e); });
var end = new Date();
return testResult(name, start, end);
}
function mapTestingDict(dict, testData) {
var name = "Dict Map Testing";
var start = new Date();
var data = dict.map(function (e) { return e; });
var end = new Date();
return testResult(name, start, end);
}
function mapTestingArr(arr, testData) {
var name = "Array Map Testing";
var start = new Date();
var data = arr.map(function (e) { return e; });
var end = new Date();
return testResult(name, start, end);
}
function reverseTestingDict(dict, testData) {
var name = "Dict Reverse Testing";
var start = new Date();
dict.reverse();
var end = new Date();
return testResult(name, start, end);
}
function reverseTestingArr(arr, testData) {
var name = "Array Reverse Testing";
var start = new Date();
arr.reverse();
var end = new Date();
return testResult(name, start, end);
}
function sortFwd(v1, v2) {
if (v1 > v2) {
return 1;
}
if (v1 < v2) {
return -1;
}
return 0;
}
function sortRev(v1, v2) {
return -1 * sortFwd(v1, v2);
}
function sortTestingDict(dict, testData) {
var name = "Dict Sort Testing";
var start = new Date();
dict.sort();
var end = new Date();
return testResult(name, start, end);
}
function sortTestingArr(arr, testData) {
var name = "Array Sort Testing";
var start = new Date();
arr.sort();
var end = new Date();
return testResult(name, start, end);
}
function sortTestingDictRevCmp(dict, testData) {
var name = "Dict Sort Reverse Comparer Testing";
var start = new Date();
dict.sort(sortRev);
var end = new Date();
return testResult(name, start, end);
}
function sortTestingArrRevCmp(arr, testData) {
var name = "Array Sort Reverse Comparer Testing";
var start = new Date();
arr.sort(sortRev);
var end = new Date();
return testResult(name, start, end);
}
function sortTestingDictFwdCmp(dict, testData) {
var name = "Dict Sort Forward Comparer Testing";
var start = new Date();
dict.sort(sortFwd);
var end = new Date();
return testResult(name, start, end);
}
function sortTestingArrFwdCmp(arr, testData) {
var name = "Array Sort Forward Comparer Testing";
var start = new Date();
arr.sort(sortFwd);
var end = new Date();
return testResult(name, start, end);
}
function makeKvp(k, v) {
return { key: k, value: v };
};
function makeTestData(size) {
var arrData = [], val;
for(var i = 0; i < size; i++) {
val = "D" + zeroPad(i, 10);
arrData.push(val);
}
shuffleArray(arrData);
return arrData;
}
/**
* Randomize array element order in-place.
* Using Fisher-Yates shuffle algorithm.
*/
function shuffleArray(array) {
var j, temp;
for (var i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function zeroPad(num, size) {
var n = Math.abs(num);
var zeros = Math.max(0, size - Math.floor(n).toString().length );
var zeroString = Math.pow(10, zeros).toString().substr(1);
if( num < 0 ) {
zeroString = '-' + zeroString;
}
return zeroString+n;
}
return this;
}
在运行测试并汇总结果后,我决定创建第二个性能测试对象,该对象使用对象作为值。对于我正在工作的项目,数据模型对象是字典值,并且原始类型数组和对象数组在性能上可能存在差异。
function DictPerfObj() {
this.testPerf = function (dict, size) {
var testData = makeTestData(size);
dict.clear();
dict.sortFunction = undefined;
var arr = [];
var results = [];
results.push("Perf Testing: Count= " + size);
results.push(loadTestingDict(dict, testData));
results.push(loadTestingArr(arr, testData));
results.push(accessTestingDict(dict, testData));
results.push(accessTestingArr(arr, testData));
results.push(mapTestingDict(dict, testData));
results.push(mapTestingArr(arr, testData));
results.push(forEachTestingDict(dict, testData));
results.push(forEachTestingArr(arr, testData));
results.push(reverseTestingDict(dict, testData));
results.push(reverseTestingArr(arr, testData));
results.push(sortTestingDict(dict, testData));
results.push(sortTestingArr(arr, testData));
results.push(sortTestingDictRevCmp(dict, testData));
results.push(sortTestingArrRevCmp(arr, testData));
results.push(sortTestingDictFwdCmp(dict, testData));
results.push(sortTestingArrFwdCmp(arr, testData));
return results;
}
function testResult(name, start, end) {
return {
Name: name,
Start: start,
End: end,
toString: function () { return this.Name +
" milliseconds:" + (this.End - this.Start) }
};
}
function loadTestingDict(dict, testData) {
var name = "Dictionary Load Testing";
var start = new Date();
for (var i = 0; i < testData.length; i++) {
dict.addOrUpdate(testData[i].key, testData[i].value);
};
var end = new Date();
return testResult(name, start, end);
}
function loadTestingArr(arr, testData) {
var name = "Array Load Testing";
var start = new Date();
for (var i = 0; i < testData.length; i++) {
arr.push(testData[i]);
};
var end = new Date();
return testResult(name, start, end);
}
function accessTestingDict(dict, testData) {
var name = "Dictionary Access Testing";
var start = new Date();
var val;
for (var i = 0; i < testData.length; i++) {
val = dict.item(testData[i].key);
if (val != testData[i]) {
throw new Error("Dict: val != testData[i]");
}
};
var end = new Date();
return testResult(name, start, end);
}
function accessTestingArr(arr, testData) {
var name = "Array Access Testing";
var start = new Date();
var val;
for (var i = 0; i < testData.length; i++) {
val = arr[arr.indexOf(testData[i])];
if (val.value != testData[i].value) {
throw new Error("Array: val != testData[i]");
}
};
var end = new Date();
return testResult(name, start, end);
}
function forEachTestingDict(dict, testData) {
var name = "Dict ForEach Testing";
var start = new Date();
var data = []
dict.forEach(function(e) { data.push(e); });
var end = new Date();
return testResult(name, start, end);
}
function forEachTestingArr(arr, testData) {
var name = "Array ForEach Testing";
var start = new Date();
var data = []
arr.forEach(function(e) { data.push(e.value); });
var end = new Date();
return testResult(name, start, end);
}
function mapTestingDict(dict, testData) {
var name = "Dict Map Testing";
var start = new Date();
var data = dict.map(function (e) { return e; });
var end = new Date();
return testResult(name, start, end);
}
function mapTestingArr(arr, testData) {
var name = "Array Map Testing";
var start = new Date();
var data = arr.map(function (e) { return e.value; });
var end = new Date();
return testResult(name, start, end);
}
function reverseTestingDict(dict, testData) {
var name = "Dict Reverse Testing";
var start = new Date();
dict.reverse();
var end = new Date();
return testResult(name, start, end);
}
function reverseTestingArr(arr, testData) {
var name = "Array Reverse Testing";
var start = new Date();
arr.reverse();
var end = new Date();
return testResult(name, start, end);
}
function sortFwd(v1, v2) {
if (v1 > v2) {
return 1;
}
if (v1 < v2) {
return -1;
}
return 0;
}
function sortFwdArr(v1, v2) {
if (v1.value > v2.value) {
return 1;
}
if (v1.value < v2.value) {
return -1;
}
return 0;
}
function sortRevArr(v1, v2) {
return -1 * sortFwdArr(v1, v2);
}
function sortRev(v1, v2) {
return -1 * sortFwd(v1, v2);
}
function sortTestingDict(dict, testData) {
var name = "Dict Sort Testing";
var start = new Date();
dict.sort();
var end = new Date();
return testResult(name, start, end);
}
function sortTestingArr(arr, testData) {
var name = "Array Sort Testing";
var start = new Date();
arr.sort();
var end = new Date();
return testResult(name, start, end);
}
function sortTestingDictRevCmp(dict, testData) {
var name = "Dict Sort Reverse Comparer Testing";
var start = new Date();
dict.sort(sortRev);
var end = new Date();
return testResult(name, start, end);
}
function sortTestingArrRevCmp(arr, testData) {
var name = "Array Sort Reverse Comparer Testing";
var start = new Date();
arr.sort(sortRevArr);
var end = new Date();
return testResult(name, start, end);
}
function sortTestingDictFwdCmp(dict, testData) {
var name = "Dict Sort Forward Comparer Testing";
var start = new Date();
dict.sort(sortFwd);
var end = new Date();
return testResult(name, start, end);
}
function sortTestingArrFwdCmp(arr, testData) {
var name = "Array Sort Forward Comparer Testing";
var start = new Date();
arr.sort(sortFwdArr);
var end = new Date();
return testResult(name, start, end);
}
/*
* Key Value Pair object - held in array
*/
function newkvp(key, value) {
return {
key: key,
value: value,
toString: function () { return this.key; },
valueOf: function () { return this.key; }
};
};
function makeTestData(size) {
var arrData = [], val;
for(var i = 0; i < size; i++) {
val = "D" + zeroPad(i, 10);
arrData.push(newkvp(val, val));
}
shuffleArray(arrData);
return arrData;
}
/**
* Randomize array element order in-place.
* Using Fisher-Yates shuffle algorithm.
*/
function shuffleArray(array) {
var j, temp;
for (var i = array.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
function zeroPad(num, size) {
var n = Math.abs(num);
var zeros = Math.max(0, size - Math.floor(n).toString().length );
var zeroString = Math.pow(10, zeros).toString().substr(1);
if( num < 0 ) {
zeroString = '-' + zeroString;
}
return zeroString+n;
}
return this;
}
我分别使用了 100、1000 和 10,000 个元素运行了这两组性能测试代码。我在 Firefox、Chrome 和 Internet Explorer 中运行了这些测试。
- 100 个元素:在所有实际意义上,可以认为这里的所有值都在 0 和 2 之间。我在每个浏览器中多次运行了测试,0 是最常见的,1 次之,有时会出现 2。我在下面的表格中只放入了最后一次测试运行的值。
- 1000 和 10,000 个元素:对于这些,我运行了测试 10 次并取了结果的平均值。我认为我不需要进行更多测试就能获得性能的一般概念。
- 对于数组访问测试,我使用了
array[array.indexOf("key"))
。这将是我根据“key
”而不是索引查找值的方式。
字符串值
Firefox Chrome IE
Process Elements Dict Array Dict Array Dict Array
Loading 100 1 0 0 0 0 0
Access 100 1 2 0 0 0 1
Map Function 100 0 0 0 0 0 0
ForEach Function 100 0 0 0 0 0 0
Reverse Function 100 0 0 0 0 0 0
Sort 100 0 0 0 0 0 0
Sort Reverse Compare 100 0 0 0 0 0 0
Sort Forward Compare 100 0 0 0 0 0 0
Loading 1000 6.1 0.4 1.5 0 5.5 0.1
Access 1000 2.7 74.2 1.6 26.7 2 41.7
Map Function 1000 0 0.3 0 0 0 0
ForEach Function 1000 0.3 0 0 1.6 0 0.1
Reverse Function 1000 0.8 0.1 0 0 0.9 0
Sort 1000 1.8 0.8 1.5 0 1.2 0.9
Sort Reverse Compare 1000 2.3 1 0 3.1 2.1 1.9
Sort Forward Compare 1000 1.2 0.9 1.6 3.1 1.8 1.5
Loading 10000 25.5 0.9 29.8 0 25.7 0.3
Access 10000 12.8 8313.6 10.9 2436.8 12.8 4985.4
Map Function 10000 1.3 1.1 3.1 0 0.5 0.4
ForEach Function 10000 0.9 0.9 1.6 1.6 0.3 0.5
Reverse Function 10000 6.6 0.2 6.2 1.5 4.4 0
Sort 10000 23.7 16.9 48 21.8 24.8 11.1
Sort Reverse Compare 10000 33.6 16.9 33 13.7 33.2 18.5
Sort Forward Compare 10000 29.6 12.8 22 9.3 33.1 18
对象值
Firefox Chrome IE
Process Elements Dict Array Dict Array Dict Array
Loading 100 1 0 0 0 0 0
Access 100 1 2 0 0 0 1
Map Function 100 0 0 0 0 0 0
ForEach Function 100 0 0 0 0 0 0
Reverse Function 100 0 0 0 0 0 0
Sort 100 0 0 0 0 0 0
Sort Reverse Compare 100 0 0 0 0 0 0
Sort Forward Compare 100 0 0 0 0 0 0
Loading 1000 7.4 0.4 9.2 0 5.2 0.1
Access 1000 4.9 54.3 0 6.3 3.7 28.9
Map Function 1000 0.5 0.1 0 0 0.3 0.4
ForEach Function 1000 0.1 0.4 0 0 0 0
Reverse Function 1000 0.4 0.1 3.1 0 0.4 0
Sort 1000 1.8 1.2 6.3 3.2 1.6 1
Sort Reverse Compare 1000 2.2 1.2 4.6 0 2.9 2.2
Sort Forward Compare 1000 1.6 0.8 1.6 3 2.7 1.7
Loading 10000 26.7 1 29.7 0 25.5 0.3
Access 10000 20.4 4870.8 15.7 563.1 18.8 3243.4
Map Function 10000 1.5 1.4 0 6.1 2.8 1.3
ForEach Function 10000 0.6 1.4 0 7.9 0.7 1.5
Reverse Function 10000 6.5 0.5 3.2 0 4.7 0
Sort 10000 23.6 15.4 48.4 43.7 39 15
Sort Reverse Compare 10000 33.9 19.5 25 15.5 33.6 20
Sort Forward Compare 10000 29.8 14.6 23.4 17.2 32.5 18.8
正如预期的那样,字典加载比数组加载慢得多,而字典访问比按“键”值进行数组访问快得多。我没有预料到的是浏览器在数组访问计时方面存在如此巨大的差异。Chrome 显然更快,其次是 Internet Explorer,然后是 Firefox。另一件让我惊讶的事情是,在所有这三个浏览器中,数组访问对象比数组访问字符串快得多。我实际上期望结果相反。
为了回答最初的问题:“JavaScript 是否需要字典对象”,我必须说绝对需要,当需要快速访问大量数据中的单个数据点时。然而,与大多数工具一样,它也会被用在不该用的地方,在某些情况下会导致性能下降。遍历短数组通常与字典查找一样快,甚至更快,并且消除了字典和 KeyValuePair
创建的开销。请记住,我的字典和测试代码都是解释执行的 JavaScript。如果 JavaScript 本身有一个字典对象,它将以编译代码的形式运行,可能消除我数组类似方法中的大部分性能缓慢问题,并且肯定会提高加载时间。
我在这里没有实现的一点是 JSON 的往返转换。