JavaScript işlevi takma adı çalışmıyor


85

Bu soruyu okudum ve işlev sarmalayıcı yöntemi yerine takma ad yöntemini denemek istedim, ancak bunu hem Firefox 3, hem de 3.5beta4 veya Google Chrome'da, hem hata ayıklama pencerelerinde hem de bir test web sayfasında.

Firebug:

>>> window.myAlias = document.getElementById
function()
>>> myAlias('item1')
>>> window.myAlias('item1')
>>> document.getElementById('item1')
<div id="item1">

Bir web sayfasına koyarsam, myAlias'a yapılan çağrı bana şu hatayı veriyor:

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 (netlik için >>> 'eklenmiş):

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

Ve test sayfasında, aynı "Yasadışı çağrı" görüyorum.

Yanlış bir şey mi yapıyorum? Bunu başka biri yeniden üretebilir mi?

Ayrıca, garip bir şekilde, sadece denedim ve IE8'de çalışıyor.


1
IE8'de - bir tür sihirli işlev veya başka bir şey olabilir.
Maciej Łebkowski

Stackoverflow.com/questions/954417 adresinde yanlış yanıtlara yorumlar ekledim ve onları buraya yönlendirdim.
Grant Wagner

1
Function.prototype.bind yöntemini destekleyen tarayıcılar için: window.myAlias ​​= document.getElementById.bind (document); MDN belgelerinde arama yapın, bir bağlama şimi olacaktır.
Ben Fleming

Yanıtlar:


39

Bu yöntemi belge nesnesine bağlamanız gerekir. Bak:

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

Basit bir takma ad yaptığınızda, işlev belge nesnesinde değil global nesnede çağrılır. Bunu düzeltmek için kapatma adı verilen bir teknik kullanın:

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

Bu şekilde orijinal nesneye olan referansı kaybetmezsiniz.

2012'de, bindES5'ten bunu daha güzel bir şekilde yapmamızı sağlayan yeni bir yöntem var :

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

4
Bu gerçekten bir sarmalayıcıdır çünkü yeni bir işlev döndürür. Sanırım Kev'nin sorusunun cevabı, yukarıda anlattığınız nedenlerle bunun mümkün olmamasıdır. Burada tarif ettiğiniz yöntem muhtemelen en iyi seçenektir.
jiggy

Yorumunuz için teşekkürler, bunu anlayacak kadar yakından bakmamıştım. Öyleyse, diğer soruya verilen cevaplardaki sözdizimi tamamen sahte mi? İlginç ...
Kev

188

Bu özel davranışı anlamak için derinlere indim ve iyi bir açıklama bulduğumu düşünüyorum.

Neden takma ad yapamadığınıza girmeden önce document.getElementById, JavaScript işlevlerinin / nesnelerinin nasıl çalıştığını açıklamaya çalışacağım.

Bir JavaScript işlevini her çalıştırdığınızda, JavaScript yorumlayıcısı bir kapsam belirler ve bunu işleve aktarır.

Aşağıdaki işlevi düşünün:

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

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

Bu işlev Pencere kapsamında bildirilir ve onu çağırdığınızda this, toplam işlevinin içindeki değer global Windownesne olur.

"Toplam" işlevi için, kullanmadığı için "bu" değerinin ne olduğu önemli değildir.


Aşağıdaki işlevi düşünü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.

Eğer dave.getAge işlevini çağırdığınızda JavaScript yorumlayıcısı, üzerinde getAge işlevini çağırarak olduğunu görür daveo ayarlar, böylece nesne thisiçin daveve çağıran getAgeişlevi. getAge()doğru şekilde geri dönecek 100.


JavaScript'te applyyöntemi kullanarak kapsamı belirtebileceğinizi biliyor olabilirsiniz . Hadi deneyelim.

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.

Yukarıdaki satırda, kapsama JavaScript'in karar vermesine izin vermek yerine, kapsamı bobnesne olarak manuel olarak geçiriyorsunuz. getAgeŞimdi dönecektir 200size denilen 'düşünce' rağmen getAgeüzerinde davenesne.


Yukarıdakilerin hepsinin anlamı nedir? İşlevler, JavaScript nesnelerinize 'gevşek bir şekilde' eklenir. Örneğin yapabilirsin

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

Bir sonraki adıma geçelim.

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

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

ageMethodyürütme bir hata veriyor! Ne oldu?

Eğer dikkatlice benim yukarıdaki noktaları okursanız dikkat ediyorum dave.getAgeyöntemi ile çağrıldı daveolarak thisJavaScript için 'kapsam' belirleyemedi oysa nesne ageMethodyürütme. Böylece global 'Pencere'yi' bu 'olarak geçti. Şimdi windowolmadığı gibibirthDate özelliği ,ageMethod yürütme başarısız olacaktır.

Bunu nasıl düzeltebilirim? Basit,

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

Yukarıdakilerin tümü mantıklı mıydı? Varsa, neden takma ad yapamadığınızı açıklayabileceksiniz document.getElementById:

var $ = document.getElementById;

$('someElement'); 

$windowas ile çağrılır thisve getElementByIduygulama bekliyorsathis olmakdocument , başarısız olur.

Yine bunu düzeltmek için yapabilirsin

$.apply(document, ['someElement']);

Peki neden Internet Explorer'da çalışıyor?

getElementByIdIE'de dahili uygulamasını bilmiyorum , ancak jQuery kaynağındaki ( inArrayyöntem uygulaması) bir yorum , IE'de window == document. Eğer durum buysa, takma addocument.getElementById IE'de çalışmalıdır.

Bunu daha da açıklamak için ayrıntılı bir örnek oluşturdum. PersonAşağıdaki işleve bir göz atı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();
    };

}

