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

Есть ли у Javascript что-то вроде функции Ruby method_missing?

В Ruby я думаю, что вы можете вызвать метод, который не был определен, и все же захватить имя вызываемого метода и обработать этот метод во время выполнения.

Может ли Javascript делать то же самое?

4b9b3361

Ответ 1

Функция 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/ для получения более подробной информации о предстоящей реализации.

Ответ 2

method_missing не подходит для JavaScript по той же причине, которой он не существует в Python: на обоих языках методы - это просто атрибуты, которые являются функциями; и объекты часто имеют общедоступные атрибуты, которые не подлежат вызову. Контраст с Ruby, где открытый интерфейс объекта - это 100% -ный метод.

Что нужно в JavaScript - это крючок, чтобы поймать доступ к отсутствующим атрибутам, независимо от того, являются они методами или нет. Python имеет это: см. __getattr__ специальный метод.

Предложение __ noSuchMethod__ от Mozilla внесло еще одну несогласованность в языке, на котором они пронизаны.

Вперед JavaScript - это механизм Прокси-сервер (также в ECMAscript Harmony), который ближе к протоколу Python для настройки доступа к атрибутам, чем к Ruby method_missing.

Ответ 4

Я создал библиотеку для 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

Ответ 5

Нет, в javascript нет возможности метапрограммирования, аналогично рубину method_missing. Интерпретатор просто вызывает ошибку, которую вызывающий код может уловить, но не может быть обнаружен объектом, к которому обращаются. Здесь есть некоторые ответы на определение функций во время выполнения, но это не одно и то же. Вы можете много метапрограммировать, изменять конкретные экземпляры объектов, определять функции, выполнять такие функциональные функции, как воспоминания и декораторы. Но нет динамического метапрограммирования отсутствующих функций, как в рубине или питоне.

Ответ 6

Я пришел к этому вопросу, потому что искал способ провалиться к другому объекту, если метод не присутствовал на первом объекте. Это не так гибко, как то, что вы просите - например, если отсутствует метод, то он будет терпеть неудачу.

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

Ответ 7

Вы можете использовать прокси-класс.

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) {
...

Ответ 8

Насколько мне известно, но вы можете имитировать его, сначала инициализируя функцию до 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, как если бы это была функция, а не более описательное сообщение об ошибке. Но вы ожидаете получить сообщение об ошибке для использования функции до ее определения.