español ejemplos ecmascript descargar definicion caracteristicas alternativa javascript prototype prototypal-inheritance

javascript - ejemplos - jquery



¿Por qué extender los objetos nativos es una mala práctica? (7)

Todos los líderes de opinión de JS dicen que extender los objetos nativos es una mala práctica. ¿Pero por qué? ¿Tenemos un golpe de rendimiento? ¿Temen que alguien lo haga "de la manera incorrecta" y agrega tipos enumerables a Object , prácticamente destruyendo todos los bucles de cualquier objeto?

Tome should.js de TJ Holowaychuk , por ejemplo. Agrega un getter simple a Object y todo funciona bien ( source ).

Object.defineProperty(Object.prototype, ''should'', { set: function(){}, get: function(){ return new Assertion(Object(this).valueOf()); }, configurable: true });

Esto realmente tiene sentido. Por ejemplo, uno podría extender Array .

Array.defineProperty(Array.prototype, "remove", { set: function(){}, get: function(){ return removeArrayElement.bind(this); } }); var arr = [0, 1, 2, 3, 4]; arr.remove(3);

¿Hay algún argumento en contra de extender los tipos nativos?


Si lo observa caso por caso, quizás algunas implementaciones sean aceptables.

String.prototype.slice = function slice( me ){ return me; }; // Definite risk.

La sobrescritura de métodos ya creados crea más problemas de los que soluciona, por lo que se afirma comúnmente, en muchos lenguajes de programación, evitar esta práctica. ¿Cómo se han cambiado los desarrolladores para conocer la función?

String.prototype.capitalize = function capitalize(){ return this.charAt(0).toUpperCase() + this.slice(1); }; // A little less risk.

En este caso, no estamos sobrescribiendo ningún método core JS conocido, pero estamos ampliando String. Un argumento en este post menciona cómo es el nuevo desarrollador saber si este método es parte del JS básico o dónde encontrar los documentos. ¿Qué pasaría si el objeto core String JS fuera a obtener un método llamado capitalize ?

¿Qué ocurre si en lugar de agregar nombres que pueden colisionar con otras bibliotecas, utilizaste un modificador específico de la compañía / aplicación que todos los desarrolladores pudieran entender?

String.prototype.weCapitalize = function weCapitalize(){ return this.charAt(0).toUpperCase() + this.slice(1); }; // marginal risk. var myString = "hello to you."; myString.weCapitalize(); // => Hello to you.

Si continúas extendiendo otros objetos, todos los desarrolladores los encontrarían en la naturaleza con (en este caso) nosotros , que les notificaría que era una extensión específica de la compañía / aplicación.

Esto no elimina las colisiones de nombres, pero reduce la posibilidad. Si determina que la extensión de objetos JS básicos es para usted y / o para su equipo, quizás esto sea para usted.


Cuando extiendes un objeto, cambias su comportamiento.

Cambiar el comportamiento de un objeto que solo será utilizado por su propio código está bien. Pero cuando cambia el comportamiento de algo que también es usado por otro código existe el riesgo de que rompa ese otro código.

Cuando se trata de agregar métodos al objeto y clases de matriz en javascript, el riesgo de romper algo es muy alto, debido a cómo funciona JavaScript. Largos años de experiencia me han enseñado que este tipo de cosas causa todo tipo de errores terribles en javascript.

Si necesita un comportamiento personalizado, es mucho mejor definir su propia clase (quizás una subclase) en lugar de cambiar una nativa. De esa forma no romperás nada en absoluto.

La capacidad de cambiar la forma en que funciona una clase sin crear subclases es una característica importante de cualquier buen lenguaje de programación, pero es una que se debe usar de forma ocasional y con precaución.


En mi opinión, es una mala práctica. La razón principal es la integración. Citando documentos de should.js:

OMG IT EXTENDS OBJECT ???!?! @ Sí, sí, con un único getter debería, y no, no romperá tu código

Bueno, ¿cómo puede saber el autor? ¿Qué pasa si mi marco burlón hace lo mismo? ¿Qué pasa si mi promesa lib hace lo mismo?

Si lo haces en tu propio proyecto, está bien. Pero para una biblioteca, entonces es un mal diseño. Underscore.js es un ejemplo de lo que se hace de la manera correcta:

var arr = []; _(arr).flatten() // or: _.flatten(arr) // NOT: arr.flatten()


No hay un inconveniente mensurable, como un golpe de rendimiento. Al menos nadie mencionó ninguno. Entonces esta es una cuestión de preferencias personales y experiencias.

El principal argumento pro: se ve mejor y es más intuitivo: azúcar sintáctica. Es una función específica de tipo / instancia, por lo que debe estar específicamente vinculada a ese tipo / instancia.

El argumento principal de la contra: el código puede interferir. Si lib A agrega una función, podría sobrescribir la función de lib B. Esto puede romper el código muy fácilmente.

Ambos tienen un punto. Cuando dependes de dos bibliotecas que cambian directamente tus tipos, lo más probable es que termines con código roto, ya que la funcionalidad esperada probablemente no sea la misma. Estoy totalmente de acuerdo en eso. Las macrobibliotecas no deben manipular los tipos nativos. De lo contrario, como desarrollador, nunca sabrá lo que sucede realmente detrás de las escenas.

Y esa es la razón por la que no me gustan las libs como jQuery, guión bajo, etc. No me malinterpreten; están absolutamente bien programados y funcionan como un encanto, pero son grandes . Usas solo el 10% de ellos, y entiendes aproximadamente el 1%.

