89

この質問を読んだばかりで、関数ラッパー メソッドではなくエイリアス メソッドを試してみたかったのですが、Firefox 3 または 3.5beta4、または Google Chrome のデバッグ ウィンドウとテスト Web ページで。

ファイアーバグ:

>>> 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で動作します。

4

6 に答える 6

191

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

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

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ます。


applyJavaScriptでは、メソッドを使用してスコープを指定できることをご存知かもしれません。それを試してみましょう。

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戻ります。200getAgedave


上記のすべてのポイントは何ですか?関数は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呼び出されたのに対し、JavaScriptは実行の「スコープ」を判別できなかったことがわかります。したがって、グローバルな「ウィンドウ」を「これ」として渡しました。プロパティがないため、実行は失敗します。davethisageMethodwindowbirthDateageMethod

これを修正する方法は?単純、

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

上記のすべてが理にかなっていますか?もしそうなら、あなたはあなたがエイリアスすることができない理由を説明することができるでしょうdocument.getElementById

var $ = document.getElementById;

$('someElement'); 

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

もう一度これを修正するには、次のことができます

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

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

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オブジェクトをasとして取得し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。:)

于 2009-07-21T23:00:27.317 に答える
42

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

>>> $ = 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();">
于 2009-06-17T14:35:29.387 に答える
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
    
于 2011-02-03T17:55:28.180 に答える
2

もう1つの簡単な答えは、ラッピング/エイリアシング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()です。MDNも参照してください。console.info()console.warn()console.error()console

于 2012-02-03T10:06:34.433 に答える
1

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

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

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

または

myAlias = $.proxy(document.getElementById, document);
于 2012-08-17T04:23:40.403 に答える
-6

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

>>> document.s = document.getElementById;
>>> document.s('myid');
<div id="myid">
于 2009-06-29T17:44:28.290 に答える