Наложение псевдонимов JavaScript, похоже, не работает
Я просто читал этот вопрос и хотел попробовать использовать метод псевдонима, а не метод функции-обертки, но я не мог заставить его работать либо Firefox 3, либо 3.5beta4, либо Google Chrome, как в их отладочных окнах, так и на тестовой веб-странице.
Firebug:
>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">
Если я помещаю его на веб-страницу, вызов myAlias дает мне эту ошибку:
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 (с добавлением → > для ясности):
>>> window.myAlias = document.getElementById
function getElementById() { [native code] }
>>> window.myAlias('item1')
TypeError: Illegal invocation
>>> document.getElementById('item1')
<div id=?"item1">?
И на тестовой странице я получаю тот же "Незаконный вызов".
Я что-то делаю неправильно? Может ли кто-нибудь еще воспроизвести это?
Кроме того, как ни странно, я просто попробовал, и он работает в IE8.
Ответы
Ответ 1
Вы должны привязать этот метод к объекту документа. Посмотрите:
>>> $ = 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();">
Когда вы выполняете простой псевдоним, функция вызывается глобальному объекту, а не объекту документа. Используйте метод, называемый замыканиями, чтобы исправить это:
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();">
Таким образом, вы не потеряете ссылку на исходный объект.
В 2012 году из ES5 появился новый bind
метод, который позволяет нам сделать это более привлекательным образом:
>>> $ = document.getElementById.bind(document)
>>> $('bn_home')
<body id="bn_home" onload="init();">
Ответ 2
Я углубился, чтобы понять это конкретное поведение, и я думаю, что нашел хорошее объяснение.
Прежде чем перейти к тому, почему вы не можете использовать псевдоним document.getElementById
, я попытаюсь объяснить, как работают функции/объекты JavaScript.
Всякий раз, когда вы вызываете функцию JavaScript, интерпретатор JavaScript определяет область действия и передает ее функции.
Рассмотрим следующую функцию:
function sum(a, b)
{
return a + b;
}
sum(10, 20); // returns 30;
Эта функция объявлена в области "Окно", и при ее вызове значение this
внутри функции sum будет глобальным объектом Window
.
Для функции "sum" не имеет значения, каково значение 'this', поскольку оно не использует его.
Рассмотрим следующую функцию:
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.
Когда вы вызываете функцию dave.getAge, интерпретатор JavaScript видит, что вы вызываете функцию getAge в объекте dave
, поэтому он устанавливает this
в dave
и вызывает функцию getAge
. getAge()
будет правильно возвращать 100
.
Вы можете знать, что в JavaScript вы можете указать область применения с помощью метода apply
. Попробуйте это.
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.
В приведенной выше строке вместо того, чтобы позволить JavaScript решить область действия, вы передаете область вручную как объект bob
. getAge
теперь вернет 200
, даже если вы думали, что вы вызвали getAge
в объекте dave
.
В чем смысл всего вышесказанного? Функции "свободно" привязаны к вашим объектам JavaScript. Например. вы можете сделать
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
Давайте сделаем следующий шаг.
var dave = new Person(new Date(1909, 1, 1));
var ageMethod = dave.getAge;
dave.getAge(); //returns 100;
ageMethod(); //returns ?????
ageMethod
выполнение вызывает ошибку! Что случилось?
Если вы внимательно прочитали мои предыдущие пункты, вы заметите, что метод dave.getAge
вызывался с dave
как this
, тогда как JavaScript не мог определить выполнение "scope" для ageMethod
. Поэтому он передал глобальное "окно" как 'this'. Теперь, когда Window
не имеет свойства birthDate
, выполнение ageMethod
завершится с ошибкой.
Как это исправить? Простой,
ageMethod.apply(dave); //returns 100.
Все ли это имеет смысл? Если да, то вы сможете объяснить, почему вы не можете использовать псевдоним document.getElementById
:
var $ = document.getElementById;
$('someElement');
$
вызывается с Window
как this
, и если getElementById
реализация ожидает, что this
будет document
, она потерпит неудачу.
Снова, чтобы исправить это, вы можете сделать
$.apply(document, ['someElement']);
Так почему это работает в Internet Explorer?
Я не знаю внутренней реализации getElementById
в IE, но комментарий в источнике jQuery (реализация метода inArray
) говорит, что в IE window == document
. Если это так, то в IE будет работать псевдоним document.getElementById
.
Чтобы проиллюстрировать это далее, я создал подробный пример. Посмотрите на функцию Person
ниже.
function Person(birthDate)
{
var self = this;
this.birthDate = birthDate;
this.getAge = function()
{
//Let 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();
};
}
Для функции Person
выше, здесь будут вести себя различные методы getAge
.
Создайте два объекта с помощью функции 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.
Прямо вперед, метод getAge получает объект yogi
как this
и выводит 100
.
var ageAlias = yogi.getAge;
console.log(ageAlias()); //Output: -1
JavaScript intercreter устанавливает Window
объект как this
, и наш метод getAge
возвращает -1
.
console.log(ageAlias.apply(yogi)); //Output: 100
Если мы установим правильную область видимости, вы можете использовать метод ageAlias
.
console.log(ageAlias.apply(anotherYogi)); //Output: 200
Если мы перейдем к другому объекту человека, он все равно правильно рассчитает возраст.
var ageSmarterAlias = yogi.getAgeSmarter;
console.log(ageSmarterAlias()); //Output: 100
Функция ageSmarter
захватила исходный объект this
, поэтому теперь вам не нужно беспокоиться о том, чтобы предоставить правильную область.
console.log(ageSmarterAlias.apply(anotherYogi)); //Output: 100 !!!
Проблема с ageSmarter
заключается в том, что вы никогда не сможете установить область для какого-либо другого объекта.
var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100
Функция ageSmartest
будет использовать исходную область, если указан недопустимый объем.
console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200
Вы по-прежнему сможете передать еще один объект Person
в getAgeSmartest
.:)
Ответ 3
Это короткий ответ.
Ниже приводится копия (ссылка на) функции. Проблема в том, что теперь функция находится в объекте window
, когда она была предназначена для работы с объектом document
.
window.myAlias = document.getElementById
Альтернативы
Ответ 4
Другой короткий ответ, просто для обертывания/сглаживания console.log
и подобных методов ведения журнала. Все они ожидаются в контексте console
.
Это можно использовать при обертке console.log
с некоторыми резервными ошибками, если вы или ваши пользователи столкнулись с трудностями при использовании с помощью браузера, который не поддерживает (всегда) его поддержку. Однако это не полное решение этой проблемы, так как необходимо расширить проверки и резерв - ваш пробег может измениться.
Пример использования предупреждений
var warn = function(){ console.warn.apply(console, arguments); }
Затем используйте его как обычно
warn("I need to debug a number and an object", 9999, { "user" : "Joel" });
Если вы предпочитаете видеть свои аргументы ведения журнала, завернутые в массив (я делаю, большую часть времени), замените .apply(...)
с помощью .call(...)
.
Должно работать с console.log()
, console.debug()
, console.info()
, console.warn()
, console.error()
. См. Также console
в MDN.
console firebug
Ответ 5
В дополнение к другим замечательным ответам существует простой метод jQuery $. proxy.
Вы можете использовать псевдоним следующим образом:
myAlias = $.proxy(document, 'getElementById');
или
myAlias = $.proxy(document.getElementById, document);
Ответ 6
На самом деле вы не можете использовать "чистый псевдоним" на предопределенном объекте. Поэтому ближайший к aliasing, который вы можете получить без обертывания, - это находиться внутри одного и того же объекта:
>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">