Es por eso que prefiero un enfoque atomista , donde solo requieres lo que realmente necesitas. De esta manera, siempre sabes lo que sucede. Las microbibliotecas solo hacen lo que usted quiere que hagan, para que no interfieran. En el contexto de que el usuario final sepa qué características se agregan, extender los tipos nativos puede considerarse seguro.

TL; DR En caso de duda, no extienda los tipos nativos. Solo extienda un tipo nativo si está 100% seguro de que el usuario final sabrá y desea ese comportamiento. En ningún caso manipule las funciones existentes de un tipo nativo, ya que rompería la interfaz existente.

Si decide extender el tipo, use Object.defineProperty(obj, prop, desc) ; si no puede , use el prototype del tipo.

Originalmente se me ocurrió esta pregunta porque quería que el Error s se pueda enviar a través de JSON. Por lo tanto, necesitaba una forma de stringify ellos. error.stringify() sintió mucho mejor que errorlib.stringify(error) ; como sugiere el segundo constructo, estoy operando en errorlib y no en error sí mismo.


Puedo ver tres razones para no hacer esto (desde dentro de una aplicación , al menos), de las cuales solo dos se abordan en las respuestas existentes aquí:

  1. Si lo haces mal, accidentalmente agregarás una propiedad enumerable a todos los objetos del tipo extendido. Se trabajó fácilmente con Object.defineProperty , que crea propiedades no enumerables de manera predeterminada.
  2. Puede causar un conflicto con una biblioteca que está utilizando. Se puede evitar con diligencia; simplemente compruebe qué métodos definen las bibliotecas que usa antes de agregar algo a un prototipo, revise las notas de la versión al actualizar y pruebe su aplicación.
  3. Puede causar un conflicto con una versión futura del entorno JavaScript nativo.

El punto 3 es posiblemente el más importante. Puede asegurarse, a través de pruebas, de que las extensiones de su prototipo no causen ningún conflicto con las bibliotecas que usa, porque usted decide qué bibliotecas usa. Lo mismo no es cierto para los objetos nativos, suponiendo que su código se ejecute en un navegador. Si define Array.prototype.swizzle(foo, bar) hoy, y mañana Google agrega Array.prototype.swizzle(bar, foo) a Chrome, es probable que termine con algunos colegas confundidos que se preguntan por qué .swizzle ''s el comportamiento no parece coincidir con lo documentado en MDN.

(Ver también la historia de cómo los mootools ''jugaron con prototipos que no poseían obligaron a renombrar un método ES6 para evitar romper la web .)

Esto se puede evitar mediante el uso de un prefijo específico de la aplicación para los métodos añadidos a objetos nativos (por ejemplo, definir Array.prototype.myappSwizzle lugar de Array.prototype.swizzle ), pero eso es feo; es tan fácil de resolver mediante el uso de funciones de utilidad independientes en lugar de aumentar los prototipos.


Una razón más por la que no deberías extender los objetos nativos:

Usamos Magento que usa prototype.js y extiende muchas cosas en Objetos nativos. Esto funciona bien hasta que decida obtener nuevas funciones y ahí es donde comienzan los grandes problemas.

Hemos introducido Webcomponents en una de nuestras páginas, por lo que webcomponents-lite.js decide reemplazar el objeto de evento completo (nativo) en IE (¿por qué?). Esto por supuesto rompe prototype.js que a su vez rompe Magento. (hasta que encuentre el problema, puede invertir muchas horas remontándolo)

Si te gustan los problemas, ¡sigue haciéndolo!


Extender prototipos de complementos es una mala idea. Sin embargo, ES2015 introdujo una nueva técnica que puede utilizarse para obtener el comportamiento deseado:

Utilizando WeakMap s para asociar tipos con prototipos incorporados

La siguiente implementación amplía los prototipos Number y Array sin tocarlos en absoluto:

// new types const AddMonoid = { empty: () => 0, concat: (x, y) => x + y, }; const ArrayMonoid = { empty: () => [], concat: (acc, x) => acc.concat(x), }; const ArrayFold = { reduce: xs => xs.reduce( type(xs[0]).monoid.concat, type(xs[0]).monoid.empty() )}; // the WeakMap that associates types to prototpyes types = new WeakMap(); types.set(Number.prototype, { monoid: AddMonoid }); types.set(Array.prototype, { monoid: ArrayMonoid, fold: ArrayFold }); // auxiliary helpers to apply functions of the extended prototypes const genericType = map => o => map.get(o.constructor.prototype); const type = genericType(types); // mock data xs = [1,2,3,4,5]; ys = [[1],[2],[3],[4],[5]]; // and run console.log("reducing an Array of Numbers:", ArrayFold.reduce(xs) ); console.log("reducing an Array of Arrays:", ArrayFold.reduce(ys) ); console.log("built-ins are unmodified:", Array.prototype.empty);

Como puede ver, incluso prototipos primitivos pueden ampliarse con esta técnica. Utiliza una estructura de mapa y una identidad de Object para asociar tipos con prototipos incorporados.

Mi ejemplo permite una función de reduce que solo espera una Array como único argumento, ya que puede extraer la información sobre cómo crear un acumulador vacío y cómo concatenar elementos con este acumulador a partir de los elementos de la matriz misma.

Tenga en cuenta que podría haber usado el tipo de Map normal, ya que las referencias débiles no tienen sentido cuando simplemente representan prototipos incorporados, que nunca son basura. Sin embargo, un WeakMap no es iterable y no puede inspeccionarse a menos que tenga la clave correcta. Esta es una característica deseada, ya que quiero evitar cualquier tipo de reflejo de tipo.