В Ruby я думаю, что вы можете вызвать метод, который не был определен, и все же захватить имя вызываемого метода и обработать этот метод во время выполнения.
Может ли Javascript делать то же самое?
В Ruby я думаю, что вы можете вызвать метод, который не был определен, и все же захватить имя вызываемого метода и обработать этот метод во время выполнения.
Может ли Javascript делать то же самое?
Функция ruby, которую вы объясняете, называется "method_missing" http://rubylearning.com/satishtalim/ruby_method_missing.htm.
Это новая функция, которая присутствует только в некоторых браузерах, таких как Firefox (в движке Javascript для паука-паука). В SpiderMonkey он называется "__noSuchMethod__" https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/NoSuchMethod
Прочтите эту статью от Yehuda Katz http://yehudakatz.com/2008/08/18/method_missing-in-javascript/ для получения более подробной информации о предстоящей реализации.
method_missing не подходит для JavaScript по той же причине, которой он не существует в Python: на обоих языках методы - это просто атрибуты, которые являются функциями; и объекты часто имеют общедоступные атрибуты, которые не подлежат вызову. Контраст с Ruby, где открытый интерфейс объекта - это 100% -ный метод.
Что нужно в JavaScript - это крючок, чтобы поймать доступ к отсутствующим атрибутам, независимо от того, являются они методами или нет. Python имеет это: см. __getattr__ специальный метод.
Предложение __ noSuchMethod__ от Mozilla внесло еще одну несогласованность в языке, на котором они пронизаны.
Вперед JavaScript - это механизм Прокси-сервер (также в ECMAscript Harmony), который ближе к протоколу Python для настройки доступа к атрибутам, чем к Ruby method_missing.
В настоящий момент нет. Существует предложение для ECMAScript Harmony, называемое прокси-серверами, которое реализует аналогичную (на самом деле, гораздо более мощную) функцию, но ECMAScript Harmony не выходит и, вероятно, не будет на пару лет.
Я создал библиотеку для javascript, которая позволяет использовать method_missing
в javascript: https://github.com/ramadis/unmiss
Он использует ES6 Proxies для работы. Вот пример использования наследования класса ES6. Однако вы также можете использовать декораторы для достижения тех же результатов.
import { MethodMissingClass } from 'unmiss'
class Example extends MethodMissingClass {
methodMissing(name, ...args) {
console.log(`Method ${name} was called with arguments: ${args.join(' ')}`);
}
}
const instance = new Example;
instance.what('is', 'this');
> Method what was called with arguments: is this
Нет, в javascript нет возможности метапрограммирования, аналогично рубину method_missing. Интерпретатор просто вызывает ошибку, которую вызывающий код может уловить, но не может быть обнаружен объектом, к которому обращаются. Здесь есть некоторые ответы на определение функций во время выполнения, но это не одно и то же. Вы можете много метапрограммировать, изменять конкретные экземпляры объектов, определять функции, выполнять такие функциональные функции, как воспоминания и декораторы. Но нет динамического метапрограммирования отсутствующих функций, как в рубине или питоне.
Я пришел к этому вопросу, потому что искал способ провалиться к другому объекту, если метод не присутствовал на первом объекте. Это не так гибко, как то, что вы просите - например, если отсутствует метод, то он будет терпеть неудачу.
Я думал о том, чтобы сделать это для небольшой библиотеки, которую у меня есть, что помогает настраивать объекты extjs таким образом, чтобы сделать их более проверенными. У меня были отдельные вызовы, чтобы на самом деле получить объекты для взаимодействия и подумал, что это может быть хорошим способом соединить эти вызовы, эффективно возвращая расширенный тип
Я могу представить два способа сделать это:
Прототипы
Вы можете сделать это, используя прототипы, поскольку материал попадает в прототип, если он не находится на самом объекте. Похоже, что это не сработает, если набор функций, которые вы хотите использовать, чтобы использовать это ключевое слово - очевидно, ваш объект не знает или не заботится о вещах, о которых знает другой.
Если все его собственный код и вы не используете это и конструкторы... что является хорошей идеей по многим причинам, вы можете сделать это следующим образом:
var makeHorse = function () {
var neigh = "neigh";
return {
doTheNoise: function () {
return neigh + " is all im saying"
},
setNeigh: function (newNoise) {
neigh = newNoise;
}
}
};
var createSomething = function (fallThrough) {
var constructor = function () {};
constructor.prototype = fallThrough;
var instance = new constructor();
instance.someMethod = function () {
console.log("aaaaa");
};
instance.callTheOther = function () {
var theNoise = instance.doTheNoise();
console.log(theNoise);
};
return instance;
};
var firstHorse = makeHorse();
var secondHorse = makeHorse();
secondHorse.setNeigh("mooo");
var firstWrapper = createSomething(firstHorse);
var secondWrapper = createSomething(secondHorse);
var nothingWrapper = createSomething();
firstWrapper.someMethod();
firstWrapper.callTheOther();
console.log(firstWrapper.doTheNoise());
secondWrapper.someMethod();
secondWrapper.callTheOther();
console.log(secondWrapper.doTheNoise());
nothingWrapper.someMethod();
//this call fails as we dont have this method on the fall through object (which is undefined)
console.log(nothingWrapper.doTheNoise());
Это не работает для моего случая использования, так как ребята из extjs не только ошибочно использовали 'this', они также создали целую сумасшедшую систему классического типа наследования, использующую прототипы и 'this'.
Это первый раз, когда я использовал прототипы/конструкторы, и я был немного озадачен тем, что вы не можете просто установить прототип - вам также придется использовать конструктор. Существует волшебное поле в объектах (по крайней мере, в firefox) вызывает __proto, который в основном является настоящим прототипом. кажется, что фактическое поле прототипа используется только во время строительства... как запутанно!
Методы копирования
Этот метод, вероятно, дороже, но кажется более элегантным для меня, а также будет работать с кодом, использующим this
(например, чтобы вы могли использовать его для обтекания объектов библиотеки). Кроме того, он будет работать и с материалами, написанными с использованием функционального/закрывающего стиля. Я просто проиллюстрировал это с помощью этих/конструкторов, чтобы показать, что он работает с такими вещами.
Здесь моды:
//this is now a constructor
var MakeHorse = function () {
this.neigh = "neigh";
};
MakeHorse.prototype.doTheNoise = function () {
return this.neigh + " is all im saying"
};
MakeHorse.prototype.setNeigh = function (newNoise) {
this.neigh = newNoise;
};
var createSomething = function (fallThrough) {
var instance = {
someMethod : function () {
console.log("aaaaa");
},
callTheOther : function () {
//note this has had to change to directly call the fallThrough object
var theNoise = fallThrough.doTheNoise();
console.log(theNoise);
}
};
//copy stuff over but not if it already exists
for (var propertyName in fallThrough)
if (!instance.hasOwnProperty(propertyName))
instance[propertyName] = fallThrough[propertyName];
return instance;
};
var firstHorse = new MakeHorse();
var secondHorse = new MakeHorse();
secondHorse.setNeigh("mooo");
var firstWrapper = createSomething(firstHorse);
var secondWrapper = createSomething(secondHorse);
var nothingWrapper = createSomething();
firstWrapper.someMethod();
firstWrapper.callTheOther();
console.log(firstWrapper.doTheNoise());
secondWrapper.someMethod();
secondWrapper.callTheOther();
console.log(secondWrapper.doTheNoise());
nothingWrapper.someMethod();
//this call fails as we dont have this method on the fall through object (which is undefined)
console.log(nothingWrapper.doTheNoise());
Я действительно ожидал, что нужно использовать bind
где-то, но, похоже, это не нужно.
Вы можете использовать прокси-класс.
var myObj = {
someAttr: 'foo'
};
var p = new Proxy(myObj, {
get: function (target, methodOrAttributeName) {
// target is the first argument passed into new Proxy, aka. target is myObj
// First give the target a chance to handle it
if (Object.keys(target).indexOf(methodOrAttributeName) !== -1) {
return target[methodOrAttributeName];
}
// If the target did not have the method/attribute return whatever we want
// Explicitly handle certain cases
if (methodOrAttributeName === 'specialPants') {
return 'trousers';
}
// return our generic method_missing function
return function () {
// Use the special "arguments" object to access a variable number arguments
return 'For show, myObj.someAttr="' + target.someAttr + '" and "'
+ methodOrAttributeName + '" called with: ['
+ Array.prototype.slice.call(arguments).join(',') + ']';
}
}
});
console.log(p.specialPants);
// outputs: trousers
console.log(p.unknownMethod('hi', 'bye', 'ok'));
// outputs:
// For show, myObj.someAttr="foo" and "unknownMethod" called with: [hi,bye,ok]
Вы бы использовали p
вместо myObj
.
Вы должны быть осторожны с get
, потому что он перехватывает все запросы атрибутов p
. Таким образом, p.specialPants()
приведет к ошибке, потому что specialPants
возвращает строку, а не функцию.
То, что действительно происходит с unknownMethod
, эквивалентно следующему:
var unk = p.unkownMethod;
unk('hi', 'bye', 'ok');
Это работает, потому что функции являются объектами в JavaScript.
Если вы знаете ожидаемое количество аргументов, вы можете объявить их как нормальные в возвращаемой функции.
например:
...
get: function (target, name) {
return function(expectedArg1, expectedArg2) {
...
Насколько мне известно, но вы можете имитировать его, сначала инициализируя функцию до null
, а затем заменяя реализацию позже.
var foo = null;
var bar = function() { alert(foo()); } // Appear to use foo before definition
// ...
foo = function() { return "ABC"; } /* Define the function */
bar(); /* Alert box pops up with "ABC" */
Этот трюк похож на трюк С# для реализации рекурсивных lambdas, как описано здесь.
Единственным недостатком является то, что если вы используете foo
до его определения, вы получите сообщение об ошибке для попытки вызвать null
, как если бы это была функция, а не более описательное сообщение об ошибке. Но вы ожидаете получить сообщение об ошибке для использования функции до ее определения.