Seleccionar página
Recientemente nos hemos encontrado con la necesidad de identificar si una determinada función es una función nativa del lenguaje Javascript o ha sido creada por código. La cuestión parece sencilla, pero cuando nos ponemos a pensar como resolverlo vemos que en principio las funciones y métodos escritos por nosotros en Javascript y los proporcionados con por el lenguaje no se diferencian a primera vista. Las funciones nativas se pueden incluso reescribir por otras nuestras, por lo que no tienen ningún privilegio o diferencia que podamos observar fácilmente.

Ya nos enfrentamos a un problema similar cuando nos preguntamos cómo diferenciar arrow function de function. En esa ocasión descubrimos dos formas de actuar, analizando el prototipo (que finalmente resultó infructuosa) y analizando la cadena devuelta por .toString()

Lamentablemente no hay nada identificable en la cadena de prototipos para localizar las funciones nativas. Es cierto que las funciones nativas no tienen prototype, ya que no pueden ser utilizadas como constructores, pero tampoco lo tienen los métodos de nuestras clases o las arrow function. Por aquí no vamos a encontrar la solución.

Por lo tanto, no nos queda más remedio que analizar el código devuelto por .toString(). Comprobamos en diferentes navegadores y motores que siempre incluye la cadena [native code]:

console.log (parseInt.toString ());
 

function parseInt() { [native code] }

Por lo tanto, con una pequeña comprobación tenemos un sencillo medio para identificar si una función es nativa o está construida por código.

function isNative(fn) {
  return fn.toString().includes('[native code]');
}

Como primera aproximación no está mal, pero cualquier código que contenga el texto '[native code]' dará positivo. Es cierto que no es un texto muy habitual, pero nada impide que ese texto aparezca en el cuerpo de una función. Por ejemplo, si preguntamos si la propia función isNativeFunction() es nativa, obtendremos un falso positivo:

console.log(isNativeFunction(isNativeFunction)); // true

Con poco esfuerzo adicional podemos hacer una comprobación un poco más completa, incluyendo si es una función, las llaves y los posibles espacios en blanco entre los elementos, el final de la cadena y el propio nombre de la función.

function isNativeFunction (fn) {
  const str = fn.toString ();
  return typeof (fn) === 'function' &&
         /\{\s*\[\s*native code\s*\]\s*\}$/.test (str) &&
         str.includes (fn.name);
}
 

Ahora se puede ejecutar una batería de pruebas con diferentes opciones y comprobar que todo funciona correctamente:

console.assert (!isNativeFunction (function () {}));
console.assert (!isNativeFunction (function* () {}));
console.assert (!isNativeFunction (() => {}));

class C {m() {}}
console.assert (!isNativeFunction (C));
console.assert (!isNativeFunction (C.prototype.m));
console.assert (!isNativeFunction (isNativeFunction));
console.assert (!isNativeFunction ({
  toString () {
    return `function x { [native code] }`;
  }
}));
console.assert (!isNativeFunction (
  function x () {
    return "function x { [native code] }";
  }
));

Pero, ¿es seguro hacer uso de .toString()?. Lo cierto es que el estándar Javascript ha sido un poco confuso en cuanto a lo que tiene que devolver la función .toString() en el caso de las funciones. No obstante, todos los navegadores y motores de Javascript han realizado una implementación muy similar, aunque con mínimas variaciones.

Recientemente se ha propuesto una revisión de la estandarización de toString() para que no tenga ambigüedades, esta propuesta se encuentra en estos momentos en Stage 4, es decir, preparado para formar parte del estándar en la próxima edición. Por ello podemos estar bastante tranquilos en su utilización.

Aunque pueda parecer una solución un poco indirecta, lo cierto es que funciona. Podemos estar tranquilos al consultar el código de la función para analizar su tipología. Este es un recurso que tenemos a nuestra disposición y con la estandarización de facto y la nueva revisión de la estandarización formal de ECMAScript es seguro utilizarlo.

Novedades

Limitar el tamaño de un Map

Limitar el tamaño de un objeto Map no parece una idea muy razonable, pero cuando tu programa se ejecuta sin interrupción durante días, semanas, meses e inclusos años, es muy importante controlar el tamaño de la memoria utilizada para evitar problemas inesperados. Una simple función memoize puede llegar a almacenar mucha más información de la que puedes pensar. Aquí te contamos como limitar el tamaño de un objeto Map para estas situaciones.

Cómo conseguir un objeto Map ordenado

Mantener un objeto Map con su contenido ordenado no es algo tan sencillo como parece. Por defecto, Map guarda los datos en el mismo orden en el que han sido creados en el objeto. Para conseguir que el contenido se muestre ordenado tendremos que explorar varias interesantes alternativas que nos descubrirán algunas de características interesantes de estas estructuras de datos.