objetos - ¿Cómo establecer el prototipo de un objeto JavaScript que ya ha sido instanciado?
prototype javascript ejemplos (11)
Supongamos que tengo un objeto foo
en mi código JavaScript. foo
es un objeto complejo y se genera en otro lugar. ¿Cómo puedo cambiar el prototipo del objeto foo
?
Mi motivación es establecer prototipos apropiados para objetos serializados de literales .NET a JavaScript.
Supongamos que he escrito el siguiente código JavaScript dentro de una página ASP.NET.
var foo = <%=MyData %>;
Supongamos que MyData
es el resultado de invocar .NET JavaScriptSerializer
en un objeto Dictionary<string,string>
.
En tiempo de ejecución, esto se convierte en lo siguiente:
var foo = [{"A":"1","B":"2"},{"X":"7","Y":"8"}];
Como puede ver, foo
convierte en una matriz de objetos. Me gustaría poder inicializar foo
con un prototipo apropiado. No quiero modificar Object.prototype
ni Array.prototype
. ¿Cómo puedo hacer esto?
ES6 finalmente especifica Object.setPrototypeOf (object, prototype) que ya está implementado en Chrome y Firefox.
No hay forma de realmente heredar de Array
o "subclase".
Lo que puede hacer es esto ( ADVERTENCIA: ADMINISTRAR EL CÓDIGO POR ADELANTADO ):
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 para cualquiera que cruce su camino (parece una matriz, pero las cosas saldrán mal si intenta manipularlo).
¿Por qué no recorre la ruta correcta y agrega métodos / propiedades directamente a foo
, o utiliza 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"}]
No puede cambiar el prototipo de un objeto JavaScript que ya se ha instanciado de forma cruzada. Como otros han mencionado, sus opciones incluyen:
- cambiando la propiedad
__proto__
no estándar / cross browser - Copie las propiedades de Objetos a un nuevo objeto
Ninguno de los dos es particularmente bueno, especialmente si tienes que recorrer recursivamente un objeto en objetos internos para cambiar efectivamente un prototipo completo de elementos.
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 acuñó con el término OOP debido a la sintaxis object.method (). Algunas de las principales ventajas de OOPs sobre la programación funcional tradicional incluyen:
- Nombres cortos de métodos y menos variables
obj.replace(''needle'',''replaced'')
versus tener que recordar nombres comostr_replace ( ''foo'' , ''bar'' , ''subject'')
y la ubicación de las diferentes variables - método de encadenamiento (
string.trim().split().join()
) es una función potencialmente más fácil de modificar y escribir y luegojoin(split(trim(string))
funcionesjoin(split(trim(string))
Desafortunadamente en JavaScript (como se muestra arriba) no se puede modificar un prototipo ya existente. De forma ideal, podría modificar Object.prototype
solo para el Objeto dado anterior, pero desafortunadamente la modificación de Object.prototype
potencialmente rompería las secuencias de comandos (lo que Object.prototype
colisión de propiedad y anulación).
No existe un término medio comúnmente utilizado entre estos 2 estilos de programación y ninguna forma de OOP para organizar funciones personalizadas.
UnlimitJS proporciona un término medio que le permite definir métodos personalizados. Evita:
- Choque de propiedad, porque no extiende prototipos de Objetos
- Todavía permite una sintaxis de encadenamiento OOP
- Se trata de un script de navegador cruzado de 450 bytes (IE6 +, Firefox 3.0 +, Chrome, Opera, Safari 3.0+) que muestra la gran cantidad de problemas de colisión de propiedad de JavaScript de Unlimit
Usando su código anterior, simplemente crearía un espacio de nombres de las funciones que pretende llamar en contra del 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]();
Puedes leer más de los ejemplos aquí UnlimitJS . Básicamente, al invocar [Unlimit]()
en una función, permite llamar a la función como método en un objeto. Es como un término medio entre el OOP y las carreteras funcionales.
No se puede cambiar la referencia [[prototype]]
de los objetos ya construidos, hasta donde yo sé. Podría alterar la propiedad del prototipo de la función constructora original pero, como ya ha comentado, ese constructor es un Object
, y la alteración de las construcciones del núcleo JS es una cosa mala.
Sin embargo, podría crear un objeto proxy del objeto construido que implemente la funcionalidad adicional que necesita. También puede poner en parche los métodos y comportamientos adicionales asignando directamente al objeto en cuestión.
Quizás puedas obtener lo que quieres de otra manera, si estás dispuesto a acercarte desde un ángulo diferente: ¿Qué necesitas hacer que involucre jugar con el prototipo?
Puede definir su función de constructor proxy y luego crear una nueva instancia y copiar todas las propiedades desde el objeto original a 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
Demostración en vivo: http://jsfiddle.net/6Xq3P/
El constructor Custom
representa el nuevo prototipo, ergo, su objeto Custom.prototype
contiene todas las nuevas propiedades que le gustaría usar con su objeto original.
Dentro del constructor Custom
, solo copia todas las propiedades desde el objeto original al nuevo objeto instancia.
Este nuevo objeto de instancia contiene todas las propiedades del objeto original (se copiaron dentro del constructor) y también todas las nuevas propiedades definidas dentro de Custom.prototype
(porque el nuevo objeto es una instancia Custom
).
Puede hacer foo.__proto__ = FooClass.prototype
, AFAIK compatible con Firefox, Chrome y Safari. Tenga en cuenta que la propiedad __proto__
no es estándar y puede desaparecer en algún momento.
Documentación: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/proto . También vea http://www.mail-archive.com/[email protected]/msg00392.html para obtener una explicación de por qué no hay Object.setPrototypeOf()
y por qué __proto__
está en desuso.
Puede 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 que hagas.
Esto significa que si tienes foo
que es una instancia de Foo
:
function Foo() {}
var foo = new Foo();
Puede agregar una bar
propiedades a todas las instancias de Foo
haciendo 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 va a pasar a los navegadores más antiguos usando este enfoque, pero estoy bastante seguro de que esto debería hacer el trabajo bastante bien.
Si su intención es mezclar 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;
}
};
};
Si conoce el prototipo, ¿por qué no inyectarlo en el código?
var foo = new MyPrototype(<%= MyData %>);
Entonces, una vez que los datos son serializados, obtienes
var foo = new MyPrototype([{"A":"1","B":"2"},{"X":"7","Y":"8"}]);
ahora solo necesitas un constructor que tome una matriz como argumento.
si quieres crear un prototipo sobre la marcha, este es uno de los caminos
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);
EDITAR Feb. 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 es necesario implementarlo, pero si lo es, 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 grado de pago de la mayoría de las personas en lo que respecta al conocimiento de las partes internas de Javascript.
La propiedad prototype
de un objeto se usa al crear nuevos objetos secundarios de ese objeto. Cambiarlo no se refleja en el objeto en sí, sino que se refleja cuando ese objetado se utiliza como un constructor para otros objetos, y no sirve 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 interna [[prototipo]] que apunta al prototipo actual. La forma en que funciona es siempre que se llame a una propiedad de un objeto, se iniciará en el objeto y luego irá por la cadena [[prototipo]] hasta que encuentre una coincidencia, o fallará después del prototipo raíz del Objeto. Así es como Javascript permite construir y modificar objetos en tiempo de ejecución; tiene un plan para buscar lo que necesita.
La propiedad __proto__
existe en algunas implementaciones (mucho ahora): cualquier implementación de Mozilla, todas las webkit que conozco, algunas otras. Esta propiedad apunta a la propiedad interna [[prototipo]] y permite la modificación posterior a la creación en los objetos. Cualquier propiedad y función cambiará instantáneamente para que coincida 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 idiomas que la soportan tiene una alta probabilidad de derribar su código en la categoría "no optimizada". Los motores JS deben hacer todo lo posible para clasificar el código, especialmente el código " __proto__
" al que se accede con mucha frecuencia, y si está haciendo algo como modificar __proto__
no optimizarán su código.
Esta publicación https://bugzilla.mozilla.org/show_bug.cgi?id=607863 trata específicamente las implementaciones actuales de __proto__
y las diferencias entre ellas. Cada implementación lo hace diferente, porque es un problema difícil de resolver. Todo en Javascript es mutable excepto a.) La sintaxis b.) Objetos de host (el DOM existe fuera de Javascript técnicamente) 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 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 que __proto__
no está muerto. Es lo suficientemente importante que ha 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á una parte de la próxima versión de Javascript y será la campana que indica los días de __proto__
está formalmente numerada.
En el corto plazo, puedes usar __proto__
si estás apuntando a navegadores que lo soporten (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 Objeto en ES5 :
Lo sentimos, pero el
__proto__
configurable, aparte del caso de uso del inicializador de objetos (es decir, en un objeto nuevo aún no accesible, análogo al Object.create de ES5), es una idea terrible. Escribo esto habiendo diseñado e implementado__proto__
configurable__proto__
más de 12 años.... la falta de estratificación es un problema (considere los datos JSON con la 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 controles constantes para la recursión infinita]Finalmente, mutar
__proto__
en un objeto existente puede romper métodos no genéricos en el nuevo objeto prototipo, que posiblemente no pueda funcionar en el objeto receptor (directo) cuyo__proto__
se está configurando. Esto es simplemente una mala práctica, una forma de confusión de tipo intencional, en general.
foo.prototype.myFunction = function(){alert("me");}