Подтвердить что ты не робот

Можно ли вызвать функцию.заправки без изменения контекста?

В некотором 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 original context>, args);
}

Изменить: Добавление более подробной информации о моем конкретном вопросе. Я создаю класс Client, содержащий объект socket (socket.io) среди других сведений, относящихся к соединению. Я подвергаю прослушиватели событий сокета через сам клиентский объект.

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 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 - это свойство уникального уникального идентификатора, которое по существу совпадает с ответом, найденным здесь.

4b9b3361

Ответ 1

'this' - это ссылка на контекст вашей функции. Это действительно точка.

Если вы хотите называть его в контексте другого объекта следующим образом:

otherObj.otherFn(args)

то просто замените этот объект на контекст:

otherObj.otherFn.apply(otherObj, args);

Это должно быть так.

Ответ 2

Если я правильно вас понимаю:

                          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]

Ответ 3

Проще говоря, просто назначьте это тому, что вы хотите, это otherFn:

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

Ответ 4

Если вы привязываете функцию к объекту и используете всюду связанную функцию, вы можете вызвать 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"

Ответ 5

Один из способов, которым вы можете обойти изменение контекста, которое может возникнуть в 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
}

Ответ 6

Я не собираюсь принимать это как ответ, поскольку я все еще надеюсь на что-то более подходящее. Но вот подход, который я использую сейчас, основываясь на отзывах по этому вопросу до сих пор.

Для любого класса, который будет вызывать 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) ->
     # ...

В приведенном выше примере message и broadcast всегда будут привязаны к новому объекту прототипа 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 никогда не будет работать успешно, поэтому моя привязка метода один раз, когда объект создается.

Ответ 7

Поскольку вы, похоже, хотите использовать функцию bind, как она определена в Javascript 1.8.5, и иметь возможность получить исходный объект 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 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"};

Ответ 8

Мне просто напомнили об этом после долгого времени. Оглядываясь назад, я думаю, что то, что я действительно пытаюсь сделать здесь, было похоже на то, как библиотека 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

Ответ 9

Просто нажмите свойства непосредственно на объект функции и вызовите его с собственным "контекстом".

function otherFn() {
    console.log(this.foo+' '+this.bar); // prints: "hello world" when called from rootFn()
}

otherFn.foo = 'hello';
otherFn.bar = 'world';

function rootFn() {
    // by the way, unless you are removing or adding elements to 'arguments',
    // just pass the arguments object directly instead of casting it to Array
    otherFn.apply(otherFn, arguments);
}