I was trying to implement some kind of factory that create a class definition in the run time,I figure this work my code look like this
function JsClassFactory() {
this.X = class {
};
this.addMethod = (name, def) => {
let Y = class extends this.X {
constructor() {
super()
this[name] = def
}
}
this.X = Y
return this;
}
this.addAttribute = (name, def) => {
return this.addMethod(name, def)
}
this.getClass = function () {
return this.X
};
}
(function () {
const cf = new JsClassFactory()
cf.addAttribute('age', 35)
cf.addAttribute('name', 'chehimi')
cf.addMethod('myName', function () {
console.log(' i am %s', this.name)
})
cf.addMethod('myAge', function () {
console.log('my age is', this.age)
})
let z = new (cf.getClass())
z.myName();
z.myAge()
cf.addAttribute('name', 'ali')
cf.addAttribute('age', 15)
let t = new (cf.getClass())
t.myName();
t.myAge()
})()
i am asking if there is a better a way to implement this feature?
or a better work arround,
Here is the approach I would take to accomplish this.
class JSClassBuilder {
constructor(){
this.constructedClass = class {};
this.constructedClassAttributes = {};
}
addStatic(name, definition){
this.constructedClass[name] = definition;
return this;
}
addAttribute(name, value){
if(typeof definition != 'function'){
this.constructedClassAttributes[name] = value;
return this;
}
throw new TypeError("parameter value must not be of type: 'function'");
}
addMethod(name, definition){
if(typeof definition == 'function'){
this.constructedClass.prototype[name] = definition;
return this;
}
throw new TypeError("parameter definition must be of type: 'function'");
}
constructClass(){
return Object.assign(
new this.constructedClass(),
this.constructedClassAttributes
);
}
}
const classBuilder = new JSClassBuilder();
const c = classBuilder
.addAttribute('age', 35)
.addAttribute('name', 'Chehimi')
.addMethod('myName', function () {
console.log('I am %s', this.name);
})
.addMethod('myAge', function () {
console.log('I am %s', this.age);
})
.constructClass();
c.myName();
c.myAge();
const a = classBuilder
.addAttribute('name', 'Ali')
.addAttribute('age', 15)
.constructClass();
a.myName();
a.myAge();
Related
So i have this code:
function Class1() {
this.i = 1;
var that=this;
function nn() {
return 21;
}
this.aa = function() {
nn();
};
this.bb = function() {
this.aa();
};
this.cc = function() {
this.bb();
};
}
var o = new Class1();
var b=o.cc();
alert(b); //undefined
But when the alert is fired, I get an undefined error and not 21, Does the private method can not use a return? Thanks!
When using the function() {} syntax to define a function, you always explicitly need to return the value, i.e. not only from nn, but from all intermediate functions as well.
function Class1() {
this.i = 1;
var that = this;
function nn() {
return 21;
}
this.aa = function() {
return nn();
}
this.bb = function() {
return this.aa();
}
this.cc = function() {
return this.bb();
}
}
var o = new Class1();
var b = o.cc();
alert(b); // "21"
Apart from the answer above, the 'this' context seems weird in your functions. Maybe you are better of with arrow functions if you dont want to bind the this context to each function. I also think that it is better to actually separate private and public functions when using a 'class' like this.
function Class1() {
var _nn = function () {
return 21;
}
var _aa = function () {
return _nn();
}
var _bb = function () {
return _aa();
}
var cc = function () {
return _bb();
};
return {
cc
};
}
var o = new Class1();
var a = o.cc();
console.log(a);
Much easier to understand that it is only cc that is a public function.
So with arrow function it would instead look like this, and you can use the Class1 this context inside of your private functions without doing
var that = this; or using bind.
function Class1() {
this.privateThing = 'private';
var _nn = () => { return this.privateThing; };
var _aa = () => { return _nn(); };
var _bb = () => { return _aa(); };
var cc = () => { return _bb(); };
return {
cc
};
}
I wrote an angular filter with typescript which works fine until I minify the source code.
Here is the filter:
module App.Test {
export interface IGroupingFilter extends ng.IFilterService {
(name:"grouping-filter"): (collection:any[]) => collection:any[];
}
class GroupingFilter {
static $inject:string[] = ["underscore"];
static ConvertDateTime(item:any):number {
var time = "" + item.time;
var newTime = (time.length == 3) ? "0" + time : time;
return +(item.pickupDate.replace(/\-/g, '') + newTime);
}
public static Factory(underscore:UnderscoreStatic) {
return underscore.memoize((collection:any[]) => {
var groupKey = "id";
var group:any = underscore.groupBy(collection, (item:any) => {
return item[groupKey];
});
var grpArray = [];
angular.forEach(group, (item) => {
grpArray.push({
"groupKey": item[0][groupKey],
"items": item
});
});
var grpArraySorted = underscore.sortBy(grpArray, (grpObj:any) => {
var min:any = underscore.min(grpObj.items, (item:any) => {
return GroupingFilter.ConvertDateTime(item);
});
return GroupingFilter.ConvertDateTime(min);
});
return grpArraySorted;
});
}
}
angular.module("app").filter("groupingFilter", GroupingFilter.Factory);
}
Here is the minified version:
var App;
!function (t) {
var e;
!function (t) {
var e = function () {
function t() {
}
return t.ConvertDateTime = function (t) {
var e = "" + t.time, r = 3 == e.length ? "0" + e : e;
return +(t.pickupDate.replace(/\-/g, "") + r)
}, t.Factory = function (e) {
return e.memoize(function (r) {
var n = "id", i = e.groupBy(r, function (t) {
return t[n]
}), o = [];
angular.forEach(i, function (t) {
o.push({groupKey: t[0][n], items: t})
});
var a = e.sortBy(o, function (r) {
var n = e.min(r.items, function (e) {
return t.ConvertDateTime(e)
});
return t.ConvertDateTime(n)
});
return a
})
}, t.$inject = ["underscore"], t
}();
angular.module("app").filter("groupingFilter", e.Factory)
}(e = t.Test || (t.Test = {}))
}(App || (App = {}));
Here is the angular error message
Error: [$injector:unpr] Unknown provider: eProvider <- e <-
groupingFilterFilter
Many thanks
The reason it does not work when minified is that you inject "underscore" into the FooFilter class not the actual filter, which is the result of FooFilter.Factory. To create such a simple filter you don't really need a class, just pass a simple function.
angular.module('app').filter('fooFilter', fooFilter);
fooFilter.$inject = ['underscore'];
function fooFilter(underscore) {
return underscore.memoize((collection:any[]) => {
return underscore.shuffle(collection);
});
}
If you really want to write the filter factory function as a static class method, you could use the array syntax like this:
angular.module("app")
.filter("groupingFilter", ['underscore', GroupingFilter.Factory]);
Remove the $inject array from your class in this case.
My "base class" does not seem to be populating correctly. Why?
<script type="text/javascript">
var exceptions = {
NotImplementedException: function (message) {
this.name = 'NotImplementedException';
this.message = message || 'Property or method is not implemented.';
}
};
exceptions.NotImplementedException.prototype = Error.prototype;
function ActionButton() {
this.execute = function () {
throw new exceptions.NotImplementedException("Execute is not implemented.");
};
this.render = function (data) {
throw new exceptions.NotImplementedException("Render is not implemented.");
};
this.$template = function () {
throw new exceptions.NotImplementedException("$template is not implemented.");
};
};
function ImageActionButton() {
this.image = { url: '' };
};
function TextActionButton() {
this.text = '';
};
function StandardActionButton() {
this.text = '';
this.image = { url: '' };
};
function MenuActionButton() {
this.buttons = [];
};
ImageActionButton.prototype = new ActionButton();
ImageActionButton.prototype.constructor = ImageActionButton;
TextActionButton.prototype = new ActionButton();
TextActionButton.prototype.constructor = TextActionButton;
StandardActionButton.prototype = new ActionButton();
StandardActionButton.prototype.constructor = StandardActionButton;
MenuActionButton.prototype = new ActionButton();
MenuActionButton.prototype.constructor = MenuActionButton;
// This fails
if (ImageActionButton.prototype != ActionButton) {
alert("ImageActionButton prototype is not ActionButton!");
}
// This works
if (ImageActionButton.prototype.constructor != ImageActionButton) {
alert("ImageActionButton prototype.constructor is not ImageActionButton!");
}
</script>
I think you'll want to use instanceof instead of comparing like you are.
if (ImageActionButton instanceof ActionButton) {
alert("ImageActionButton prototype is not ActionButton!");
}
This is a question for the guru of JavaScript. I'm trying to do work with JavaScript prototype model more elegant. Here is my utility code (it provides real chain of prototypes and correct work with instanceof operator):
function Class(conf) {
var init = conf.init || function () {};
delete conf.init;
var parent = conf.parent || function () {};
delete conf.parent;
var F = function () {};
F.prototype = parent.prototype;
var f = new F();
for (var fn in conf) f[fn] = conf[fn];
init.prototype = f;
return init;
};
It allows me to do such thigns:
var Class_1 = new Class({
init: function (msg) { // constructor
this.msg = msg;
},
method_1: function () {
alert(this.msg + ' in Class_1::method_1');
},
method_2: function () {
alert(this.msg + ' in Class_1::method_2');
}
});
var Class_2 = new Class({
parent: Class_1,
init: function (msg) { // constructor
this.msg = msg;
},
// method_1 will be taken from Class_1
method_2: function () { // this method will overwrite the original one
alert(this.msg + ' in Class_2::method_2');
},
method_3: function () { // just new method
alert(this.msg + ' in Class_2::method_3');
}
});
var c1 = new Class_1('msg');
c1.method_1(); // msg in Class_1::method_1
c1.method_2(); // msg in Class_1::method_2
var c2 = new Class_2('msg');
c2.method_1(); // msg in Class_1::method_1
c2.method_2(); // msg in Class_2::method_2
c2.method_3(); // msg in Class_2::method_3
alert('c1 < Class_1 - ' + (c1 instanceof Class_1 ? 'true' : 'false')); // true
alert('c1 < Class_2 - ' + (c1 instanceof Class_2 ? 'true' : 'false')); // false
alert('c2 < Class_1 - ' + (c2 instanceof Class_1 ? 'true' : 'false')); // true
alert('c2 < Class_2 - ' + (c2 instanceof Class_2 ? 'true' : 'false')); // true
My question is: Is there more simple way to do this?
Yes, there is a better way to do this.
var call = Function.prototype.call;
var classes = createStorage(),
namespaces = createStorage(),
instances = createStorage(createStorage);
function createStorage(creator){
var storage = new WeakMap;
creator = typeof creator === 'function' ? creator : Object.create.bind(null, null, {});
return function store(o, v){
if (v) {
storage.set(o, v);
} else {
v = storage.get(o);
if (!v) {
storage.set(o, v = creator(o));
}
}
return v;
};
}
function Type(){
var self = function(){}
self.__proto__ = Type.prototype;
return self;
}
Type.prototype = Object.create(Function, {
constructor: { value: Type,
writable: true,
configurable: true },
subclass: { value: function subclass(scope){ return new Class(this, scope) },
configurable: true,
writable: true }
});
function Class(Super, scope){
if (!scope) {
scope = Super;
Super = new Type;
}
if (typeof Super !== 'function') {
throw new TypeError('Superconstructor must be a function');
} else if (typeof scope !== 'function') {
throw new TypeError('A scope function was not provided');
}
this.super = Super;
this.scope = scope;
return this.instantiate();
}
Class.unwrap = function unwrap(Ctor){
return classes(Ctor);
};
Class.prototype.instantiate = function instantiate(){
function super_(){
var name = super_.caller === Ctor ? 'constructor' : super_.caller.name;
var method = Super.prototype[name];
if (typeof method !== 'function') {
throw new Error('Attempted to call non-existent supermethod');
}
return call.apply(method, arguments);
}
var Super = this.super,
namespace = namespaces(Super),
private = instances(namespace)
var Ctor = this.scope.call(namespace, private, super_);
Ctor.__proto__ = Super;
Ctor.prototype.__proto__ = Super.prototype;
namespaces(Ctor, namespace);
classes(Ctor, this);
return Ctor;
}
example usage:
var Primary = new Class(function(_, super_){
var namespace = this;
namespace.instances = 0;
function Primary(name, secret){
this.name = name;
_(this).secret = secret;
namespace.instances++;
}
Primary.prototype.logSecret = function logSecret(label){
label = label || 'secret';
console.log(label + ': ' + _(this).secret);
}
return Primary;
});
var Derived = Primary.subclass(function(_, super_){
function Derived(name, secret, size){
super_(this, name, secret);
this.size = size;
}
Derived.prototype.logSecret = function logSecret(){
super_(this, 'derived secret');
}
Derived.prototype.exposeSecret = function exposeSecret(){
return _(this).secret;
}
return Derived;
});
var Bob = new Derived('Bob', 'is dumb', 20);
Bob.logSecret();
console.log(Bob);
console.log(Bob.exposeSecret());
After some research I've concluded there is no more simple way to do this.
Is there a way to specify something similar to the following in javascript?
var c = {};
c.a = function() { }
c.__call__ = function (function_name, args) {
c[function_name] = function () { }; //it doesn't have to capture c... we can also have the obj passed in
return c[function_name](args);
}
c.a(); //calls c.a() directly
c.b(); //goes into c.__call__ because c.b() doesn't exist
Mozilla implements noSuchMethod but otherwise...no.
No, not really. There are some alternatives - though not as nice or convenient as your example.
For example:
function MethodManager(object) {
var methods = {};
this.defineMethod = function (methodName, func) {
methods[methodName] = func;
};
this.call = function (methodName, args, thisp) {
var method = methods[methodName] = methods[methodName] || function () {};
return methods[methodName].apply(thisp || object, args);
};
}
var obj = new MethodManager({});
obj.defineMethod('hello', function (name) { console.log("hello " + name); });
obj.call('hello', ['world']);
// "hello world"
obj.call('dne');
Almost 6 years later and there's finally a way, using Proxy:
const c = new Proxy({}, {
get (target, key) {
if (key in target) return target[key];
return function () {
console.log(`invoked ${key}() from proxy`);
};
}
});
c.a = function () {
console.log('invoked a()');
};
c.a();
c.b();
No.