PersonYukarıdaki işlev için, çeşitli getAgeyöntemlerin nasıl davranacağı aşağıda açıklanmıştır.

PersonFunction kullanarak iki nesne oluşturalım .

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.

Açıkçası, getAge yöntemi yoginesneyi alır thisve çıktı alır 100.


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

JavaScript yorumlayıcı windownesneyi olarak ayarlar thisve bizim getAgeyöntemimiz geri döner -1.


console.log(ageAlias.apply(yogi)); //Output: 100

Doğru kapsamı ayarlarsak, ageAliasyöntemi kullanabilirsiniz .


console.log(ageAlias.apply(anotherYogi)); //Output: 200

Başka bir kişi nesnesini geçersek, yine de yaşı doğru hesaplayacaktır.

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

ageSmarterFonksiyon orijinal yakalanan thisDoğru kapsamını sunma hakkında endişe zorunda kalmamak için şimdi nesne.


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

Sorun ageSmarterşu ki, kapsamı başka bir nesneye asla ayarlayamazsınız.


var ageSmartestAlias = yogi.getAgeSmartest;
console.log(ageSmartestAlias()); //Output: 100
console.log(ageSmartestAlias.apply(document)); //Output: 100

ageSmartestGeçersiz bir kapsam verilir ise işlev orijinal kapsamını kullanacaktır.


console.log(ageSmartestAlias.apply(anotherYogi)); //Output: 200

Yine de başka bir Personnesneye geçebileceksiniz getAgeSmartest. :)


7
IE'nin neden çalıştığına dair +1. Gerisi biraz konu dışı ama yine de genel olarak faydalıdır. :)
Kev

44
Mükemmel cevap. Yine de hiçbirinin konu dışı olduğunu anlamıyorum.
Justin Johnson

Gerçekten çok güzel cevap. Bir miktar kısa versiyonu yazılır burada .
Marcel Korpel

8
Stackoverflow'da gördüğüm en iyi cevaplardan biri.
warbaker

neden IE'de çalışıyor? çünkü: stackoverflow.com/questions/6994139/… - bu nedenle bu yöntem uygulaması aslında {return window [id]} olabilir, bu nedenle herhangi bir bağlamda çalışabilir
Maciej Łebkowski

3

Bu kısa bir cevaptır.

Aşağıdaki, fonksiyonun bir kopyasını (referansını) yapar. Sorun, windownesne üzerinde yaşamak üzere tasarlandığında artık işlev nesnenin üzerindedir document.

window.myAlias = document.getElementById

Alternatifler

  • bir sarmalayıcı kullanmak için (daha önce Fabien Ménager tarafından bahsedilmiştir)
  • veya iki takma ad kullanabilirsiniz.

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

2

Başka bir kısa cevap, sadece sarma / takma ad console.logve benzer günlükleme yöntemleri için. Hepsi içinde olmayı bekliyorlarconsole bağlam .

Bu, (her zaman) desteklemeyen bir tarayıcı kullanırkenconsole.log sizin veya kullanıcılarınızın sorun yaşaması durumunda, bazı yedeklerle sarmalarken kullanılabilir. . Bu, genişletilmiş kontroller ve bir geri dönüş olması gerektiğinden, bu soruna tam bir çözüm değildir - kilometreniz değişebilir.

Uyarıların kullanıldığı örnek

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

O zaman her zamanki gibi kullan

warn("I need to debug a number and an object", 9999, { "user" : "Joel" });

Bir dizide bir sarılmış günlüğü argümanları görmeyi tercih ederse (Var, çoğu zaman), yedek .apply(...)ile .call(...).

İle çalışmalıdır console.log(), console.debug(), console.info(), console.warn(), console.error(). consoleMDN'ye de bakınız .


1

Diğer harika yanıtlara ek olarak, basit bir jQuery yöntemi var $ .proxy .

Bunun gibi takma adlar kullanabilirsiniz:

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

Veya

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

+1 çünkü uygulanabilir bir çözüm. Ancak, kesinlikle konuşursak, perde arkası $.proxyda gerçekten bir sarmalayıcıdır.
pimvdb

-6

Aslında , önceden tanımlanmış bir nesne üzerinde bir işlevi "saf takma ad" olarak adlandıramazsınız. Bu nedenle, sarmalamadan alabileceğiniz diğer adlara en yakın olanı aynı nesne içinde kalmaktır:

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

5
Bu biraz yanlış. İşlev uygulamasının "this" i kullanması koşuluyla, bir işlevi "saf takma ad" yapabilirsiniz. Yani değişir.
SolutionYogi

Üzgünüm, getElementById bağlamını kastetmiştim. Haklısın. Cevabı bunu yansıtmak için biraz değiştirdim ...
Kev
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.