How can we dynamically/programmatically extend a javascript class?
More concretely, given something like
class Polygon {
constructor(area, sides) {
this.area = area;
this.sides = sides;
}
}
const Rectangle = extend(Polygon, (length, width) => {
super(length * width, 4);
this.length = length;
this.width = width;
});
how can we implement something like extend such that it behaves the same as
class Rectangle extends Polygon {
constructor(length, width) {
super(length * width, 4);
this.length = length;
this.width = width;
}
}
?
There are three problems here:
(1) super is only available inside object methods, so there is no way to access super in an arrow function. That needs to be somehow replaced by a regular function call.
(2) Classes can only be constructed, not called (unlike functions acting as constructors). Therefore you cannot just .call the classes constructor onto the "subclass" instance. You have to create an instance of the superclass and copy that into the subclass, eventually loosing getters / setters.
(3) Arrow functions have a lexical this, so you cannot access the instance with this inside an arrow function.
Given these three problems, a viable alternative would be:
function extend(superclass, constructor) {
function Extended(...args) {
const _super = (...args) => Object.assign(this, new superclass(...args));
constructor.call(this, _super, ...args);
}
Object.setPrototypeOf(Extended, superclass);
Object.setPrototypeOf(Extended.prototype, superclass.prototype);
return Extended;
}
const Rectangle = extend(Polygon, function(_super, length, width) {
_super(/*...*/);
/*...*/
});
But honestly ... what's wrong with the native class ... extends ?
After some hacking around, I've found that this horrifyingly works.
function extend(superclass, construct) {
return class extends superclass {
constructor(...args) {
let _super = (...args2) => {
super(...args2)
return this;
};
construct(_super, ...args);
}
};
}
const Rectangle = extend(Polygon, function(_super, length, width) {
let _this = _super(length * width, 4);
_this.length = length;
_this.width = width;
});
class A {
m () {
console.log('A')
}
}
class B extends A {
m () {
console.log('B')
}
}
var a = new A()
var b = new B()
a.m()
b.m()
const MixinClass = superclass =>
class extends superclass {
m () {
console.log('extended')
}
}
const extendsAnyClass = AnyClass =>
class MyMixinClass extends MixinClass(AnyClass) {}
var AA = extendsAnyClass(A)
var BB = extendsAnyClass(B)
var aa = new AA()
var bb = new BB()
aa.m()
bb.m()
This worked for me:
const extend = SuperClass => class E extends SuperClass {
constructor() {
super('E')
}
}
class A {
constructor(arg) {
console.log('Hello from ' + arg)
}
}
class B {
constructor(arg) {
console.log('Hi from ' + arg)
}
}
const C = extend(A)
const D = extend(B)
new C() // Hello from E
new D() // Hi from E
This will dynamically extend E for any given parent class
https://jsfiddle.net/1s0op2ng/
Related
i was writting a greedy snack game ,but i found that the class GreedySnack has too many big functions.so i want put the functions in other files and import them into the class file. but i jst dont know how to do.
part of the codes:
class GreedySnake {
constructor() {
this.canvas = document.querySelector("#snake");
this.ctx = this.canvas.getContext("2d");
this.maxX = 64; // max row
this.maxY = 40; // max col
this.itemWidth = 10; // size of the point
this.direction = "right"; // up down right left direction
this.speed = 150; // ms speed
this.isStop = false; //
this.isOver = false; //
this.isStart = false; //
this.score = 0; //
this.timer = null; // movement timer
this.j = 1;
this.canChange = true;
this.grid = new Array();
for (let i = 0; i < this.maxX; i++) {
for (let j = 0; j < this.maxY; j++) {
this.grid.push([i, j]);
}
}
// this.drawGridLine();
this.getDirection();
}
// create the body
createSnake() {
this.snake = [
[4, 25],
[3, 25],
[2, 25],
[1, 25],
[0, 25],
];
}
// movement
move() {
...
// if the next-step is not food
...
}
// start
start() {
...
}
// pause
stop() {
...
}
}
i've tired the require
createPos = require("./food");
createFood = require("./food");
snackClass.js:29 Uncaught ReferenceError: require is not defined
You can use functions defined elsewhere as functions in your class, although they won't be methods in the ES2015+ sense (specifically: they can't use super). They'll still be "methods" in the general sense that you call them on the instance and they work just like real methods. (They'll be like the methods we used to have before ES2015's class syntax.) You can even set them up using modern property syntax.
Here's a simple example:
// The functions declared elsewhere
function start() {
console.log(`Doing a start operation for ${this.name}`);
}
function stop() {
console.log(`Doing a stop operation for ${this.name}`);
}
// The class
class Example {
constructor(name) {
this.name = name;
}
// Using property syntax to set up the `start` and `stop` "methods"
start = start;
stop = stop;
}
// Using it
const e = new Example("test");
e.start();
e.stop();
Unlike true methods, those:
Will be "own" properties of each instance, not prototype properies
Will be enumerable
You could fix #1 by assigning them to your constructor functions' prototype object instead:
// The functions declared elsewhere
function start() {
console.log(`Doing a start operation for ${this.name}`);
}
function stop() {
console.log(`Doing a stop operation for ${this.name}`);
}
// The class
class Example {
constructor(name) {
this.name = name;
}
}
Object.assign(Example.prototype, { start, stop });
// Using it
const e = new Example("test");
e.start();
e.stop();
You can fix #2 by using Object.defineProperty (or Object.defineProperties):
// The functions declared elsewhere
function start() {
console.log(`Doing a start operation for ${this.name}`);
}
function stop() {
console.log(`Doing a stop operation for ${this.name}`);
}
// The class
class Example {
constructor(name) {
this.name = name;
}
}
for (const [name, method] of Object.entries({start, stop})) {
Object.defineProperty(Example.prototype, name, {
value: method,
writable: true,
configurable: true,
})
}
// Using it
const e = new Example("test");
e.start();
e.stop();
I am new to Object Orientated Programming, please have in mind. I have understood how the first part shown here works (it does):
function Car() {
var __registration;
var setReg = function(val) {
__registration= val;
}
var getReg= function() {
return __registration;
}
return {
setReg: setReg ,
getReg: getReg
}
}
var myCar = new Car();
myCar.setReg("LSKM5215");
alert(myCar.getReg() ); //ALERTS LSKM5215
But when trying to manage inheritance on this way of Object Orientated Programming, it just fails once and again:
function Extras(){
var __sound;
var setSound= function(val) {
__sound= val;
}
var getSound= function() {
return __sound;
}
return {
setSound: setSound,
getSound: getSound
}
}
Extras.prototype = new Car();
myCar.setSound("SUPERB SOUNDSYSTEM 2.2"); //TypeError: myCar.setSound is not a function
How could I create inheritance on this case? To make Car() get the private variables about the "soundsystem extras"?
Very grateful.
You not need return when plan use function as constructor.
in derived class you should call base constructor with needed parameters.
in derived class assign proptotype based on base prototype.
Something like this:
function Car() {
var __registration;
this.setReg = function(val) {
__registration = val;
}
this.getReg = function() {
return __registration;
}
}
function Extras() {
Car.call(this);
var __sound;
this.setSound = function(val) {
__sound = val;
}
this.getSound = function() {
return __sound;
}
}
Extras.prototype = Object.create(Car.prototype);
myExtras = new Extras();
myExtras.setReg("LSKM5215");
myExtras.setSound("SUPERB SOUNDSYSTEM 2.2");
document.write("<div>Extras: reg - ", myExtras.getReg(), '</div>');
document.write("<div>Extras: sound - ", myExtras.getSound(), '</div>');
And for ES2015 you can use classes
class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
get area() {
return this.calcArea();
}
calcArea() {
return this.height * this.width;
}
}
class Car {
constructor() {
this.__registration = undefined;
}
set Reg(val) {
this.__registration = val;
}
get Reg() {
return this.__registration;
}
}
class Extras extends Car {
constructor() {
super();
this.__sound = undefined;
}
set Sound(val) {
this.__sound = val;
}
get Sound() {
return this.__sound;
}
}
myExtras = new Extras();
myExtras.Reg = ("LSKM5215");
myExtras.Sound = ("SUPERB SOUNDSYSTEM 2.2");
document.write("<div>Extras: reg - ", myExtras.Reg, '</div>');
document.write("<div>Extras: sound - ", myExtras.Sound, '</div>');
I have some classes that share identical methods and are distinguished only by a few static (aka class) variables. My thought is to put the common methods into a base class that accesses the static variables.
Here is a solution that works, but it seems like a real cough kludge. Is there a better / more idiomatic way to do this?
"use strict";
// common code
function Base() { }
Base.prototype.f1 = function() {
console.log(Object.getPrototypeOf(this).constructor.VAR1); // this feels really really wrong!
}
Base.prototype.f2 = function() {
console.log(Object.getPrototypeOf(this).constructor.VAR2); // ditto
}
// specialization A
function SubA() { Base.call(this); }
SubA.prototype = Object.create(Base.prototype);
SubA.prototype.constructor = SubA;
SubA.VAR1 = "suba v1";
SubA.VAR2 = "suba v2";
// specialization B
function SubB() { Base.call(this); }
SubB.prototype = Object.create(Base.prototype);
SubB.prototype.constructor = SubB;
SubB.VAR1 = "subb v1";
SubB.VAR2 = "subb v2";
This works as expected:
> var a = new SubA();
> var b = new SubB();
> a.f1()
suba v1
undefined
> b.f2()
subb v2
undefined
an alternative
Of course I could write methods to encapsulate the differences between SubA and SubB. The syntax is less tortured, but it still feels wrong to write methods that are essentially behaving like static variables:
"use strict";
function Base() { }
Base.prototype.f1 = function() {
console.log(this.getVar1());
}
Base.prototype.f2 = function() {
console.log(this.getVar2());
}
function SubA() { Base.call(this); }
SubA.prototype = Object.create(Base.prototype);
SubA.prototype.constructor = SubA;
SubA.prototype.getVar1 = function() { return 'suba v1'; }
SubA.prototype.getVar2 = function() { return 'suba v2'; }
function SubB() { Base.call(this); }
SubB.prototype = Object.create(Base.prototype);
SubB.prototype.constructor = SubB;
SubB.prototype.getVar1 = function() { return 'subb v1'; }
SubB.prototype.getVar2 = function() { return 'subb v2'; }
> var a = new SubA();
> var b = new SubB();
> a.f1()
suba v1
undefined
> b.f2()
subb v2
undefined
Is there a particular reason to put VAR1 and VAR2 in the class objects themselves rather than in their prototypes? If not, things become much simpler:
function Base() { }
Base.prototype.f1 = function() {
console.log(this.VAR1);
};
Base.prototype.f2 = function() {
console.log(this.VAR2);
};
// specialization A
function SubA() { Base.call(this); }
SubA.prototype = Object.create(Base.prototype);
SubA.prototype.constructor = SubA;
SubA.prototype.VAR1 = "suba v1";
SubA.prototype.VAR2 = "suba v2";
// specialization B
function SubB() { Base.call(this); }
SubB.prototype = Object.create(Base.prototype);
SubB.prototype.constructor = SubB;
SubB.prototype.VAR1 = "subb v1";
SubB.prototype.VAR2 = "subb v2";
The above code passes your tests.
Can someone write an example, of OOP JS code, i assume with annotations, where JetBrains IDEs can recognize inheritance?
e.g.
class Animal:
prop: weight
method: run()
class Cat extends Animal:
prop: name
method: eat()
so i want that Webstorm/PHPStorm autocompletes and shows info (ctrl+q) for such things:
Cat.prototype.eat= function(){
this.weight; //should be recognized as inherited property
}
var cat = new Cat();
cat.run(); //should be recognized as inherited method
What is the best way?
the following will work without any annotations:
var Animal = function () {
this.weight = 0;
this.run = function () {
}
return this;
};
var Cat = function () {
this.name = "Tom";
this.eat = function () {
return "Nyam"
}
return this;
}
Cat.prototype = new Animal();
var cat = new Cat();
cat.run()
this way also works:
function Shape() {
this.x = 0;
this.y = 0;
}
// superclass method
Shape.prototype.move = function(x, y) {
this.x += x;
this.y += y;
console.info('Shape moved.');
};
// Rectangle - subclass
function Rectangle() {
Shape.call(this); // call super constructor.
}
// subclass extends superclass
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var rect = new Rectangle();
Plus, you can use JSDoc here - see http://usejsdoc.org/tags-augments.html, for example
I am experimenting with ES6. In particular, classes and inheritance. In class Apple, it extends Polygon. I want to extend Polygon's method sayName() and have it go to console.log.
When I run it through traceur, I get undefined for console.log(foo);
class Polygon {
constructor(height, width) { //class constructor
this.name = 'Polygon';
this.height = height;
this.width = width;
}
sayName() { //class method
return 'Hi, I am a', this.name + '.';
}
}
class Apple extends Polygon {
constructor(length) {
super(length, length); //call the parent method with super
this.name = 'apple';
}
sayName() {
var foo = super();
console.log(foo);
}
}
let d = new Apple(5);
d.sayName();
Traceur:
System.register("class", [], function() {
"use strict";
var __moduleName = "class";
function require(path) {
return $traceurRuntime.require("class", path);
}
var Polygon = function Polygon(height, width) {
this.name = 'Polygon';
this.height = height;
this.width = width;
};
($traceurRuntime.createClass)(Polygon, {sayName: function() {
return 'Hi, I am a', this.name + '.';
}}, {});
var Apple = function Apple(length) {
$traceurRuntime.superConstructor($Apple).call(this, length, length);
this.name = 'apple';
};
var $Apple = Apple;
($traceurRuntime.createClass)(Apple, {sayName: function() {
var foo = $traceurRuntime.superConstructor($Apple).call(this);
console.log(foo);
}}, {}, Polygon);
var d = new Apple(5);
d.sayName();
return {};
});
System.get("class" + '');
How can I super sayName() in Apple class and make the console.log(foo) show the value?
I thought traceur would show me the compiled code, but it is not really. For instance, $traceurRuntime.createClass() isn't helping me see how it is creating these constructors. Am I using traceur incorrectly to view the compiled code?
super refers to the class/constructor, not to the method from which it is called. Therefore, if you want to call the parent function from within sayName(), you will have to write it like this:
sayName() {
var foo = super.sayName();
console.log(foo);
}