¿Cómo configurar el prototipo de un objeto JavaScript que ya ha sido instanciado?


106

Supongamos que tengo un objeto fooen mi código JavaScript. fooes un objeto complejo y se genera en otro lugar. ¿Cómo puedo cambiar el prototipo del fooobjeto?

Mi motivación es establecer prototipos apropiados para objetos serializados de .NET a literales de JavaScript.

Suponga que he escrito el siguiente código JavaScript dentro de una página ASP.NET.

var foo = <%=MyData %>;

Supongamos que MyDataes el resultado de invocar .NET JavaScriptSerializeren un Dictionary<string,string>objeto.

En tiempo de ejecución, esto se convierte en lo siguiente:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

Como puede ver, se fooconvierte en una matriz de objetos. Me gustaría poder inicializar foocon un prototipo apropiado. Yo no quiero modificar el Object.prototypeni Array.prototype. ¿Cómo puedo hacer esto?


¿Desea agregarlo a su prototipo existente o cambiarlo por un nuevo prototipo?
SLaks

¿Te refieres a realizar cambios en el prototipo, o realmente cambiar el prototipo como en cambiar un prototipo y reemplazarlo por otro? Ni siquiera estoy seguro de que el último caso sea posible.
James Gaunt

2
¿Te refieres a la propiedad de prototipo explícita o al enlace de prototipo implícito ? (Esas dos son dos cosas muy diferentes)
Šime Vidas

Originalmente me preocupaba esto: stackoverflow.com/questions/7013545/…
Vivian River

1
¿Está familiarizado con Backbone extendo Google goog.inherit? Muchos desarrolladores proporcionan formas de generar herencia antes de llamar al newconstructor de edad avanzada , que es antes de que nos lo dieran Object.createy no teníamos que preocuparnos por anular Object.prototype.
Ryan

Respuestas:


114

EDITAR Febrero de 2012: la respuesta a continuación ya no es precisa. __proto__ se está agregando a ECMAScript 6 como "normativo opcional", lo que significa que no se requiere que se implemente, pero si lo está, debe seguir el conjunto de reglas dado. Esto no está resuelto actualmente pero al menos será oficialmente parte de la especificación de JavaScript.

Esta pregunta es mucho más complicada de lo que parece en la superficie, y más allá del nivel de pago de la mayoría de las personas en lo que respecta al conocimiento de los componentes internos de Javascript.

La prototypepropiedad de un objeto se utiliza al crear nuevos objetos secundarios de ese objeto. Cambiarlo no se refleja en el objeto en sí, sino que se refleja cuando ese objeto se utiliza como constructor de otros objetos y no tiene ninguna utilidad para cambiar el prototipo de un objeto existente.

function myFactory(){};
myFactory.prototype = someOtherObject;

var newChild = new myFactory;
newChild.__proto__ === myFactory.prototype === someOtherObject; //true

Los objetos tienen una propiedad [[prototype]] interna que apunta al prototipo actual. La forma en que funciona es cada vez que se llama a una propiedad en un objeto, comenzará en el objeto y luego irá hacia arriba a través de la cadena [[prototype]] hasta que encuentre una coincidencia, o falle, después del prototipo del objeto raíz. Así es como Javascript permite la construcción y modificación de objetos en tiempo de ejecución; tiene un plan para buscar lo que necesita.

La __proto__propiedad existe en algunas implementaciones (muchas ahora): cualquier implementación de Mozilla, todas las webkit que conozco, algunas otras. Esta propiedad apunta a la propiedad interna [[prototype]] y permite la modificación posterior a la creación en los objetos. Todas las propiedades y funciones cambiarán instantáneamente para coincidir con el prototipo debido a esta búsqueda encadenada.

Esta característica, aunque está estandarizada ahora, todavía no es una parte requerida de JavaScript, y en los lenguajes que la soportan tiene una alta probabilidad de derribar su código en la categoría "no optimizado". Los motores JS deben hacer todo lo posible para clasificar el código, especialmente el código "activo" al que se accede con mucha frecuencia, y si está haciendo algo elegante como modificar__proto__ , no optimizarán su código en absoluto.

Esta publicación https://bugzilla.mozilla.org/show_bug.cgi?id=607863 analiza específicamente las implementaciones actuales __proto__y las diferencias entre ellas. Cada implementación lo hace de manera diferente, porque es un problema difícil y sin resolver. Todo en Javascript es mutable, excepto a.) La sintaxis b.) Objetos de host (el DOM existe técnicamente fuera de Javascript) y c.)__proto__ . El resto está completamente en manos de usted y de cualquier otro desarrollador, por lo que puede ver por qué __proto__sobresale como un pulgar dolorido.

