Sabemos que un objeto es una "bolsa" de keys (propiedades) y values (valores). Por ejemplo si ejecutamos el siguiente código:
var obj = { a: 1 };
console.log(Object.keys(obj)); // [ "a" ]
podemos entender que Object.keys nos retorna un array con los keys que contiene el objeto que recibe por parámetro. A su vez, un objeto puede poseer propiedades pero éstas estar definidas para que no sean enumerables. Una propiedad no-enumerable no es recorrida con loops for...in
y no es considerada por Object.keys
. La forma de definir propiedades no-enumerables es con Object.defineProperty. Se puede saber también si una propiedad es enumerable usando Object.prototype.propertyIsEnumerable, por ejemplo:
console.log(Object.keys(Math)); // []
console.log(Math.propertyIsEnumerable('random')); // false
console.log(Object.getOwnPropertyNames(Math)); // ["E", "LN10", "LN2", "LOG2E", "LOG10E", "PI", "SQRT1_2", "SQRT2", "random", "abs", "acos", "asin", "atan", "ceil", "cos", "exp", "floor", "log", "round", "sin", "sqrt", "tan", "atan2", "pow", "max", "min", "imul", "sign", "trunc", "sinh", "cosh", "tanh", "asinh", "acosh", "atanh", "log10", "log2", "hypot", "fround", "clz32", "cbrt", "log1p", "expm1"]
Por lo tanto, si queremos obtener todas las propiedades de un objeto, ya sean enumerables o no, debemos utilizar Object.getOwnPropertyNames. Si lo aplicamos a un array vacío:
console.log(Object.getOwnPropertyNames([])); // ["length"]
console.log([].forEach); // function forEach() { [native code] }
Entonces, ¿dónde reside la función forEach? ¿Cuando se setea en el array vacío?¿Pertenece realmente al array vacío?
JavaScript es conocido por ser uno de los únicos lenguajes de programación popular en utilizar herencia prototipada. Otros conocidos son Perl, Lua y Lisp. Esto significa básicamente que el reuso de componentes será dado por un link ascendente y uno descendente dentro de algo llamado prototype chain. Por ejemplo, cualquier objeto hereda de un único objeto que tiene predefinidas las propiedades que utilizamos comunmente. Para "subir" por la prototype chain se puede utilizar la propiedad (no estandar) __proto__
. Por ejemplo:
var obj = {};
Object.getOwnPropertyNames(obj); // []
Object.getOwnPropertyNames(obj.__proto__); // ["constructor", "toString", "toLocaleString", "valueOf", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__lookupGetter__", "__defineSetter__", "__lookupSetter__", "__proto__"]
Dónde obj.__proto__
referencia a el objeto del cual heredan todos los objetos. Lo mismo ocurre con arrays y funciones, que a su vez cómo ya sabemos son objetos también.
Dada una función constructora, por ejemplo Array, si queremos referenciar al objeto que van a referenciar todos los arrays que se creen debemos utilizar la propiedad prototype
. Por ejemplo:
console.log([].__proto__ === Array.prototype) // true
Podemos visualizar que todo array creado linkea a través de la prototype chain (ascendente) a Array.prototype
. Douglas Crockford comenta mucho acerca de este tema y hace incapié especialmente porque muchos desarrolladores mantienen la forma de pensar de "herencia clásica" y no aceptan la forma prototipada. Según Douglas, la mejor forma de verlo es objetos que heredan comportamiento de objetos.