Класс javascript наследуется от класса Function

Мне нравится, что в javascript я могу создать функцию, а затем добавить дополнительные методы и атрибуты к этой функции

myInstance = function() {return 5}
myInstance.attr = 10

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

Другими словами, я хотел бы:
var myInstance = new myFunctionClass()
var x = myInstance()
// x == 5

Но я не знаю, как создать myFunctionClass. Я пробовал следующее, но он не работает:

var myFunctionClass = function() {Function.call(this, "return 5")}
myFunctionClass.prototype = new Function()
myInstance = new myFunctionClass()
myInstance()
// I would hope this would return 5, but instead I get
// TypeError: Property 'myInstance' of object #<Object> is not a function

Я также попробовал более сложный (и более правильный?) метод наследования, найденный здесь: Как правильно и quot; создать пользовательский объект в JavaScript?, не более удачи. Я также попытался использовать утилиту util.inherits(myFunctionClass, Function), найденную в node.js. Еще не повезло

Я исчерпал Google, и поэтому чувствую, что мне не хватает чего-то фундаментального или очевидного. Помощь будет принята с благодарностью.

Ответ 1

Ваша попытка наследовать от Function. Это правильная боль. Я предлагаю вам сделать следующее:

Живой пример

var Proto = Object.create(Function.prototype);
Object.extend(Proto, {
  constructor: function (d) {
    console.log("construct, argument : ", d);
    this.d = d; 
    // this is your constructor logic
  },
  call: function () {
    console.log("call", this.d);
    // this get called when you invoke the "function" that is the instance
    return "from call";
  },
  method: function () {
    console.log("method");
    // some method
    return "return from method";
  },
  // some attr
  attr: 42
});

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

var functionFactory = function (proto) {
  return function () {
    var f = function () {
      return f.call.apply(f, arguments);      
    };
    Object.keys(proto).forEach(function (key) {
      f[key] = proto[key];
    });
    f.constructor.apply(f, arguments);
    return f;
  }
}

Функция factory принимает объект-прототип и возвращает для него factory. Возвращенная функция при вызове даст вам новый объект функции, который "наследует" от вашего объекта-прототипа.

var protoFactory = functionFactory(proto);
var instance = protoFactory();

Здесь вы создаете свой factory, а затем создаете свой экземпляр.

Однако это не является правильным прототипом OO. мы просто мелкие копирующие свойства прототипа в новый объект. Поэтому изменения прототипа не отразятся на исходном объекте.

Если вам нужен настоящий прототип OO, вам нужно использовать хак.

var f = function () {
  // your logic here
};
f.__proto__ = Proto;

Обратите внимание, как мы используем нестандартную устаревшую .__proto__, и мы мутируем значение [[Prototype]] во время выполнения, которое считается злым.

Ответ 2

JS не позволяет конструктору возвращать функцию, даже если функции являются объектами. Таким образом, вы не можете создать экземпляр прототипа, который сам по себе является исполняемым. (Я прав в этом? Пожалуйста, исправьте, если я не, это интересный вопрос).

Хотя вы можете сделать factory функцию:

var makeCoolFunc = function() {
  var f = function() { return 5 };
  f.a = 123;
  f.b = 'hell yes!'
  return f;
};

var func = makeCoolFunc();
var x = func();

Ответ 3

Вы можете расширить Function и передать тело требуемой функции как String в супер-конструктор. Доступ к контексту функции можно получить с помощью arguments.callee.

Пример для наблюдаемого класса атрибута:

    export default class Attribute extends Function  {

    constructor(defaultValue){
        super("value", "return arguments.callee.apply(arguments);");
        this.value = defaultValue;
        this.defaultValue = defaultValue;
        this.changeListeners = [];
    }

    apply([value]){
        if(value!==undefined){
           if(value!==this.value){
               var oldValue = this.value;
               this.value=value;
               this.changeListeners.every((changeListener)=>changeListener(oldValue, value));
           }
        }
        return this.value;
    }

    clear(){
        this.value=undefined;
    }

    reset(){
        this.value=this.defaultValue;
    }

    addChangeListener(listener){
        this.changeListeners.push(listener);
    }

    removeChangeListener(listener){
        this.changeListeners.remove(listener);
    }

    clearChangeListeners(){
        this.changeListeners = [];
    }
}

Пример использования:

import Attribute from './attribute.js';

var name= new Attribute();
name('foo'); //set value of name to 'foo'

name.addChangeListener((oldValue, newValue)=>{
    alert('value changed from ' +oldValue+ ' to ' +newValue);
});
alert(name()); //show value of name: 'foo'

name('baa'); //set value of name to new value 'baa' and trigger change listener