2009-06-17 3 views
76

私はちょうどthis questionを読んでいて、関数ラッパーメソッドではなくエイリアスメソッドを試してみたかったのですが、Firefox 3または3.5beta4 、またはGoogle Chromeをデバッグウィンドウとテストウェブページの両方に表示します。JavaScript関数のエイリアシングが機能していないようです

の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] 

(わかりやすくするために挿入>>>さんと)クローム:

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

テストページでは、同じ「不正な呼び出し」が発生します。

何か間違っていますか?他の誰かがこれを再現できますか?

また、奇妙なことに、私は試してみましたが、それはIE8で動作します。

+0

- それは魔法の関数か何かのいくつかの種類である可能性があります。 –

+0

http://stackoverflow.com/questions/954417で誤った回答にコメントを追加し、ここで指示しました。 –

+1

Function.prototype.bindメソッドをサポートするブラウザーの場合:window.myAlias = document.getElementById.bind(document); MDNドキュメントを検索すると、バインドシムが存在します。 –

答えて

33

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

単純なエイリアスを実行しているときは、関数はドキュメントオブジェクトではなくグローバルオブジェクトで呼び出されます。

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

本当に新しい関数を返すので、実際にはラッパーです。 Kevの質問への答えは、あなたが上記の理由では不可能だということです。ここで説明している方法は、おそらく最適なオプションです。 – jiggy

+0

あなたのコメントをありがとう、私はそれを実現するために十分に密接に見ていなかった。だから、他の質問の答えの構文は完全に偽ですか?面白い... – Kev

+0

実際には、答えとして提出する必要があります... – Kev

-6

あなた実際にことはできません「純粋な別名」事前に定義されたオブジェクトの機能。そのため、あなたはラッピングせずに取得することができエイリアシングに最も近いが、同じオブジェクト内に留まることである:

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

これはやや間違っています。関数の実装で 'this'を使用している場合は、関数を「純粋なエイリアス」にすることができます。それはそれに依存します。 – SolutionYogi

+0

申し訳ありませんが、私はgetElementByIdのコンテキストで意味します。あなたが正しい。私はこれを反映するために少し答えを変更しました... – Kev

+0

ああKevこれは本当にIEの理由lolzzzz –

181

私は、この特定の動作を理解するために深く掘って私は良い説明を見つけたと思います。

document.getElementByIdのエイリアスにならないうちに、JavaScript関数/オブジェクトの動作を説明します。

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

は、関数を次の点を考慮

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

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

この関数は、ウィンドウのスコープで宣言され、あなたがそれを呼び出すときsum関数の内部thisの値は、グローバルWindow対象となります。

'sum'関数の場合、 'this'の値が使用されていないため、その値は関係ありません。


機能を、次の点を考慮:あなたはdave.getAge関数を呼び出すとき

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. 

、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オブジェクトとして手動でスコープを渡しています。 getAgedaveオブジェクトでgetAgeを呼び出したと思っても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実行時にエラーが発生します。何が起こった?あなたは慎重に私のような点を読めば

は、JavaScriptがageMethod実行のための「スコープ」を決めることができなかったのに対し、dave.getAge方法がdavethisとしてオブジェクトと呼ばれたことに注意します。それで、グローバルな「ウィンドウ」を「this」として渡しました。現在windowにはbirthDateプロパティがないため、ageMethodの実行は失敗します。

これを修正するにはどうすればよいですか?シンプル、

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

上記メイク感覚のすべてをしましたか?それがない場合は、エイリアスdocument.getElementByIdにできない理由を説明することができるようになります:

var $ = document.getElementById; 

$('someElement'); 

$thisとしてwindowと呼ばれ、getElementById実装はthisdocumentであることを期待している場合、それは失敗します。

再びこの問題を解決するために、あなたがそう

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

を行うことができますなぜそれがInternet Explorerで動作しますか?

私はgetElementByIdのIEでの内部実装はわかりませんが、jQueryソース(inArrayメソッド実装)のコメントでは、IEではwindow == documentと表示されています。その場合、エイリアスでdocument.getElementByIdのエイリアシングが機能するはずです。

これをさらに説明するために、私は精巧な例を作成しました。下記のPerson関数を見てください。上記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(); 
    }; 

} 

、ここでは様々な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方法はthis出力100としてyogiオブジェクトを取得します。 thisとしてwindowオブジェクトを設定し、私たちのgetAge方法は-1を返しますinterepreter


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

JavaScriptを。


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 

あなたはまだgetAgeSmartestに別のPersonオブジェクトを渡すことができるようになります。 :)

+7

+1です。残りの部分は少し話題にはなりますが、一般的にはまだ役立ちます。:) – Kev

+42

優秀な答え。私はそれがどのように話題から外れているかわかりません。 –

+0

本当に素晴らしい答えです。多少短いバージョンが[ここに書かれている](http://stackoverflow.com/questions/585840/implement-map-in-javascript-that-supports-object-methods-as-mapped-functions/585918#585918)。 –

3

これは簡単な答えです。

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

window.myAlias = document.getElementById 

選択肢は(すでにファビアンMénagerが言及した)ラッパー

  • を使用するか、次の2つのエイリアスを使用することができます

    • です。

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

    だけラッピング/エイリアシングconsole.logと同様の伐採方法のもう一つの短い答え、。彼らはすべてconsoleのコンテキストになると予想しています。

    これは、using a browser that doesn't (always) support itのときに問題が発生した場合に備えて、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()で動作するはずです。 console on MDNも参照してください。他の偉大な答えに加えて

    1

    、簡単なjQueryの方法$.proxyがあります。

    あなたはこのようなエイリアスができます:IE8で

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

    それとも

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

    +1を読んだ後、ソフトウェアエンジニアリングのために再認証されました。しかし、厳密に言えば、 '$ .proxy'は実際にはラッパーでもあります。 – pimvdb

    関連する問題