Можно ли вызвать функцию.заправки без изменения контекста?
В некотором 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
- это свойство уникального уникального идентификатора, которое по существу совпадает с ответом, найденным здесь.
Ответы
Ответ 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);
}