web-dev-qa-db-ja.com

JavaScript関数のエイリアシングが機能しないようです

この質問 を読んでいて、関数ラッパーメソッドではなくエイリアスメソッドを試してみたかったのですが、Firefox 3または3.5beta4、またはGoogle Chromeで動作させることができなかったようです。 、デバッグウィンドウとテストWebページの両方で。

Firebug:

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

Webページに配置すると、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で動作します。

81
Kev

そのメソッドをドキュメントオブジェクトにバインドする必要があります。見て:

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

この特定の動作を理解するために深く掘り下げたところ、良い説明を見つけたと思います。

_document.getElementById_をエイリアスできない理由を理解する前に、JavaScript関数/オブジェクトがどのように機能するかを説明します。

JavaScript関数を呼び出すたびに、JavaScriptインタープリターがスコープを決定し、それを関数に渡します。

次の機能を検討してください。

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

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

この関数はWindowスコープで宣言されており、呼び出すと、sum関数内のthisの値はグローバルな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インタープリターはdaveオブジェクトでgetAge関数を呼び出していることを認識するため、thisdaveに設定し、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は、getAgeオブジェクトでdaveを呼び出したときに「_200_」を返すようになりました。


上記のすべてのポイントは何ですか?関数は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の実行はエラーをスローします!どうした?

上記の点を注意深く読んだ場合、davethisオブジェクトとして_dave.getAge_メソッドが呼び出されたのに対し、JavaScriptはageMethod実行の「スコープ」を決定できなかったことに注意してください。したがって、グローバルな「ウィンドウ」を「this」として渡しました。現在、windowにはbirthDateプロパティがないため、ageMethodの実行は失敗します。

これを修正する方法は?シンプル、

_ageMethod.apply(dave); //returns 100.
_

上記のすべてが理にかなっていますか?存在する場合は、_document.getElementById_をエイリアスできない理由を説明できます。

_var $ = document.getElementById;

$('someElement'); 
_

_$_がwindowとしてthisとして呼び出され、getElementById実装がthisdocumentであると予期している場合、失敗します。

再度これを修正するには、次のようにします

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

では、なぜInternet Explorerで機能するのでしょうか?

IEでのgetElementByIdの内部実装はわかりませんが、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関数を使用して2つのオブジェクトを作成しましょう。

_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に渡すことはできます。 :)

184
SolutionYogi

これは簡単な答えです。

以下は、関数のコピー(参照)です。問題は、関数がwindowオブジェクト上にあるように設計されたときに、documentオブジェクト上にあることです。

window.myAlias = document.getElementById

選択肢は

  • ラッパーを使用する(既にFabienMénagerが言及)
  • または、2つのエイリアスを使用できます。

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

_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()で動作するはずです。 MDNの consoleも参照してください

コンソールfirebug

2
Joel Purra

他の優れた答えに加えて、単純なjQueryメソッド $。proxy があります。

次のようにエイリアスできます:

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

または

myAlias = $.proxy(document.getElementById, document);
1
Sanghyun Lee