web-dev-qa-db-ja.com

コンテキストを変更せずにfunction.applyを呼び出すことは可能ですか?

一部のJavascriptコード(具体的にはnode.js)では、コンテキストを変更せずに、不明な引数のセットを使用して関数を呼び出す必要があります。例えば:

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(this, args);
}

上記の問題は、applyを呼び出すときに、最初の引数としてthisを渡すことによってコンテキストを変更することです。呼び出されている関数にargsを渡したいなし呼び出されている関数のコンテキストを変更します。私は本質的にこれをしたい:

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(<otherFn's original context>, args);
}

編集:私の特定の質問に関する詳細を追加します。接続に関連する他の情報の中でも、ソケット(socket.io)オブジェクトを含むClientクラスを作成しています。クライアントオブジェクト自体を介してソケットのイベントリスナーを公開しています。

class Client
  constructor: (socket) ->
    @socket    = socket
    @avatar    = socket.handshake.avatar
    @listeners = {}

  addListener: (name, handler) ->
    @listeners[name] ||= {}
    @listeners[name][handler.clientListenerId] = wrapper = =>
      # append client object as the first argument before passing to handler
      args = Array.prototype.slice.call(arguments)
      args.unshift(this)
      handler.apply(this, args)  # <---- HANDLER'S CONTEXT IS CHANGING HERE :(

    @socket.addListener(name, wrapper)

  removeListener: (name, handler) ->
    try
      obj = @listeners[name]
      @socket.removeListener(obj[handler.clientListenerId])
      delete obj[handler.clientListenerId]

clientListenerIdは、基本的に ここにある答え と同じカスタムの一意の識別子プロパティであることに注意してください。

34
Matt Huggins

私があなたを正しく理解している場合:

_                          changes context
                   |    n     |      y       |
accepts array    n |  func()  | func.call()  |
of arguments     y | ???????? | func.apply() |
_

PHPには、このための関数_call_user_func_array_があります。残念ながら、JavaScriptはこの点で欠けています。 eval()を使用してこの動作をシミュレートしているようです。

_Function.prototype.invoke = function(args) {
    var i, code = 'this(';
    for (i=0; i<args.length; i++) {
        if (i) { code += ',' }
        code += 'args[' + i + ']';
    }
    eval(code + ');');
}
_

はい、知っています。 eval()が好きな人はいません。遅くて危険です。ただし、この状況では、少なくともすべての変数が関数内に含まれているため、クロスサイトスクリプティングについて心配する必要はありません。本当に、JavaScriptにこのためのネイティブ関数がないのは残念ですが、evalがあるのはこのような状況のためだと思います。

それが機能することの証明:

_function showArgs() {
    for (x in arguments) {console.log(arguments[x]);}
}

showArgs.invoke(['foo',/bar/g]);
showArgs.invoke([window,[1,2,3]]);
_

Firefoxコンソールの出力:

_--
[12:31:05.778] "foo"
[12:31:05.778] [object RegExp]
[12:31:05.778] [object Window]
[12:31:05.778] [object Array]
_
7
Brian McCutchon

'this' is関数のコンテキストへの参照。それが本当にポイントです。

このような別のオブジェクトのコンテキストでそれを呼び出すことを意味する場合:

otherObj.otherFn(args)

次に、そのオブジェクトをコンテキストの代わりに使用します。

otherObj.otherFn.apply(otherObj, args);

それはそれであるはずです。

7
Scott Sauyet

簡単に言えば、これを希望するものに割り当てるだけです。つまり、otherFn:です。

function fn() {
    var args = Array.prototype.slice.call(arguments);
    otherFn.apply(otherFn, args);
}
6
Dave

関数をオブジェクトにバインドし、バインドされた関数をどこでも使用する場合は、nullを指定してapplyを呼び出すことができますが、それでも正しいコンテキストを取得できます。

var Person = function(name){
    this.name = name;
}
Person.prototype.printName = function(){
    console.log("Name: " + this.name);
}

var bob = new Person("Bob");

bob.printName.apply(null); //window.name
bob.printName.bind(bob).apply(null); //"Bob"
3
Blackfire

最近は使用できます restparameters

function fn(...args) {
    otherFn(...args);
}

唯一の欠点は、fnで特定のパラメータを使用する場合は、argsから抽出する必要があることです。

function fn(...args) {
    let importantParam = args[2]; //third param
    // ...
    otherFn(...args);
}

試してみる例を次に示します(ESの次のバージョンで短くします)。

