javascript - cluster - nodejs multithread
Ampliación de tipos de núcleo sin modificar prototipo. (5)
¿Cómo se pueden extender los tipos de JavaScript principales (Cadena, Fecha, etc.) sin modificar sus prototipos? Por ejemplo, supongamos que quisiera crear una clase de cadena derivada con algunos métodos prácticos:
function MyString() { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
return this.split('''').reverse().join('''');
};
var s = new MyString("Foobar"); // Hmm, where do we use the argument?
s.reverse();
// Chrome - TypeError: String.prototype.toString is not generic
// Firefox - TypeError: String.prototype.toString called on incompatible Object
El error parece originarse en los métodos de base de cadena, probablemente "divididos" en este caso, ya que sus métodos se aplican a algún objeto que no es de cadena. Pero si no podemos aplicar los objetos sin cadena, ¿podemos realmente reutilizarlos automáticamente?
[Editar]
Obviamente, mi intento es defectuoso en muchos aspectos, pero creo que demuestra mi intención. Después de pensar un poco, parece que no podemos reutilizar ninguna de las funciones del objeto prototipo String sin llamarlos explícitamente en un String.
¿Es posible extender los tipos de núcleo como tales?
Creo que la respuesta básica es que probablemente no puedas. Lo que puedes hacer es lo que hace Sugar.js: crea un objeto similar a un objeto y extiéndelo a partir de eso:
Sugar.js tiene que ver con las extensiones de objetos nativos, y no extienden Object.prototype.
En primer lugar, en este código:
MyString.prototype = String.prototype;
MyString.prototype.reverse = function() {
this.split('''').reverse().join('''');
};
Las variables MyString.prototype
y String.prototype
están haciendo referencia al mismo objeto. Asignar a uno es asignar a otro. Cuando soltó un método reverse
en MyString.prototype
también lo estaba escribiendo en String.prototype
. Así que prueba esto:
MyString.prototype = String.prototype;
MyString.prototype.charAt = function () {alert("Haha");}
var s = new MyString();
s.charAt(4);
"dog".charAt(3);
Las dos últimas líneas ambas alertan porque sus prototipos son el mismo objeto. Realmente String.prototype
.
Ahora sobre tu error. MyString
reverse
en tu objeto MyString
. ¿Dónde se define este método? En el prototipo, que es lo mismo que String.prototype
. Usted sobrescribió el reverse
. ¿Qué es lo primero que hace? Se llama a split
en el objeto de destino. Ahora la cosa es que, para que String.prototype.split
funcione, tiene que llamar String.prototype.toString
. Por ejemplo:
var s = new MyString();
if (s.split("")) {alert("Hi");}
Este código genera un error:
TypeError: String.prototype.toString is not generic
Lo que esto significa es que String.prototype.toString
usa la representación interna de una cadena para hacer lo suyo (es decir, devolviendo su cadena primitiva interna), y no se puede aplicar a objetos de destino arbitrarios que comparten el prototipo de cadena . Entonces, cuando llamó a split
, la implementación de split dijo "oh, mi objetivo no es una cadena, déjeme llamar a toString
", pero luego toString
dijo "mi objetivo no es una cadena y no soy genérico", así que lanzó el TypeError
.
Si desea obtener más información sobre los genéricos en JavaScript, puede ver esta sección de MDN en Genéricos de matrices y cadenas .
En cuanto a que esto funcione sin el error, vea la respuesta de Alxandr.
En cuanto a la extensión de los tipos incorporados exactos como String
y Date
etc. sin cambiar sus prototipos, realmente no lo hace, sin crear envolturas, delegados o subclases. Pero entonces esto no permitirá la sintaxis como
d1.itervalTo(d2)
donde d1
y d2
son instancias de la clase Date
incorporada cuyo prototipo no extendió . :-) JavaScript usa cadenas de prototipos para este tipo de sintaxis de llamadas a métodos. Simplemente lo hace. Excelente pregunta, sin embargo ... pero, ¿es esto lo que tenías en mente?
Solo tienes una parte equivocada aquí. MyString.prototype no debería ser String.prototype, debería ser así:
function MyString(s) { }
MyString.prototype = new String();
MyString.prototype.reverse = function() {
this.split('''').reverse().join('''');
};
var s = new MyString("Foobar");
s.reverse();
[Editar]
Para responder mejor a tu pregunta, no, no debería ser posible. Si echa un vistazo a esto: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/constructor explica que no puede cambiar el tipo en bools, ints y cadenas, por lo que no pueden ser "subclasificado".
2 años después: mutar cualquier cosa en el ámbito global es una idea terrible.
Original:
Al estar algo "mal" con la extensión de los prototipos nativos, está el FUD en los navegadores ES5.
Object.defineProperty(String.prototype, "my_method", {
value: function _my_method() { ... },
configurable: true,
enumerable: false,
writeable: true
});
Sin embargo, si tiene que admitir navegadores ES3, entonces hay problemas con las personas que usan for ... in
loops en cadenas.
Mi opinión es que puede cambiar los prototipos nativos y debería dejar de usar cualquier código mal escrito que se rompa
Actualización: incluso este código no extiende completamente el tipo de String
nativo (la propiedad de length
no funciona).
Imo probablemente no vale la pena seguir este enfoque. Hay demasiadas cosas que considerar y tiene que invertir demasiado tiempo para garantizar que funcione completamente ( si es que lo hace). @Raynos proporciona otro enfoque interesante .
Sin embargo aquí está la idea:
Parece que no puede llamar String.prototype.toString
en otra cosa que no sea una cadena real. Podrías anular este método:
// constructor
function MyString(s) {
String.call(this, s); // call the "parent" constructor
this.s_ = s;
}
// create a new empty prototype to *not* override the original one
tmp = function(){};
tmp.prototype = String.prototype;
MyString.prototype = new tmp();
MyString.prototype.constructor = MyString;
// new method
MyString.prototype.reverse = function() {
return this.split('''').reverse().join('''');
};
// override
MyString.prototype.toString = function() {
return this.s_;
};
MyString.prototype.valueOf = function() {
return this.s_;
};
var s = new MyString("Foobar");
alert(s.reverse());
Como ve, también tuve que anular valueOf
para que funcione.
Pero: no sé si estos son los únicos métodos que tiene que anular y para otros tipos incorporados es posible que tenga que anular otros métodos. Un buen comienzo sería tomar la especificación ECMAScript y echar un vistazo a la especificación de los métodos.
Por ejemplo, el segundo paso en el algoritmo String.prototype.split
es:
Sea S el resultado de llamar a ToString, dándole el valor de este como su argumento.
Si un objeto se pasa a ToString
, entonces básicamente llama al método toString
de este objeto. Y es por eso que funciona cuando anulamos toString
.
Actualización: Lo que no funciona es de s.length
. Entonces, aunque podría hacer que los métodos funcionen, otras propiedades parecen ser más difíciles.