tipos - llamar javascript desde html
El alias de la función de JavaScript no parece funcionar (6)
Estaba leyendo esta pregunta y quería probar el método de alias en lugar del método de función-envoltura, pero parecía que no podía hacerlo funcionar en Firefox 3 o 3.5beta4, o Google Chrome, tanto en sus ventanas de depuración y en una página web de prueba.
Firebug:
>>> window.myAlias = document.getElementById
function()
>>> myAlias(''item1'')
>>> window.myAlias(''item1'')
>>> document.getElementById(''item1'')
<div id="item1">
Si lo pongo en una página web, la llamada a myAlias me da este error:
uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///[...snip...]/test.html :: <TOP_LEVEL> :: line 7" data: no]
Chrome (con >>> insertado para mayor claridad):
>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias(''item1'')
TypeError: Illegal invocation
>>> document.getElementById(''item1'')
<div id=?"item1">?
Y en la página de prueba, recibo la misma "invocación ilegal".
¿Estoy haciendo algo mal? ¿Alguien más puede reproducir esto?
Además, curiosamente, lo intenté y funciona en IE8.
Además de otras excelentes respuestas, hay un método jQuery simple $.proxy .
Puedes alias así:
myAlias = $.proxy(document, ''getElementById'');
O
myAlias = $.proxy(document.getElementById, document);
Debe vincular ese método al objeto del documento. Mira:
>>> $ = document.getElementById
getElementById()
>>> $(''bn_home'')
[Exception... "Cannot modify properties of a WrappedNative" ... anonymous :: line 72 data: no]
>>> $.call(document, ''bn_home'')
<body id="bn_home" onload="init();">
Cuando está haciendo un alias simple, la función se invoca en el objeto global, no en el objeto del documento. Use una técnica llamada cierres para arreglar esto:
function makeAlias(object, name) {
var fn = object ? object[name] : null;
if (typeof fn == ''undefined'') return function () {}
return function () {
return fn.apply(object, arguments)
}
}
$ = makeAlias(document, ''getElementById'');
>>> $(''bn_home'')
<body id="bn_home" onload="init();">
De esta forma, no perderá la referencia al objeto original.
En 2012, existe el nuevo método de vinculación de ES5 que nos permite hacer esto de una manera más elegante:
>>> $ = document.getElementById.bind(document)
>>> $(''bn_home'')
<body id="bn_home" onload="init();">
En realidad , no puede "alias puro" una función en un objeto predefinido. Por lo tanto, lo más parecido al aliasing que puede obtener sin envolverse es permanecer dentro del mismo objeto:
>>> document.s = document.getElementById;
>>> document.s(''myid'');
<div id="myid">
Esta es una respuesta corta.
Lo siguiente hace una copia de (una referencia a) la función. El problema es que ahora la función está en el objeto window
cuando fue diseñado para vivir en el objeto del document
.
window.myAlias = document.getElementById
Las alternativas son
- usar una envoltura (ya mencionado por Fabien Ménager)
o puede usar dos alias.
window.d = document // A renamed reference to the object window.d.myAlias = window.d.getElementById
Otra respuesta corta, solo para envolver / aliasing console.log
y métodos de registro similares. Todos esperan estar en el contexto de la console
.
Esto se puede usar al envolver console.log
con algunos errores, en caso de que usted o sus usuarios tengan problemas al usar un navegador que no (siempre) lo admite . Sin embargo, esta no es una solución completa para ese problema, ya que es necesario ampliar los controles y una alternativa: su kilometraje puede variar.
Ejemplo de uso de advertencias
var warn = function(){ console.warn.apply(console, arguments); }
Entonces úsalo como de costumbre
warn("I need to debug a number and an object", 9999, { "user" : "Joel" });
Si prefiere ver sus argumentos de registro envueltos en una matriz (lo hago, la mayoría de las veces), sustituya .apply(...)
con .call(...)
.
Debería funcionar con console.log()
, console.debug()
, console.info()
, console.warn()
, console.error()
. Ver también console
en MDN .
console firebug
Profundicé para entender este comportamiento particular y creo que encontré una buena explicación.
Antes de acceder a por qué no puede alias document.getElementById
, intentaré explicar cómo funcionan los objetos / funciones de JavaScript.
Siempre que invoque una función de JavaScript, el intérprete de JavaScript determina un alcance y lo pasa a la función.
Considera seguir la función:
function sum(a, b)
{
return a + b;
}
sum(10, 20); // returns 30;
Esta función se declara en el ámbito de la ventana y cuando se invoca, el valor de this
dentro de la función suma será el objeto de Window
global.
Para la función ''suma'', no importa cuál sea el valor de ''esto'' ya que no lo está usando.
Considera seguir la función:
function Person(birthDate)
{
this.birthDate = birthDate;
this.getAge = function() { return new Date().getFullYear() - this.birthDate.getFullYear(); };
}
var dave = new Person(new Date(1909, 1, 1));
dave.getAge(); //returns 100.
Cuando llamas a la función dave.getAge, el intérprete de JavaScript ve que estás llamando a la función getAge en el objeto dave
, por lo que establece this
para getAge
función getAge
. getAge()
devolverá 100
correctamente.
Puede saber que en JavaScript puede especificar el alcance utilizando el método apply
. Probemos eso.
var dave = new Person(new Date(1909, 1, 1)); //Age 100 in 2009
var bob = new Person(new Date(1809, 1, 1)); //Age 200 in 2009
dave.getAge.apply(bob); //returns 200.
En la línea anterior, en lugar de dejar que JavaScript decida el alcance, está pasando el alcance manualmente como el objeto bob
. getAge
ahora devolverá 200
aunque haya pensado que ha llamado getAge
al objeto dave
.
¿Cuál es el punto de todo lo anterior? Las funciones están "sueltas" unidas a sus objetos de JavaScript. Por ejemplo, puedes hacer
var dave = new Person(new Date(1909, 1, 1));
var bob = new Person(new Date(1809, 1, 1));
bob.getAge = function() { return -1; };
bob.getAge(); //returns -1
dave.getAge(); //returns 100
Tomemos el siguiente paso.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge(); //returns 100;
ageMethod(); //returns ?????
ageMethod
ejecución de ageMethod
arroja un error! ¿Que pasó?
Si lee cuidadosamente los puntos anteriores, dave.getAge
que el método dave.getAge
se dave.getAge
con dave
como this
objeto, mientras que JavaScript no pudo determinar el ''alcance'' para la ejecución de ageMethod
. Pasó la ''Ventana'' global como ''esto''. Ahora, como window
no tiene una propiedad ageMethod
, la ejecución de ageMethod
fallará.
¿Cómo arreglar esto? Sencillo,
ageMethod.apply(dave); //returns 100.
¿Todo lo anterior tiene sentido? Si lo hace, podrá explicar por qué no puede alias document.getElementById
:
var $ = document.getElementById;
$(''someElement'');
$
se llama con una window
como this
y si la implementación getElementById
espera que this
sea un document
, fallará.
Nuevamente para arreglar esto, puedes hacer
$.apply(document, [''someElement'']);
Entonces, ¿por qué funciona en Internet Explorer?
No conozco la implementación interna de getElementById
en IE, pero un comentario en jQuery source (en la implementación del método inArray
) dice que en IE, window == document
. Si ese es el caso, entonces aliasing document.getElementById
debería funcionar en IE.
Para ilustrar esto más a fondo, he creado un ejemplo elaborado. Echa un vistazo a la función Person
continuación.
function Person(birthDate)
{
var self = this;
this.birthDate = birthDate;
this.getAge = function()
{
//Let''s make sure that getAge method was invoked
//with an object which was constructed from our Person function.
if(this.constructor == Person)
return new Date().getFullYear() - this.birthDate.getFullYear();
else
return -1;
};
//Smarter version of getAge function, it will always refer to the object
//it was created with.
this.getAgeSmarter = function()
{
return self.getAge();
};
//Smartest version of getAge function.
//It will try to use the most appropriate scope.
this.getAgeSmartest = function()
{
var scope = this.constructor == Person ? this : self;
return scope.getAge();
};
}
Para la función Person
anterior, así es como se comportarán los diversos métodos getAge
.
Vamos a crear dos objetos usando la función Person
.
var yogi = new Person(new Date(1909, 1,1)); //Age is 100
var anotherYogi = new Person(new Date(1809, 1, 1)); //Age is 200
console.log(yogi.getAge()); //Output: 100.
En línea recta, el método getAge obtiene el objeto yogi
como this
y emite 100
.
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
JavaScript interepreter establece el objeto window
como this
y nuestro método getAge
devolverá -1
.
console.log(ageAlias.apply(yogi)); //Output: 100
Si establecemos el alcance correcto, puede usar el método ageAlias
.
console.log(ageAlias.apply(anotherYogi)); //Output: 200
Si pasamos en algún otro objeto de persona, todavía calculará la edad correctamente.
var ageSmarterAlias = yogi.getAgeSmarter;
console.log(ageSmarterAlias()); //Output: 100
La función ageSmarter
capturó el original de this
objeto, por lo que ahora no tiene que preocuparse por el alcance correcto.
console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
El problema con ageSmarter
es que nunca puedes establecer el alcance para algún otro objeto.
var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
La función ageSmartest
utilizará el alcance original si se proporciona un alcance no válido.
console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
Aún podrá pasar otro objeto Person
para getAgeSmartest
. :)