Pregunta

Estaba leyendo esta pregunta y quería probar método alias en lugar del método de envoltura de funciones, pero parece que no pude hacerlo funcionar en Firefox 3 o 3.5beta4, o Google Chrome, tanto en sus ventanas de depuración como 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 > > > 's 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, obtengo la misma "invocación ilegal".

¿Estoy haciendo algo mal? ¿Alguien más puede reproducir esto?

Además, por extraño que parezca, acabo de intentarlo y funciona en IE8.

¿Fue útil?

Solución

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 llama 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 manera no perderá la referencia al objeto original.

En 2012, existe el nuevo método bind 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();">

Otros consejos

Busqué profundamente para comprender este comportamiento particular y creo que he encontrado una buena explicación.

Antes de explicar por qué no puede alias document.getElementById , intentaré explicar cómo funcionan las funciones / objetos de JavaScript.

Cada vez que invocas una función de JavaScript, el intérprete de JavaScript determina un alcance y lo pasa a la función.

Considere la siguiente 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 la invoque, el valor de this dentro de la función de suma será el objeto global Window .

Para la función 'suma', no importa cuál sea el valor de 'esto' ya que no la está usando.


Considere la siguiente 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 llama a la función dave.getAge, el intérprete de JavaScript ve que está llamando a la función getAge en el objeto dave , por lo que establece this en dave y llama a la función getAge . getAge () devolverá correctamente 100 .


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 'pensó' que llamó getAge en el objeto dave .


¿Cuál es el punto de todo lo anterior? Las funciones están "sueltas" adjuntas a sus objetos JavaScript. P.ej. 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

Vamos a dar el siguiente paso.

var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;

dave.getAge(); //returns 100;
ageMethod(); //returns ?????

ageMethod ¡la ejecución arroja un error! ¿Qué pasó?

Si lees cuidadosamente mis puntos anteriores, notarías que el método dave.getAge fue llamado con dave como objeto this mientras que JavaScript podría no determina el 'alcance' para la ejecución de ageMethod . Entonces pasó a la 'Ventana' global como 'esto'. Ahora como la ventana no tiene una propiedad birthDate , la ejecución de ageMethod fallará.

¿Cómo arreglar esto? Simple,

ageMethod.apply(dave); //returns 100.

¿Tenía sentido todo lo anterior? Si lo hace, podrá explicar por qué no puede alias document.getElementById :

var $ = document.getElementById;

$('someElement'); 

$ se llama con window como this y si la implementación getElementById espera this para ser 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 la fuente jQuery (implementación del método inArray ) dice que en IE, ventana == documento . Si ese es el caso, entonces aliasing document.getElementById debería funcionar en IE.

Para ilustrar esto más a fondo, he creado un elaborado ejemplo. Eche un vistazo a la función Persona a 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 Persona anterior, así es como se comportarán los diversos métodos getAge .

Creemos dos objetos usando la función Persona .

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.

Directamente, el método getAge obtiene el objeto yogi como this y genera 100 .


var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
El intérprete de

JavaScript 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 algún objeto de otra persona, seguirá calculando la edad correctamente.

var ageSmarterAlias = yogi.getAgeSmarter;    
console.log(ageSmarterAlias()); //Output: 100

La función ageSmarter capturó el objeto original this , por lo que ahora no tiene que preocuparse por proporcionar el alcance correcto.


console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!

El problema con ageSmarter es que nunca puede establecer el alcance en 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 Persona a getAgeSmartest . :)

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ñada para vivir en el objeto document .

window.myAlias = document.getElementById

Las alternativas son

  • usar un contenedor (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 / alias console.log y métodos de registro similares. Todos esperan estar en el contexto de console .

Esto se puede usar al envolver console.log con algunos fallos, en caso de que usted o sus usuarios hayan tenido problemas cuando usando un navegador que no (siempre) lo soporta . Sin embargo, esta no es una solución completa a ese problema, ya que debe ampliarse con cheques y un retroceso; su millaje puede variar.

Ejemplo con advertencias

var warn = function(){ console.warn.apply(console, arguments); }

Luego úsalo como siempre

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 () , consola. warn () , console.error () . Consulte también console en MDN .

Además de otras excelentes respuestas, hay un método jQuery simple $ .proxy .

Puede un alias como este:

myAlias = $.proxy(document, 'getElementById');

O

myAlias = $.proxy(document.getElementById, document);

En realidad no puede " alias puro " una función en un objeto predefinido. Por lo tanto, lo más cercano al alias que puede obtener sin envolver es permanecer dentro del mismo objeto:

>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top