Hay una cosa que __proto__permite que de otro modo sea imposible de hacer: la designación de un prototipo de objetos en tiempo de ejecución separado de su constructor. Este es un caso de uso importante y es una de las razones principales por las __proto__que aún no está muerto. Es bastante importante que haya sido un punto de discusión serio en la formulación de Harmony, o que pronto se conocerá como ECMAScript 6. La capacidad de especificar el prototipo de un objeto durante la creación será parte de la próxima versión de Javascript y esto será la campana que indica __proto__los días está formalmente numerada.

A corto plazo, puede usarlo __proto__si se dirige a navegadores que lo admitan (no IE, y ningún IE lo hará). Es probable que funcione en webkit y moz durante los próximos 10 años, ya que ES6 no se finalizará hasta 2013.

Brendan Eich - re: Enfoque de nuevos métodos de objetos en ES5 :

Lo siento, ... pero configurable __proto__, aparte del caso de uso del inicializador del objeto (es decir, en un nuevo objeto aún no accesible, análogo al Object.create de ES5), es una idea terrible. Escribo esto habiendo diseñado e implementado configurables hace __proto__más de 12 años.

... la falta de estratificación es un problema (considere los datos JSON con una clave "__proto__"). Y lo que es peor, la mutabilidad significa que las implementaciones deben verificar las cadenas de prototipos cíclicos para evitar el ilooping. [se requieren comprobaciones constantes para la recursividad infinita]

Por último, la mutación __proto__en un objeto existente puede romper métodos no genéricos en el nuevo objeto prototipo, que no puede funcionar en el objeto receptor (directo) que __proto__se está configurando. Esto es simplemente una mala práctica, una forma de confusión de tipo intencional, en general.


Excelente desglose! Si bien no es exactamente lo mismo que un prototipo mutable, ECMA Harmony probablemente implementará proxies , que le permitirán aplicar funciones adicionales a objetos particulares mediante un patrón general.
Nick Husher

2
El problema de Brendan Eich es nativo de los lenguajes de creación de prototipos en su conjunto. Posiblemente, hacer __proto__ non-writable, configurablesolucionaría estas preocupaciones al obligar al usuario a reconfigurar explícitamente la propiedad. Al final, las malas prácticas están asociadas con los abusos de las capacidades de un idioma, no con las capacidades en sí mismas. __Proto__ escribible no es inaudito. Hay muchas otras propiedades de escritura no enumerables y, si bien existen peligros, también existen las mejores prácticas. La cabeza de un martillo no debe quitarse simplemente porque se puede usar de manera incorrecta para herir a alguien.
Swivel

La mutación __proto__es necesaria, porque Object.create solo producirá Objetos, no funciones, por ejemplo, ni Símbolos, Regexes, elementos DOM u otros objetos host. Si desea que sus objetos sean invocables o especiales de otras maneras, pero aún así cambian su cadena de prototipos, está atascado sin configurables __proto__o Proxies
user2451227

2
Sin embargo, sería mejor si no se hiciera con una propiedad mágica, sino con Object.setPrototype, eliminando la __proto__preocupación " en JSON". Las otras preocupaciones de Brendan Eich son ridículas. Las cadenas de prototipos recursivas o el uso de un prototipo con métodos inadecuados para el objeto dado son errores del programador, no fallas de lenguaje y no deberían ser un factor, y además pueden ocurrir con Object.create tan bien como con libremente configurable __proto__.
user2451227

1
Compatible con IE 10 __proto__.
kzh


14

Puedes usar constructor en una instancia de un objeto para alterar el prototipo de un objeto in situ. Creo que esto es lo que estás pidiendo hacer.

Esto significa que si tiene foocuál es una instancia de Foo:

function Foo() {}

var foo = new Foo();

Puede agregar una propiedad bara todas las instancias de Foohaciendo lo siguiente:

foo.constructor.prototype.bar = "bar";

Aquí hay un violín que muestra la prueba de concepto: http://jsfiddle.net/C2cpw/ . No estoy muy seguro de cómo les irá a los navegadores más antiguos con este enfoque, pero estoy bastante seguro de que esto debería funcionar bastante bien.

Si su intención es mezclar la funcionalidad en objetos, este fragmento debería hacer el trabajo:

function mix() {
  var mixins = arguments,
      i = 0, len = mixins.length;

  return {
    into: function (target) {
      var mixin, key;

      if (target == null) {
        throw new TypeError("Cannot mix into null or undefined values.");
      }

      for (; i < len; i += 1) {
        mixin = mixins[i];
        for (key in mixin) {
          target[key] = mixin[key];
        }

        // Take care of IE clobbering `toString` and `valueOf`
        if (mixin && mixin.toString !== Object.prototype.toString) {
          target.toString = mixin.toString;
        } else if (mixin && mixin.valueOf !== Object.prototype.valueOf) {
          target.valueOf = mixin.valueOf;
        }
      }
      return target;
    }
  };
};

1
+1: Esto es informativo e interesante, pero realmente no me ayuda a conseguir lo que quiero. Estoy actualizando mi pregunta para ser más específica.
Vivian River

También es posible usarfoo.__proto__.bar = 'bar';
Jam Risser

9

Puede hacerlo foo.__proto__ = FooClass.prototype, AFAIK que es compatible con Firefox, Chrome y Safari. Tenga en cuenta que la __proto__propiedad no es estándar y podría desaparecer en algún momento.

Documentación: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . Consulte también http://www.mail-archive.com/jsmentors@googlegroups.com/msg00392.html para obtener una explicación de por qué no existe Object.setPrototypeOf()y por qué __proto__está obsoleto.


3

Puede definir su función de constructor de proxy y luego crear una nueva instancia y copiar todas las propiedades del objeto original en ella.

// your original object
var obj = { 'foo': true };

// your constructor - "the new prototype"
function Custom(obj) {
    for ( prop in obj ) {
        if ( obj.hasOwnProperty(prop) ) {
            this[prop] = obj[prop];
        }
    }
}

// the properties of the new prototype
Custom.prototype.bar = true;

// pass your original object into the constructor
var obj2 = new Custom(obj);

// the constructor instance contains all properties from the original 
// object and also all properties inherited by the new prototype
obj2.foo; // true
obj2.bar; // true

Demo en vivo: http://jsfiddle.net/6Xq3P/

El Customconstructor representa el nuevo prototipo, ergo, su Custom.prototypeobjeto contiene todas las nuevas propiedades que le gustaría usar con su objeto original.

Dentro del Customconstructor, simplemente copie todas las propiedades del objeto original al nuevo objeto de instancia.

Este nuevo objeto de instancia contiene todas las propiedades del objeto original (se copiaron en él dentro del constructor), y también todas las nuevas propiedades definidas adentro Custom.prototype(porque el nuevo objeto es una Custominstancia).


3

No puede cambiar el prototipo de un objeto JavaScript que ya ha sido instanciado en una forma de navegador cruzado. Como han mencionado otros, sus opciones incluyen:

  1. cambiar la __proto__propiedad de navegador cruzado / no estándar
  2. Copie las propiedades de los objetos a un nuevo objeto

Ninguno de los dos es particularmente bueno, especialmente si tiene que recorrer recursivamente un objeto hacia los objetos internos para cambiar efectivamente el prototipo completo de un elemento.

Solución alternativa a la pregunta

Voy a echar un vistazo más abstracto a la funcionalidad que parece que deseas.

Básicamente, los prototipos / métodos solo permiten una forma de agrupar funciones basadas en un objeto.
En lugar de escribir

function trim(x){ /* implementation */ }
trim('   test   ');

usted escribe

'   test  '.trim();

La sintaxis anterior se ha acuñado con el término OOP debido a la sintaxis object.method (). Algunas de las principales ventajas de OOP sobre la programación funcional tradicional incluyen:

  1. Nombres de métodos cortos y menos variables obj.replace('needle','replaced')frente a tener que recordar nombres como str_replace ( 'foo' , 'bar' , 'subject')y la ubicación de las diferentes variables
  2. método chaining ( string.trim().split().join()) es potencialmente más fácil de modificar y escribir que funciones anidadasjoin(split(trim(string))

Desafortunadamente, en JavaScript (como se muestra arriba) no se puede modificar un prototipo ya existente. Idealmente arriba, podría modificar Object.prototypesolo para el Objeto dado arriba, pero desafortunadamente modificar Object.prototypepodría romper los scripts (lo que resultaría en colisión de propiedades y anulación).

No existe un término medio de uso común entre estos 2 estilos de programación, ni una forma de programación orientada a objetos para organizar funciones personalizadas.

UnlimitJS proporciona un término medio que le permite definir métodos personalizados. Evita:

  1. Colisión de propiedad, porque no extiende los prototipos de Objects
  2. Todavía permite una sintaxis de encadenamiento OOP
  3. Es un script de 450 bytes entre navegadores (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) que es la mayor parte de los problemas de colisión de propiedades del prototipo de JavaScript de Unlimit

Usando su código anterior, simplemente crearía un espacio de nombres de funciones que pretende llamar contra el objeto.

Aquí hay un ejemplo:

var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];

