Есть ли в Javascript что-то вроде метода method_missing в Ruby?

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

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

7 ответов

Решение

Объясняемая вами рубиновая функция называется "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__.

Предложение Mozilla " __noSuchMethod__ " внесло еще одно несоответствие в язык, изобилующий ими.

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

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

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

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

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

Я думал сделать это для небольшой библиотеки, которая помогает настроить объекты extjs таким образом, чтобы сделать их более тестируемыми. У меня были отдельные вызовы, чтобы на самом деле получить объекты для взаимодействия, и я подумал, что это может быть хорошим способом склеить эти вызовы, эффективно возвращая расширенный тип

Я могу придумать два способа сделать это:

Прототипы

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

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

    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/, чтобы показать, что он работает с такими вещами.

Вот моды:

    //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 где-то там, но это, кажется, не нужно.

Не насколько мне известно, но вы можете имитировать его, инициализируя функцию 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" */

Этот прием похож на прием C# для реализации рекурсивных лямбд, как описано здесь.

Единственным недостатком является то, что если вы используете foo прежде чем он будет определен, вы получите сообщение об ошибке при попытке вызвать null как будто это была функция, а не более описательное сообщение об ошибке. Но вы ожидаете получить сообщение об ошибке при использовании функции до ее определения.

Другие вопросы по тегам