I have a code where a function is used to modify the existing functions and return a new function reference. I want that function to be applied to specific methods of a class.
My current code is
function modifyMethod(func) {
return function() {
console.log('working');
return func.apply(this, arguments);
};
}
function modifyClassMethods(ClassName, methodArray) {
// The code goes here
return ClassName;
}
class Temp {
hi() {
console.log("hi method");
}
}
Temp = modifyClassMethods(Temp, ["hi"]);
const temp = new Temp();
// This should print
//
// working
// hi method
temp.hi();
When I try to call the method modifyMethod with Temp.hi, func is undefined. If I create an object and then modify the method, then the new method will be applied only to the method of that particular object and not to all the objects of that particular class.
Note that this is just an example. I want to apply this modification to the methods of multiple classes. So, I can't generalize the method names also. Any code snippet for the modifyClassMethods will be helpful.
The methods defined with method syntax in the body of a class construct that aren't marked static are prototype methods and so they're on Temp.prototype, not Temp itself. So that's where you'd update them:
Temp.prototype.hi = modifyMethod(Temp.prototype.hi);
Only static methods end up on Temp itself.
You may see other functions created within the class body using the class fields proposal's syntax:
class Temp {
hi = () => {
//
};
}
Those are instance methods. They're created by the constructor, and re-created for each instance, roughly as though they'd been written like this:¹
class Temp {
constructor() {
this.hi = () => {
//
};
}
}
You can't wrap those until/unless an instance is created, as they're instance-specific.
So to wrap up, consider:
class Temp {
static staticMethod() {
// ...
}
prototypeMethod() {
// ...
}
instanceMethod = () => {
// ...
};
constructor() {
this.anotherInstanceMethod = () => {
// ...
};
this.yetAnotherInstanceMethod = function {
// ...
};
}
}
That class shows the three types of methods:
Static Methods, such as staticMethod, which you'll find on Temp (e.g., Temp.staticMethod);
Prototype Methods, such as prototypeMethod, which you'll find on Temp.prototype (e.g., Temp.prototype.prototypeMethod); and
Instance Methods, such as instanceMethod, anotherInstanceMethod, and yetAnotherInstanceMethod, which you'll find on the instances themselves, if/when any instances are created
¹ Technically, they're created as though with Object.defineProperty like this:
class Temp {
constructor() {
Object.defineProperty(this, "hi", {
value: () => {
//
},
writable: true,
configurable: true,
enumerable: true
});
}
}
...not via simple assignment. I used simple assignment in the example to keep it...simple. :-)
Related
It seems like I don't understand prototypes correctly once again.
If you call a method on an object, and it doesn't exist - does it not check the prototype for the method can run it?
like
Array.prototype.myArrayMagic = function () { ... }
Then on any array you can call arr.myArrayMagic() ?
That was my understanding, but I am now running into issue where calling a prototype method wont work unless I explicitly call it through .prototype
Also: I am restricted to ES5 code
The base module setup
function Wall() {}
Wall.prototype = {
shouldShowWall: function() {
return true;
},
show: function () {
if (!this.shouldShowWall()) {
return;
}
// Do other stuff here
}
}
module.exports = Wall;
The child object
function OtherWall() {
this.item = 'my_tracking_id',
this.minutes = 30
// ...
}
OtherWall.prototype = {
constructor: Object.create(Wall.prototype),
/**
* Override wall.prototype.shouldShowWall method for deciding if the wall should be shown at this time
*/
shouldShowWall: function() {
// return true or flase here
}.bind(this)
}
module.exports = OtherWall;
So following other answers and trying to understand as best as possible, this should be set up in a way where I can call
new OtherWall().show();
but it doesn't work, it only works if I call
new OtherWall().prototype.show();
Is this normal, or have I set it up wrong?
What do I need to change to get it to work with OtherWall().show();
If you call a method on an object, and it doesn't exist - does it not check the prototype for the method can run it?
Yes, it does (like with any property), but this code is incorrect:
OtherWall.prototype = {
constructor: Object.create(Wall.prototype)
// ...
That won't make Wall.prototype the prototype of OtherWall.prototype. To do that, do this:
OtherWall.prototype = Object.create(Wall.prototype);
Then add properties on OtherWall.prototype, starting with constructor:
// But keep reading, I wouldn't do it quite like this
OtherWall.prototype.constructor = OtherWall;
...then shouldShowWall, etc.
My answer here may also be useful, it shows creating a "subclass" using ES5 syntax (contrasting it with ES2015's class syntax).
Another couple of notes:
1. This looks like it's probably not what you intend:
/**
* Override wall.prototype.shouldShowWall method for deciding if the wall should be shown at this time
*/
shouldShowWall: function() {
// return true or flase here
}.bind(this)
The this in scope there isn't an instance of OtherWall, it's whatever this is outside that object literal. If you want to bind shouldShowWall to the instance, you'll need to do that in the constructor.
2. I'd suggest making methods non-enumerable, like ES2015's class syntax does, and the same for the constructor property like JavaScript always has. When you create a property via simple assignment, the property is enumerable.
3. I think you have a typo in OtherWall, you've used a , at the end of the first statement instead of a ;. Technically, it works, because of the comma operator, but I don't think you were using the comma operator on purpose. :-)
Here's an example applying all of the above; Object.defineProperty is an ES5 method (I'm out of practice writing ES5 code, but I think I've avoided all ES2015+ stuff here):
function assignNonEnumerable(target, source) {
Object.keys(source).forEach(function (key) {
Object.defineProperty(target, key, {
value: source[key],
configurable: true,
writable: true,
enumerable: false, // This is the default, but showing it here for emphasis
});
});
}
function Wall() {
}
assignNonEnumerable(Wall.prototype, {
shouldShowWall: function () {
return true;
},
show: function () {
console.log("show called");
if (!this.shouldShowWall()) {
return;
}
// Do other stuff here
},
});
function OtherWall() {
this.item = "my_tracking_id";
this.minutes = 30;
// If you want to bind it to the instance:
this.shouldShowWall = this.shouldShowWall.bind(this);
}
OtherWall.prototype = Object.create(Wall.prototype);
assignNonEnumerable(OtherWall.prototype, {
constructor: OtherWall,
shouldShowWall: function () {
// You can't bind the instance here, see the constructor above
console.log("shouldShowWall called");
return Math.random() < 0.5;
},
});
new OtherWall().show();
I have the following class:
class NGPlayer {
constructor(videoPlayerID, videoJSConfig, code) {
this.player = videojs(videoPlayerID, videoJSConfig, code);
return this.player;
}
setAutoplay() {
this.player.getAXSMetrics().setTempo(true);
}
}
my goal is to create an instance:
const foo = new NGPlayer(videoID, videoOptions, '232'); // this returns `this.player` from the constructor, which is the videojs object
I want to be able to also call foo.setAutoplay(). Is it possible to do this from just setting the one variable, foo ?
I have a CameraBuilder class that looks like this:
class CameraBuilder {
constructor() {
if (arguments.length) {
throw new Error('[CameraBuilder constructor ERROR] class constructor does not accept parameters.');
}
this.camera = {};
}
withFarmLabel(farmLabel) {
this.camera.farm_label = farmLabel;
return this;
}
// more methods here
build() {
const missingProps = [];
if (!this.camera.farm_label) {
missingProps.push('\nMissing farm_label property. Use the withFarmLabel method in order to assign it.');
}
// more validations like the one above here
if (missingProps.length) {
const errorMsg = missingProps.join('');
throw new Error(`[CameraBuilder build ERROR] ${errorMsg}`);
}
return this.camera;
}
}
Since most of my validations are on the build() method and there are some business logic on some of these methods associated with how the user is building an instance of CameraBuilder, I wouldn't want anyone assigning cameraBuilderObj.camera directly. Is there any way I can enforce the use of the Class methods in order to assign properties to the Camera object?
You could make the camera property private by putting # in front of it, ensuring that only CameraBuilder's internals can reference it:
class CameraBuilder {
#camera = {};
constructor() {
if (arguments.length) {
throw new Error('[CameraBuilder constructor ERROR] class constructor does not accept parameters.');
}
}
withFarmLabel(farmLabel) {
this.#camera.farm_label = farmLabel;
return this;
}
// more methods here
build() {
const missingProps = [];
if (!this.#camera.farm_label) {
missingProps.push('\nMissing farm_label property. Use the withFarmLabel method in order to assign it.');
}
// more validations like the one above here
if (missingProps.length) {
const errorMsg = missingProps.join('');
throw new Error(`[CameraBuilder build ERROR] ${errorMsg}`);
}
return this.#camera;
}
}
const c = new CameraBuilder();
c.withFarmLabel('label');
console.log(c.camera);
console.log(c.build().farm_label);
CertainPerformance's answer probably makes more sense--don't expose it in the first place--but if for some reason you didn't want to go that route (or if you're in an environment where private fields aren't supported) you could define setters on it, so that direct assignments go through your function.
class Foo {
constructor () {
this._bar = 'baz';
}
set bar (value) {
this._bar = value;
console.log('do whatever you want to do here.');
}
}
const f = new Foo();
f.bar = 'hey'; // direct assignment invokes the setter
Given class
class Test {
test() {
console.log('test called');
}
}
And some object toExtend = {}
How can I extend this object so it will have test method?
Object.assign ( as well as _.extend, _.assign, $.extend) do not copy methods. What is preferable way to do that?
Note that toExtend is passed from outside
UPD:
toExtend is instance of another class and has it's own prototype's methods
Object Literals
For object literals, which start with no protoype of their own (Object.getPrototypeOf(toExtend) === Object.protoype)), you can simply use Object.setPrototypeOf to extend the object:
class Test {
test() {
console.log('test called');
}
}
const toExtend = {};
// Set the prototype, so you "inherit" methods:
Object.setPrototypeOf(toExtend, Test.prototype);
toExtend.test();
In older runtimes, you would have to manually assign the prototype:
function Test() {
// noop ctor
}
Test.prototype.test = function() {
console.log('test called');
};
var toExtend = {};
// Set the prototype, so you "inherit" methods:
toExtend.__proto__ = Test.prototype;
toExtend.test();
Class Instances
For instances of an existing class, things are significantly more complex. They do have a prototype of their own, potentially with properties that must be copied, so you need to walk through those:
class Foo {
test() {
console.log('test');
}
}
class Bar {
toast() {
console.log('toast');
}
}
function dynamicExtend(target, base) {
const baseProto = Object.getPrototypeOf(target);
if (baseProto == Object.prototype) {
// simple case: no existing prototype
Object.setPrototypeOf(target, base.prototype);
} else {
// complex case: existing prototype
const proxyClass = class extends base {};
const proxyProto = proxyClass.prototype;
// assign the target properties
Object.getOwnPropertyNames(baseProto).forEach(n => {
const desc = Object.getOwnPropertyDescriptor(baseProto, n);
Object.defineProperty(proxyProto, n, desc);
});
Object.setPrototypeOf(target, proxyProto);
}
}
const targets = [{},
new Bar()
];
targets.forEach(t => {
dynamicExtend(t, Foo);
t.test();
if (t.toast) {
t.toast();
}
});
Note that, thanks to the proxy class, this does break instanceof style inheritance checks.
__proto__
As #PatrickRoberts noted in the comments, __proto__ is deprecated, so you should prefer setPrototypeOf whenever possible.
I would like to implement the following behavior in JS. Please note that the syntax is symbolic.
This is my parent class
class = TList {
FList: array;
function AddElement(Ele) {
Flist.Add(Ele)
};
function RemoveEle(Ele) {
FList.Remove(Ele)
};
}
Now I'm going to inherit from this class. My child class should automatically have all the properties of the parent and should be able to extend them without rewriting the code.
class = TAlertList(inherit from TList) {
function AddElement(Ele) {
Alert('element will be added');
call.parent.AddElement(Ele)
};
function RemoveElement(Ele) {
call.parent.RemoveElement(Ele);
Alert('element removed');
}
}
Please note how I inherit the parent methods at places I wish.
Now I should be able to create an object from my child class and do the following.
MyAlertList = new TAlertList;
MyAlertList.Add('hello');
console.log(MyAlertList.FList);
I should be able to inherit more child classes from TAlertList and be able to change the existing behavior. I need to do this in pure ES5 without using any libraries. Standard OOP practices are expected.
Please note that the TList constructor should be applied to the TAlertList instance;
ES5, first set up the base constructor
function TList() {
this.Flist = [];
// ...
}
TList.prototype = {
constructor: TList,
AddElement: function AddElement(Ele) {
this.Flist.push(Ele);
},
RemoveEle: function RemoveEle(Ele) {
var i = this.Flist.lastIndexOf(Ele);
if (i !== -1)
this.Flist.splice(i, 1);
}
};
Next set up the constructor which extends it, see how this means calling the base constructor on the instance being created by the extended constructor and creating a prototype object which inherits the prototype of the base constructor
function TAlertList() {
// construct from base
TList.call(this);
// further construct
// ...
}
TAlertList.prototype = Object.create(TList.prototype);
TAlertList.prototype.constructor = TAlertList;
// depending on how you want to reference stuff
TAlertList.prototype.AddElement = function AddElement(Ele) {
alert('element will be added');
TList.prototype.AddElement.call(this, Ele);
};
TAlertList.prototype.RemoveElement = function RemoveElement(Ele) {
TList.prototype.RemoveEle.call(this, Ele);
alert('element removed');
};
ES6 syntax makes use of the super keyword
class TList {
constructor() {
this.FList = [];
}
AddElement(Ele) {
this.Flist.push(Ele);
}
RemoveEle(Ele) {
var i = this.Flist.lastIndexOf(Ele);
if (i !== -1)
this.Flist.splice(i, 1);
}
}
class TAlertList extends TList {
constructor() {
super();
}
AddElement(Ele) {
alert('element will be added');
super.AddElement(Ele);
}
RemoveElement(Ele) {
super.RemoveEle(Ele);
alert('element removed');
}
}
Back to ES5, generalising as a factory so you can see a sort of algorithm of how to do it
function extend(baseConstructor, extendedConstructor, prototypeLayer) {
function Constructor() {
var i = 0, j = 0, args = Array.prototype.slice.call(arguments);
i = j, j += baseConstructor.length;
baseConstructor.apply(this, args.slice(i, j));
i = j, j = args.length;
extendedConstructor.apply(this, args.slice(i, j));
}
Object.defineProperty(Constructor, 'length', { // fix .length
value: baseConstructor.length + extendedConstructor.length,
configurable: true
});
Constructor.prototype = Object.create(baseConstructor.prototype);
Constructor.prototype.constructor = Constructor;
Object.assign(Constructor.prototype, prototypeLayer);
return Constructor;
}
So then
function Foo(x) {this.foo = x;}
Foo.prototype.fizz = 1;
var Bar = extend(Foo, function (x) {this.bar = x;}, {buzz: 1});
// ...
var b = new Bar('foo', 'bar');
b.foo; // "foo"
b.bar; // "bar"
b instanceof Foo; // true
b instanceof Bar; // true
b.fizz; // 1
b.buzz; // 1
Please note that this is an example of the algorithm you should be following when you write each extended constructor, not production code
Your code would be the following
function TList(){
this.FList = [];
}
TList.prototype.AddElement = function(Ele){
this.FList.push(Ele);
}
TList.prototype.RemoveElement = function(Ele){
this.FList.splice(Ele,1); //Ele is the index to remove;
}
This is an approximation to know how the inherit works in JavaScript.
function TAlertList (){
TList.call(this);
}
TAlertList.prototype = Object.create(TList.prototype);
TAlertList.prototype.constructor = TAlertList;
TAlertList.prototype.AddElement = function(ele){
alert('Element will be added');
TList.prototype.AddElement.call(this,ele);
};
TAlertList.prototype.RemoveElement = function(ele){
alert('Element will be remove');
TList.prototype.RemoveElement.call(this,ele);
};
So, the classic super call is
ParentClass.prototype.myMethod.call(this,args);
This is Q&A
Edit - Don't forget to read #paul's comment too if you planning to read the full text.
Almost all the answers came in were based on the popular "Person" example in the MDN documentation about JS OOP.
The theory behind this method is to put the fields of the object inside a constructor function while implementing the methods in a prototype object. A child object can inherit all the fields by calling the constructor function with a contrived this value. Also it can inherit all the methods by having the same prototype object of the parent as it's prototype object too. The only rule is that you need to call the methods using call or apply to point the methods to the correct this object when implementing inheritance.
I didn't like this approach for two reasons.
The fields and methods of an objects has to be separated between two objects (fields - constructor function, methods - prototype). This doesn't have the flavor of a unique behavior of a unique entity - which should be a single class.
You have to specify the name of the parent object when inheriting. This is not automatic. Let's say object C inherits from the object A. So, inside the methods of the object C, you need to mention object A when inheriting from it. (TList.prototype.AddElement.call(this, Ele);) What if object B comes in between later on? You will have to change all the inheriting methods of C to call from B. This is in no way near good inheritance.
I wanted to overcome these problems in MDN method. I came up with the following model with no this no call and no apply. Code is simple and easy to follow. You don't have to mention the name of the parent object when inheriting from it. So, a new class can come in between the parent and the child at any time without too many changes. Please discuss this and point out the weaknesses of this model.
Here is the function which returns the parent object.
function tList() {
var ret = Object.create(null);
ret.list = [];
ret.addElement = fucntion(ele) {
ret.list.push(ele)
};
return ret;
}
//You can create tList object like this:
var myList = tList();
myList.addElement('foo');
Now comes the child object:
function tAlertList() {
var ret = Object.create(tList());
//Lets inherit with the fashion of overriding a virtual method
ret.addElement = function(ele) {
//Here is new code
alert('Adding element ' + ele);
//Automatic inheritance now
Object.getPrototypeOf(ret).addElement(ele);
}
return ret;
}
//Just create the child object and use it
var myAlertList = tAlertList();
myAlertList.addElement('buzz') ;
You can have grand children object and inherit from parent without mentioning their names. Let's say you have to put tCustomList between tList and tAlertList. All you have to do is to tell the tAlertList to inherit from tCustomList in a single place. (var ret = Object.create(tCustomList());) Everything else remain the same.
Here is the fiddle.
With pure ES5, you could do it like this:
function TList() {
this.FList = []; //not really ideal.
}
TList.prototype.addElement = function(ele) {
this.FList.push(ele);
};
TList.prototype.removeElement = function(ele) {
this.FList.splice(this.FList.indexOf(ele), 1);
}
function TAlertList(){
this.FList = [];
}
TAlertList.prototype = new TList(); //inherit from TList
TAlertList.prototype.constructor = TAlertList; //reset constructor
TAlertList.prototype.addElement = function(ele) {
alert('element will be added');
TList.prototype.addElement.call(this, ele);
};
TAlertList.prototype.removeElement = function(ele) {
alert('element removed');
TList.prototype.removeElement.call(this, ele);
};
Couple of notes:
The FList property of its parent will actually be shared amongst all objects that inherit from the parent unless overwritten. That means that if you don't overwrite FList you'll get this:
var a = new TAlertList();
var b = new TAlertList();
a.push(1); //b.Flist === [1]
In my opinion, it would be best if you named your children functions with other names different from the parent. This way you don't need to do:
TList.prototype.function.call(this, parameter1, parameter2, ..);
You can just call them like this:
this.function(paremeter1, parameter2);
Of course, it's not a static way to call the parent as you can overwrite the function with your own. Then again TList.prototype.function isn't necessary the function of the parent of the object that owns the function. For that you'd need to use non-standard ES5 __proto__ property.
this.__proto__.function.call(this, parameter1, parameter2, ..);
Unless you plan on juggling the function around different objects, you don't need that.