// a one-line "sum any number of arguments" function
const sum = (...args) => args.reduce((sum, value) => sum + value);

// a "proxy" function to test:
var pass = (...args) => sum(...args);
console.log(pass(1, 2, 15));
1
YakovL

関数が呼び出されたときにJavaScriptで発生する可能性のあるコンテキストの変更を回避する方法のひとつは、thisが実行されないコンテキストで操作できるようにする必要がある場合、オブジェクトのコンストラクターの一部であるメソッドを使用することです。元のthis識別子を格納するローカルプライベート変数を効果的に作成することにより、親オブジェクトを意味します。

JavaScriptでのスコープに関するほとんどの議論と同様に、これは完全には明確ではないことを認めます。そのため、これをどのように行ったかの例を次に示します。

function CounterType()
{
    var counter=1;
    var self=this; // 'self' will now be visible to all

    var incrementCount = function()
    {
        // it doesn't matter that 'this' has changed because 'self' now points to CounterType()
        self.counter++;
    };

}

function SecondaryType()
{
    var myCounter = new CounterType();
    console.log("First Counter : "+myCounter.counter); // 0
    myCounter.incrementCount.apply(this); 
    console.log("Second Counter: "+myCounter.counter); // 1
}
1
glenatron

Javascript1.8.5で定義されているbind関数を使用し、バインド関数を渡した元のthisオブジェクトを取得できるようにしたいようですので、再定義することをお勧めします。 Function.prototype.bind 関数:

Function.prototype.bind = function (oThis) {
    if (typeof this !== "function") {
        throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var aArgs = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP = function () {},
        fBound = function () {
            return fToBind.apply(this instanceof fNOP && oThis
            ? this
            : oThis,
            aArgs.concat(Array.prototype.slice.call(arguments)));
        };

    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();

    /** here's the additional code **/
    fBound.getContext = function() {
        return oThis;
    };
    /**/

    return fBound;
};

これで、bind関数を呼び出した元のコンテキストを次のコマンドで取得できます。

function A() {
    return this.foo+' '+this.bar;
}

var HelloWorld = A.bind({
    foo: 'hello',
    bar: 'world',
});

HelloWorld(); // returns "hello world";
HelloWorld.getContext(); // returns {foo:"hello", bar:"world"};
0
Blake Regalia

久しぶりにこの質問を思い出しました。今振り返ってみると、ここで本当に達成しようとしていたのは、Reactライブラリが自動バインドで機能する方法に似たものだったと思います。

基本的に、各関数は呼び出されるラップされたバインドされた関数です。

function SomeClass() {
};

SomeClass.prototype.whoami = function () {
  return this;
};

SomeClass.createInstance = function () {
  var obj = new SomeClass();

  for (var fn in obj) {
    if (typeof obj[fn] == 'function') {
      var original = obj[fn];

      obj[fn] = function () {
        return original.apply(obj, arguments);
      };
    }
  }

  return obj;
};

var instance = SomeClass.createInstance();
instance.whoami() == instance;            // true
instance.whoami.apply(null) == instance;  // true
0
Matt Huggins

私はまだもっと適切なものを望んでいるので、これを答えとして受け入れるつもりはありません。しかし、これまでのところ、この質問に対するフィードバックに基づいて、現在使用しているアプローチは次のとおりです。

_Client.prototype.addListener_または_Client.prototype.removeListener_を呼び出すクラスについては、コンストラクターに次のコードを追加しました。

_class ExampleClass
  constructor: ->
    # ...
    for name, fn of this
      this[name] = fn.bind(this) if typeof(fn) == 'function'

   message: (recipient, body) ->
     # ...

   broadcast: (body) ->
     # ...
_

上記の例では、messagebroadcastは、インスタンス化されるときに常に新しいExampleClassプロトタイプオブジェクトにバインドされ、元の質問のaddListenerコードを許可します。働くために。

なぜ私が次のようなことをしなかったのか疑問に思っている方もいらっしゃると思います。

_example = new ExampleClass
client.addListener('message', example.bind(example))
# ...
client.removeListener('message', example.bind(example))
_

問題は、.bind( )が呼び出されるたびに、それが新しいオブジェクトになることです。つまり、次のことが当てはまります。

_example.bind(example) != example.bind(example)
_

そのため、removeListenerは正常に機能しないため、オブジェクトがインスタンス化されたときにメソッドを1回バインドします。

0
Matt Huggins