В JavaScript перечисление по обычным (не массивам) объектам часто более болезненно, чем должно быть. Массивы весело отправляются через циклы for и while с использованием всевозможных безумных, забавных приемов ; Объекты навсегда находятся во власти пешехода, одного направленного цикла for-in, без которого мы не можем даже узнать имена и длину его собственного набора свойств. Массивы имеют доступ к множеству элегантных функций высшего порядка (forEach, map, filter и т. Д.); Объекты не До сих пор это так.
Заимствуя из Prototype.js, ECMAScript 5 определяет два изящных новых метода Object.keys (obj) и довольно неуклюже названный Object.getOwnPropertyNames (obj) . Они уже работают в текущих версиях Chrome и Safari и будут поддерживаться в Firefox 4 и IE9.
Object.keys (OBJ)
Этот метод возвращает массив всех имен перечислимых свойств, определенных данным объектом (унаследованные свойства не рассматриваются). Обратите внимание, что последовательность основана на последовательности циклов for-in по умолчанию, которая может незначительно отличаться в разных браузерах (подробности о последовательности for-in см. В этой статье ):
//Chrome, Safari, FF4, IE9 var purchases = {butter: 3.00, soap: 5.95, pineapple: 3.50 }; Object.keys(purchases); //['butter', 'soap', 'pineapple']
Теперь мы можем перебирать свойства объекта в любой последовательности, используя цикл for…
//Chrome, Safari, FF4, IE9 var keys = Object.keys(purchases), totalCost = 0; for (var i=keys.length; i--;) { totalCost += purchases[keys[i]]; } totalCost; //12.45
… или цикл времени …
//Chrome, Safari, FF4, IE9 var keys = Object.keys(purchases), i=keys.length, totalCost = 0; while (i--) { totalCost += purchases[keys[i]]; } totalCost; //12.45
Для тех браузеров, которые еще не реализуют Object.keys, мы можем применить следующее shim (спасибо @jdalton за напоминание о необходимости добавления проверки типов):
//all browsers if (typeof Object.keys != 'function') { Object.keys = function(obj) { if (typeof obj != "object" && typeof obj != "function" || obj == null) { throw TypeError("Object.keys called on non-object"); } var keys = []; for (var p in obj) obj.hasOwnProperty(p) &&keys.push(p); return keys; } } Object.keys({a:1, b:2, c:3}); //['a', 'b', 'c']
Теперь легко использовать Object с одним из итераторов высшего порядка, предоставляемых Array.prototype…
var thing = { size: 14, color: 'kind of off-white', greet: function() {return "thronk"} }; var thingFunctions = Object.keys(thing).filter(function(e) { return typeof thing[e] == 'function' }); thingFunctions; //["greet"]
… И мы можем использовать функцию map для создания метода Object.values (потому что вы знаете, что Harmony добавит его в любую минуту )
Object.values = function(obj) { return Object.keys(obj).map(function(e) { return obj[e] }); } Object.values({a:1, b:2, c:3}); //[1, 2, 3]
Object.getOwnPropertyNames (OBJ)
Это драгоценный камень. Он похож на Object.keys, но дополнительно возвращает имена не перечисляемых свойств (опять же, унаследованные свойства не включены). Теперь, наконец, вы можете перечислить свойства Math! Следующий фрагмент собирает каждую математическую функцию, которая ожидает ровно один аргумент, и вызывает его, передавая число 10…
//Chrome, Safari, FF4, IE9 var mathProps = Object.getOwnPropertyNames(Math); var oneArgMathFunctions = mathProps.forEach(function(e) { if((typeof Math[e] == 'function') && (Math[e].length == 1)) { console.log("Math." + e + "(10) -> " + Math[e](10)); } }); //Math.cos(10) -> -0.8390715290764524 //Math.log(10) -> 2.302585092994046 //Math.tan(10) -> 0.6483608274590867 //Math.sqrt(10) -> 3.1622776601683795 //etc...
… и вот массив всех свойств String.prototype …
//Chrome, Safari, FF4, IE9 Object.getOwnPropertyNames(String.prototype); //["length", "constructor", "concat", "localeCompare", "substring", "italics", "charCodeAt", "strike", "indexOf", "toLowerCase", "trimRight", "toString", "toLocaleLowerCase", "replace", "toUpperCase", "fontsize", "trim", "split", "substr", "sub", "charAt", "blink", "lastIndexOf", "sup", "fontcolor", "valueOf", "link", "bold", "anchor", "trimLeft", "small", "search", "fixed", "big", "match", "toLocaleUpperCase", "slice"]
Unlike Object.keys we can’t replicate Object.getOwnPropertyNames using regular JavaScript since non-enumerable properties are out of bounds when using traditional iteration loops. Check out this log for an insight into the hazards encountered during the webkit implementation.
A word on TypeErrors
EcmaScript 5 is making gestures towards limiting auto-coercion, notably with the introduction of Strict Mode. That effort also extends to most of the new methods introduced on Object, including Object.keys and Object.getOwnPropertyNames. Neither method will coerce primitive arguments into Objects – in fact they will both throw a TypeError:
//Chrome, Safari, FF4, IE9 Object.keys("potato"); //TypeError: Object.keys called on non-object Object.getOwnPropertyNames("potato"); //TypeError: Object.getOwnPropertyNames called on non-object
Thus, the following examples represent one of the few scenarios outside of Strict Mode where it makes sense to use the new String construction. Note that when either method is passed a string, the index name of each character is included.
//Chrome, Safari, FF4, IE9 Object.keys(new String("potato")) //["0", "1", "2", "3", "4", "5"] Object.getOwnPropertyNames(new String("potato")) //["0", "1", "2", "3", "4", "5", "length"]
Wrap Up
Once they are available across all the major browsers Object.keys and Object.getOwnPropertyNames will make object/hash manipulation leaner and more powerful by plugging a major hole in the JavaScript Object API. Moreover as the line between Arrays and regular Objects blurs (aided by custom getters and setters) we’re likely to see a growth in generic “array-like” objects which enjoy the best of both worlds – non-numeric identifiers and access to the rich API set defined by Array.prototype. EcmaScript 5 has apparently pre-empted this trend by introducing the generic method, defined by one type but useable by any.
There’s a seismic shift under way – be ready for it!
Further Reading
ECMA-262 5th Edition
Object.keys(obj)
Object.getOwnPropertyNames(obj)