2009-06-17 4 views
76

Я просто читал this question и хотел попробовать метод alias, а не метод функции-обертки, но я не мог заставить его работать в Firefox 3 или 3.5beta4 , или Google Chrome, как в их отладочных окнах, так и на тестовой веб-странице.Функция сглаживания JavaScript, похоже, не работает

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 (с >>> 's вставленный для ясности) :

>>> window.myAlias = document.getElementById 
function getElementById() { [native code] } 
>>> window.myAlias('item1') 
TypeError: Illegal invocation 
>>> document.getElementById('item1') 
<div id=?"item1">? 

И на тестовой странице я получаю тот же «Незаконный вызов».

Я что-то не так? Может ли кто-нибудь еще воспроизвести это?

Кроме того, как ни странно, я просто попытался и работает в IE8.

+0

в IE8 - это может быть какой-то волшебной функции или что-то. –

+0

Я добавил комментарии к неправильным ответам по адресу http://stackoverflow.com/questions/954417 и направил их сюда. –

+1

Для браузеров, поддерживающих метод Function.prototype.bind: window.myAlias ​​= document.getElementById.bind (document); Поиск в документах MDN, там будет привязка. –

ответ

33

Вы должны привязать этот метод к объекту документа. Смотри:

>>> $ = 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 году, есть новый метод bind из ES5, что позволяет нам делать это в голубевода образом:

>>> $ = document.getElementById.bind(document) 
>>> $('bn_home') 
<body id="bn_home" onload="init();"> 
+3

Это действительно оболочка, потому что она возвращает новую функцию. Я думаю, что ответ на вопрос Кева заключается в том, что по причинам, описанным выше, невозможно. Метод, который вы здесь описываете, вероятно, является лучшим вариантом. – jiggy

+0

Спасибо за ваш комментарий, я не пристально посмотрел, чтобы понять это. Итак, синтаксис ответов на другой вопрос полностью фиктивный? Интересно ... – Kev

+0

Собственно, вы должны представить это как ответ ... – Kev

-6

Вы на самом деле не может «чистый псевдоним» функция на заранее определенном объекте , Таким образом, ближе всего к альясингу вы можете получить без оборачивания, оставаясь в пределах того же объекта:

>>> document.s = document.getElementById; 
>>> document.s('myid'); 
<div id="myid"> 
+5

Это немного некорректно. Вы можете использовать «чистый псевдоним», при условии, что использование функции «this» используется в функции. Так что это зависит. – SolutionYogi

+0

Извините, я имел в виду в контексте getElementById. Ты прав. Я немного изменил ответ, чтобы отразить это ... – Kev

+0

Oh Kev это действительно OP lolzzzz –

181

я копал глубоко, чтобы понять это определенное поведение, и я думаю, что я нашел хорошее объяснение.

Прежде чем я узнаю, почему вы не можете использовать псевдоним document.getElementById, я попытаюсь объяснить, как работают функции/объекты JavaScript.

Всякий раз, когда вы вызываете функцию JavaScript, интерпретатор JavaScript определяет область действия и передает ее функции.

Рассмотрим следующие функции:

function sum(a, b) 
{ 
    return a + b; 
} 

sum(10, 20); // returns 30; 

Эта функция объявлена ​​в области видимости окна и при вызове его значение this внутри функции суммы будет глобальный Window объект.

Для функции «сумма» не имеет значения, что такое значение «это», поскольку оно не использует его.


Рассмотрим следующие функции:

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 видит, что вы вызываете функцию СеЬАде на 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 не может определить «сферу» для ageMethod исполнения. Поэтому он передал глобальное «окно» как «это». Теперь как 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.Если это так, то сглаживание document.getElementById должно работать в IE.

Чтобы проиллюстрировать это далее, я создал сложный пример. Посмотрите на функцию Person ниже.

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(); 
    }; 

} 

Для 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. 

прямо вперед, метод СеЬАде получает yogi объект как this и выходов 100.


var ageAlias = yogi.getAge; 
console.log(ageAlias()); //Output: -1 

JavaScript interepreter устанавливает 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. :)

+7

+1 для чего IE работает. Остальное немного не по теме, но по-прежнему полезно в целом.:) – Kev

+42

Отличный ответ. Я не понимаю, как все это не в тему. –

+0

Очень хороший ответ. Несколько более короткая версия написана [здесь] (http://stackoverflow.com/questions/585840/implement-map-in-javascript-that-supports-object-methods-as-mapped-functions/585918#585918). –

3

Это короткий ответ.

Следующая копия (ссылка) на функцию. Проблема в том, что теперь функция находится на объекте window, когда он был предназначен для работы на объекте document.

window.myAlias = document.getElementById 

Альтернативы

  • использовать обертку (уже упоминаемый Фабьен Menager)
  • или вы можете использовать два псевдонима.

    window.d = document // A renamed reference to the object 
    window.d.myAlias = window.d.getElementById 
    
2

Еще один короткий ответ, только для упаковки/альясинг console.log и подобных методов ведения журнала. Они все ожидают быть в контексте console.

Это можно использовать при упаковке console.log с некоторыми запасными частями, если вы или ваши пользователи столкнулись с проблемой, когда using a browser that doesn't (always) support it. Это не полное решение этой проблемы, поскольку, поскольку для этого необходимо расширить проверки и резерв - ваш пробег может отличаться.

Пример использования предупреждения

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 on MDN.

1

В дополнении к другим большим ответов, есть простой метод JQuery $.proxy.

Вы можете псевдоним, как это:

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

Или

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

+1, потому что это приемлемое решение. Но, строго говоря, за кулисами «$ .proxy» также является оберткой. – pimvdb

Смежные вопросы