Можно ли создать функцию с другим прототипом, чем Function.prototype? - PullRequest
5 голосов
/ 27 февраля 2011

Я работаю над библиотекой комбинатора синтаксического анализа в JavaScript.Для этого я хочу создать функции, которые можно вызывать, как и любые другие функции, но также иметь функции-члены, которые можно вызывать по очереди для получения вывода на основе функции, к которой они присоединены (например, комбинаторы).

IКонечно, я могу добавить членов к функциям следующим образом:

//the functions I want to add additional members to
function hello(x) {
    return "Hello " + x;
}

function goodbye(x) {
    return "Goodbye " + x;
}

//The function I want as a member of the above functions.
//it creates another function based on the function it is 
//attached to.
function double() { 
    var that = this;
    return function(x) {
        return that(x) + ", " + that(x);
    };
}

//I can attach them manually to the function objects:
hello.double = double;
//calling hello.double()('Joe') results in => "Hello Joe, Hello Joe"
goodbye.double = double;
//calling goodbye.double()('Joe') results in => "Goodbye Joe, Goodbye Joe"

Я мог бы создать функцию, которая расширяет все мои функции с помощью члена double, но я должен помнить, чтобы вызывать его каждый раз, когда я создаюHey, Sayonara и т. Д.Также мои функции приветствия будут иметь все эти члены каждый, непосредственно в объекте функции, для каждого экземпляра.Я бы предпочел поместить их всех в один прототип и сделать его прототипом всех моих функций приветствия.Следующие опции также не работают:

  • замена hello.__proto__ (нестандартный, не работает во всех браузерах)
  • изменение Function.prototype напрямую (добавит этих членов ввсе остальные функции, но они не имеют смысла там - я хочу вызывать double только для набора моих собственных функций)

Возможно ли даже дать объекту функции пользовательскийпрототип или я застрял с изменением каждого создаваемого объекта функции?


Обновление : я изменил приведенный выше пример, чтобы он больше походил на реальную проблему, над которой я работаю.Речь идет о модификации функциональных объектов не обычных объектов.Конечная цель состоит в том, чтобы включить удобный синтаксис для комбинаторов синтаксического анализа, например (очень упрощенный):

//parses one argument
var arg = …
//parses one function name
var name = …
//parses a function call, e.g. foo(x+y, "test", a*2)
var functionCall = name.then(elem("(")).then(arg.repSep(",")).then(")").into(process(…))

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

Ответы [ 4 ]

2 голосов
/ 14 октября 2014

Да, вы можете, используя setPrototypeOf.

См. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf.

function prototype() { }
prototype.double = function() { console.log("double"); };

function myfunc() { }
Object.setPrototypeOf(myfunc, prototype);

myfunc.double();

Это по сути стандартная версия __proto__.

Тамнет другого способа напрямую создать функцию с прототипом, отличным от Function.prototype.Рассмотрим:

function makeFunc() {
    return function x() { };
}
function protofunc { }
protofunc.double = { }...
makeFunc.prototype = protofunc;
new makeFunc()

К сожалению, при возврате функции из конструктора makeFunc вместо значения по умолчанию this makeFunc.prototype не применяется.

Или:

myFunc = Object.create(protofunc, ???);

Обычно мы думаем об использовании Object.create для создания объекта с прототипом, но в этом случае нет способа указать функцию как объект.

Итог:Единственной альтернативой является явная установка прототипа, что можно сделать с помощью __proto__ или setPrototypeOf.Это особенность ES6.Нормальный браузер поддержки предостережения применяются.См http://kangax.github.io/compat-table/es6/.

1 голос
/ 02 апреля 2011

Хотя я знаю, что вы просили решение без использования Function.prototype, мне интересно, считали ли вы, что вы можете связать условно-выполняемые поведения таким образом, чтобы функция Function.prototype, такая как double (), позволяла сосуществовать различнымповедение, зависящее от экземпляра "класса" ...

// This could also be adapted to itself be on Function.prototype
function chainConditions () {
    var args = arguments;
    return function () {
        for (var i=0, argl = args.length; i < argl; i++) {
            var ret = args[i].apply(this, arguments);
            if (typeof (ret) !== 'undefined') { // If you want to allow undefined to be returnable, you could instead add this to a try-catch and ignore those which throw exceptions or a certain exception
                return ret;
            }
        }
    };
}
Function.prototype.double = function () {
    if (this.prototype instanceof Salutation) {
        var that = this;
        return function(x) {
            return that(x) + ", " + that(x);
        };
    }
};
Function.prototype.double = chainConditions(
    function () {
        if (this.prototype instanceof Tennis) {
            var that = this;
            return function(x) {
                return that(x) + ", " + that(x);
            };
        }
    },
    Function.prototype.double
);


function Salutation () {}

function Hello(x) {
    return "Hello " + x;
}

function Goodbye(x) {
    return "Goodbye " + x;
}
Goodbye.prototype = new Salutation();


function Tennis () {
}
function TennisPlayer (x) {   
    return x + ' serve';
}
TennisPlayer.prototype = new Tennis();



alert(TennisPlayer.double()('Forehand')); // Forehand serve, Forehand serve

alert(Goodbye.double()('Yellow Brick Road')); // Goodbye Yellow Brick Road, Goodbye Yellow Brick Road

alert(Hello.double()('Yellow Brick Road')); // does not work as we did not use Hello.prototype = new Salutation();

Необходимо уметь абстрагировать это еще дальше, разрешив использовать одну функцию для проверки типа класса (например, сгруппировать все методы Salutation в функции.prototype вместе как одна проверка типа), а другой - для приема списка методов, связанных с их условным поведением.

Хотя он использует собственный API, он довольно ненавязчив и может даже работать ссуществующие методы в Function.prototype, которые не подыгрывают, выдавая исключение (просто убедитесь, что последние любые существующие методы включены последними). ​​

0 голосов
/ 02 апреля 2011

Краткий ответ на ваш вопрос будет: Да.

function Parent() {
    // constructor
}

function Child() {
    // constructor
}

Child.prototype = new Parent();

Вот как добавить методы в класс Child:

Child.prototype.someMethod = function() {
    // do something
}

К сожалению, я долгое время использовал Javascript объектно-ориентированным способом, но я считаю, что есть некоторые решения, чтобы сделать синтаксис немного более понятным.

0 голосов
/ 28 февраля 2011

Пара разных опций. Я думаю, что вы должны думать с точки зрения объектов и наследования, а не функций и глобалов. В приведенном выше примере вы подразумеваете, что хотите, чтобы «мир» наследовал набор методов приветствия. (Или вы хотите, чтобы "привет" унаследовал "мир"?)

Вы должны начать с чтения этой статьи здесь: http://www.crockford.com/javascript/inheritance.html

Вот простой код, демонстрирующий наследование с использованием «прототипного наследования»

// ---------------
function inherit (obj, base) {
    var tmp = function () { };
    tmp.prototype = base.prototype;
    obj.prototype = new tmp();
    obj.prototype.constructor = obj;
};

function HelloSet() {
    ;
}

HelloSet.prototype.helloA = function() {
    return "Hello A " + this.displayName;
};

HelloSet.prototype.helloB = function() {
    return "Hello B " + this.displayName;
};

function World(displayName) {
    HelloSet.constructor.call(this); // constructor chaining
    this.displayName = displayName;
}

inherit(World, HelloSet);  // World now has all the methdods of HelloSet
// ---------------

// now let's create some worlds
var earth = new World("Earth");
var mars = new World("Mars");

earth.helloA();  // returns "Hello A Earth"
mars.helloB();  // returns "Hello B Mars"
...