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


85

私はこの質問を読んでいて、function-wrapperメソッドではなくaliasメソッドを試したかったのですが、Firefox 3または3.5beta4、あるいはGoogleChromeのデバッグウィンドウとGoogleChromeの両方で動作させることができなかったようです。テスト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で動作します。


1
IE8では—それはある種の魔法の機能か何かかもしれません。
マチェイŁebkowski

stackoverflow.com/questions/954417で不正解にコメントを追加し、ここに指示しました。
Grant Wagner

1
Function.prototype.bindメソッドをサポートするブラウザーの場合:window.myAlias = document.getElementById.bind(document); 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
これは、新しい関数を返すため、実際にはラッパーです。Kevの質問に対する答えは、上記の理由で不可能であるということだと思います。ここで説明する方法がおそらく最良のオプションです。
jiggy

コメントありがとうございます。私はそれを理解するのに十分なほど詳しく調べていませんでした。それで、他の質問の答えの構文は完全に偽物ですか?興味深い
...– Kev

188

私はこの特定の振る舞いを理解するために深く掘り下げました、そして私は良い説明を見つけたと思います。

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

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

次の関数を検討してください。

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

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

この関数はWindowスコープで宣言されており、呼び出すとthis、sum関数内の値がグローバルWindowオブジェクトになります。

'sum'関数の場合、 'this'は使用されていないため、 '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今すぐ戻ります200あなたが「と呼ばれると思った」にもかかわらず、getAgedaveのオブジェクト。


上記のすべてのポイントは何ですか?関数は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'); 

$windowasで呼び出されthisgetElementById実装がであると予想thisされる場合document、失敗します。

もう一度これを修正するには、

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

では、なぜInternetExplorerで機能するのでしょうか。

getElementByIdIEでの内部実装はわかりませんが、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。:)


7
IEが機能する理由の+1。残りは少しトピックから外れていますが、それでも一般的に役立ちます。:)
Kev

44
素晴らしい答え。しかし、それがどのように話題から外れているかはわかりません。
ジャスティンジョンソン

確かに、とてもいい答えです。やや短いバージョンがここに書かれています
Marcel Korpel 2010

8
私がstackoverflowで見た最良の答えの1つ。
warbaker 2011年

なぜIEで動作するのですか?理由:stackoverflow.com/questions/6994139/... -それはどのようなコンテキストで動作しますので、このメソッドの実装は、実際には、{戻りウィンドウ[ID]}とすることができるよう
マチェイŁebkowski

3

これは短い答えです。

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

window.myAlias = document.getElementById

代替案は

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

    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 2012

-6

実際に、事前定義されたオブジェクトの関数を「純粋なエイリアス」にすることはできません。したがって、ラップせずに取得できるエイリアシングに最も近いのは、同じオブジェクト内にとどまることです。

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

5
これは少し間違っています。関数の実装で「this」を使用する場合は、「純粋なエイリアス」関数を使用できます。だからそれは異なります。
SolutionYogi

申し訳ありませんが、getElementByIdのコンテキストで意味しました。あなたが正しい。私はこれを反映するために答えを少し変更しました
...– Kev
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.