Згладжування функції JavaScript, схоже, не працює


85

Я просто читав це запитання і хотів спробувати метод псевдоніма, а не метод-обгортки, але, здається, я не міг змусити його працювати як у Firefox 3, так і в 3.5beta4, або в Google Chrome, як у вікнах налагодження, так і на тестовій веб-сторінці.

Пожежна помилка:

>>> 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
в IE8 - це може бути якась магічна функція чи щось інше.
Maciej Łebkowski

Я додав коментарі до неправильних відповідей на stackoverflow.com/questions/954417 і скерував їх сюди.
Грант Вагнер,

1
Для браузерів, що підтримують метод Function.prototype.bind: window.myAlias ​​= document.getElementById.bind (документ); Шукайте в документах MDN, буде прив'язка шима.
Бен Флемінг

Відповіді:


39

Ви повинні прив’язати цей метод до об’єкта документа. Подивіться:

>>> $ = 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();">

4
Що насправді є обгорткою, оскільки повертає нову функцію. Я думаю, що відповідь на запитання Кева полягає в тому, що це неможливо з причин, які ви описали вище. Описаний тут метод, мабуть, найкращий варіант.
джиггі

Дякую за ваш коментар, я недостатньо придивився, щоб усвідомити це. Отже, синтаксис у відповідях на інше питання є абсолютно помилковим? Цікаво ...
Кев

188

Я глибоко копався, щоб зрозуміти цю особливу поведінку, і, думаю, знайшов хороше пояснення.

Перш ніж я дізнаюся, чому ви не можете створити псевдонім document.getElementById, я спробую пояснити, як працюють функції / об'єкти JavaScript.

Кожного разу, коли ви викликаєте функцію JavaScript, інтерпретатор JavaScript визначає область дії та передає її функції.

Розглянемо наступну функцію:

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

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

Ця функція оголошена в області Window, і коли ви викликаєте її, значення thisвсередині функції sum буде глобальним 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.

Прямо вперед, метод getAge отримує yogiоб'єкт як thisі виводить 100.


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

Інтерпретер JavaScript встановлює 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. Решта дещо не в темі, але все-таки корисна в цілому. :)
Кев

44
Відмінна відповідь. Я не бачу, як щось із цього не відповідає темі.
Джастін Джонсон,

Дуже приємна відповідь, справді. Кілька більш коротка версія написана тут .
Марсель Корпель,

8
Одна з найкращих відповідей, яку я бачив на stackoverflow.
warbaker

чому це працює в IE? тому що: stackoverflow.com/questions/6994139/… - отже, реалізація цього методу насправді може бути {вікном повернення [id]}, тому він буде працювати в будь-якому контексті
Maciej Łebkowski

3

Це коротка відповідь.

Далі робиться копія (посилання на) функції. Проблема в тому, що тепер функція знаходиться на windowоб’єкті, коли вона була розроблена для життя на documentоб’єкті.

window.myAlias = document.getElementById

Альтернативи такі

  • використовувати обгортку (про яку вже згадував Fabien Ménager)
  • або ви можете використовувати два псевдоніми.

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

2

Ще одна коротка відповідь, просто для обгортання / псевдонімів 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(). Див. Також consoleMDN .


1

На додаток до інших чудових відповідей, існує простий метод jQuery $ .proxy .

Ви можете псевдонім, як це:

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

Або

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

+1, оскільки це можливе рішення. Але, строго кажучи, за лаштунками $.proxyсправді є і обгортка.
pimvdb

-6

Ви насправді не можете "чистий псевдонім" функції на заздалегідь визначеному об'єкті. Отже, найближчим до псевдоніму, який ви можете отримати без обтікання, є перебування в одному об’єкті:

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

5
Це трохи неправильно. Ви можете "чистий псевдонім" функції, за умови, що в реалізації функції використовується "this". Так це залежить.
SolutionYogi

Вибачте, я мав на увазі в контексті getElementById. Ти маєш рацію. Я трохи змінив відповідь, щоб відобразити це ...
Кев
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.