Класс 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