// define namespace with methods
var $ = {
  log:function(){
    console.log(this);
    return this;
  }[Unlimit](),
  alert:function(){
    alert(''+this);
  }[Unlimit]()
}


foo[$.log]()
   [$.log]()
   [$.alert]();

Puede leer más ejemplos aquí UnlimitJS . Básicamente, cuando llama [Unlimit]()a una función, permite que la función se llame como un método en un objeto. Es como un término medio entre la POO y las carreteras funcionales.


2

No puede cambiar la [[prototype]]referencia de objetos ya construidos, hasta donde yo sé. Podría alterar la propiedad del prototipo de la función de constructor original pero, como ya ha comentado, ese constructor lo es Object, y alterar las construcciones centrales de JS es algo malo.

Sin embargo, puede crear un objeto proxy del objeto construido que implemente la funcionalidad adicional que necesita. También puede parchear los métodos y comportamientos adicionales asignándolos directamente al objeto en cuestión.

Tal vez puedas conseguir lo que quieres de otra manera, si estás dispuesto a abordar desde un ángulo diferente: ¿Qué necesitas hacer que implique jugar con el prototipo?


¿Alguien más puede comentar sobre la precisión de esto?
Vivian River

Parece, basado en este terrible jsfiddle, que puedes alterar el prototipo de un objeto existente alterando su __proto__propiedad. Esto solo funcionará en navegadores que admitan la __proto__notación, que es Chrome y Firefox, y ha quedado obsoleto . Entonces, en resumen, puede cambiar el [[prototype]]de un objeto, pero probablemente no debería hacerlo.
Nick Husher

1

Si conoce el prototipo, ¿por qué no inyectarlo en el código?

var foo = new MyPrototype(<%= MyData %>);

Entonces, una vez que se serializan los datos, obtienes

var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);

ahora solo necesita un constructor que tome una matriz como argumento.


0

No hay forma de heredar realmente de Array o "subclasificarlo".

Lo que puede hacer es esto ( ADVERTENCIA: CÓDIGO COMPLETO ADELANTE ):

function Foo(arr){
  [].push.apply(this, arr)
}
Foo.prototype = []
Foo.prototype.something = 123

var foo = new Foo(<%=MyData %>)

foo.length // => 2
foo[0] // => {"A":"1","B":"2"}
foo.something // => 123

Esto funciona, pero causará ciertos problemas a cualquiera que se cruce en su camino (parece una matriz, pero las cosas saldrán mal si intenta manipularlo).

¿Por qué no sigue la ruta sensata y agrega métodos / propiedades directamente foo, o usa un constructor y guarda su matriz como una propiedad?

function Foo(arr){
  this.items = arr
}
Foo.prototype = {
  someMethod : function(){ ... }
  //...
}

var foo = new Foo(<%=MyData %>)
foo.items // => [{"A":"1","B":"2"},{"X":"7","Y":"8"}]

0

Si desea crear un prototipo sobre la marcha, esta es una de las formas

function OntheFlyProto (info){
    this.items = info;
    this.y =-1;
    for(var i = 0; i < this.items.length ; i++){
        OntheFlyProto.prototype["get"+this.items[i].name] = function (){
            this.y++;
            return this.items[this.y].value;
        }
    }
}

var foo = [{name:"one", value:1},{name:"two", value:2}];
v = new OntheFlyProto(foo);

-1
foo.prototype.myFunction = function(){alert("me");}

No, no puedes. Esa es una propiedad estática.
SLaks

@SLaks prototypees estático? No estoy seguro de lo que quieres decir.
Šime Vidas

1
prototypees una propiedad de una función, no un objeto. Object.prototypeexiste; {}.prototypeno lo hace.
SLaks

Si no le importa la compatibilidad del navegador, puede usarlo Object.getPrototypeOf(foo)para obtener el objeto prototipo del constructor. Puede modificar las propiedades para modificar el prototipo del objeto. Solo funciona en navegadores recientes, sin embargo, no podría decirte cuáles.
Nick Husher

@TeslaNick: Podrías ir y modificar Object.prototypedirectamente porque es muy probable que eso sea lo Object.getPrototypeOf(foo)que regrese. No es muy útil.
Wladimir Palant
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.