JavaScript prototype inheritance with defineProperty - javascript

Say I have this "class":
function Car()
{
}
Object.defineProperty(Car.prototype, "Make",
{
get:function() { return this._make; },
set:function(value) { this._make = value; }
});
Object.prototype.Drive = function Drive() { console.log("Car.Drive"); }
Now I want to make a "child class" using prototype inheritance:
function Sedan()
{
}
Sedan.prototype = new Car();
Sedan.prototype.constructor = Sedan;
Sedan.prototype.Drive = function Drive() { Car.prototype.Drive.call(this); console.log("Sedan.Drive"); }
Then I can instantiate a car or a sedan, and drive both. Notice how with sedans, Drive also calls base class (Car) Drive:
var car = new Car(); car.Drive(); var carMake = car.Make;
var sedan = new Sedan(); sedan.Drive(); var sedanMake = sedan.Make;
Is it possible to achieve something similar with properties?
Object.defineProperty(Sedan.prototype, "Make",
{
get: function() { return Car.prototype.Make.<<CALL_GETTER>>(this) + " - Sedan"; },
set: function(value) { Car.prototype.Make.<<CALL_SETTER>>(this, value.replace(" - Sedan", "")); }
});
The only idea I could come up with is something like this:
Car.prototype.get_Make = function get_Make() { return this._make; }
Car.prototype.set_Make = function set_Make(value) { this._make = value; }
Object.defineProperty(Car.prototype, "Make",
{
get:function() { return this.get_Make(); },
set:function(value) { this.set_Make(value); }
});
Then the explicit get_Make and set_Make can be overridden similar to Drive. However, this is clunky. Sure, this boilerplate can be extracted into a helper function which defines the get_ and set_ methods and the property in one shot.
function DefineVirtualProperty(obj, name, getter, setter)
{
obj["get_" + name] = getter;
obj["set_" + name] = setter;
Object.defineProperty(obj, name,
{
get:function() { return this["get_" + name](); },
set: function(value) { this["set_" + name](value); }
});
}
DefineVirtualProperty(Car.prototype, "Make", function() { return this._make; }, function(value) { this._make = value; });
However the overriding still looks a big ugly.

You can use Object.getOwnPropertyDescriptor to get the property descriptor of the parent property.
Then you can use .call() to invoke it, e.g.:
function Car() {}
Object.defineProperty(Car.prototype, "Make", {
get() {
return this._make;
},
set(value) {
this._make = value;
}
});
function Sedan() {}
Sedan.prototype = Object.create(Car);
Sedan.prototype.constructor = Sedan;
Object.defineProperty(Sedan.prototype, "Make", {
get() {
console.log("Sedan Make get");
let desc = Object.getOwnPropertyDescriptor(Car.prototype, "Make");
return desc.get.call(this);
},
set(value) {
console.log("Sedan Make set");
let desc = Object.getOwnPropertyDescriptor(Car.prototype, "Make");
return desc.set.call(this, value);
}
});
let sedan = new Sedan();
sedan.Make = 12;
console.log(sedan.Make);
A few minor tips:
Ideally you should use Object.create for prototype creation, since it doesn't call the constructor when creating the object
Prefer to use Object.defineProperty instead of directly creating properties on the prototype (so you can set enumerable to false)
If you can use ES6 classes this becomes a lot nicer.
You can just use super with them to access the parent property:
class Car {
get Make() {
return this._make;
}
set Make(value) {
this._make = value;
}
}
class Sedan extends Car {
get Make() {
console.log("Sedan Make get");
return super.Make;
}
set Make(value) {
console.log("Sedan Make set");
super.Make = value;
}
}
let sedan = new Sedan();
sedan.Make = 12;
console.log(sedan.Make);

Related

How to convert a function into a class?

I would like to convert a function that return multiple functions, into a class with a constructor that should be called only with the new keyword.
I tried this :
const toast = () => {
return ({
getToast: () => {
return 'toast'
},
setToast: () => {
return 'wtf'
}
})
}
class t {
constructor() {}
}
const t1 = t.bind(toast())
const tt = new t1()
console.log(tt.getToast)
But it print undefined.
I also tried Object.assign(t, toast()), or doing simply this = toast() in the constructor but it doesn't work.
I do not know what are you trying to do. May be you are looking for this.
const toast = () => {
return ({
getToast: () => {
return 'toast'
},
setToast: () => {
return 'wtf'
}
})
}
class t {
constructor(fn) {
return fn;
}
}
const tt = new t(toast())
console.log(tt.getToast())
console.log(tt.setToast())
For your exact scenario, that is if the function returns an object with only functions and no non-function properties, one way to do this is simply using prototype inheritance:
function t () {}; // though by convention a class should REALLY be uppercase
t.prototype = toast();
Now you can do:
let bread = new t();
let bagel = new t();
bread.getToast();
bagel.getToast();
You can also use a design pattern called parasitic inheritance but this is less memory efficient because just like the toast() function it creates a copy of the functions for each object:
class t {
constructor () {
let tmp = toast();
// Copy methods to our instance:
for (let prop in tmp) {
this[prop] = tmp[prop];
}
}
}
Or with Object.assign() it would simply be:
class t {
constructor () {
Object.assign(this, toast());
}
}
However, as mentioned, the prototype inheritance is the better mechanism for this use-case.

Javascript object syntax that I can't recognize

I have this code snipet:
proxy('http://my-custom-api-endpoint.com', {
proxyReqOptDecorator(options) {
options.headers['x-forwarded-host'] = 'localhost:3000'
return options
}
})
It is a call to a function named proxy, the first argument is a string, but the second argument has a syntax than I can't recognize:
{
functionName(args) {
// statements
}
}
Can someone explain that syntax please?
Its a shorthand method in Object Initializer to create a property whose value is a function.
// Shorthand method names (ES2015)
let o = {
property(parameters) {}
}
//Before
let o = {
property: function(parameters) {}
}
This syntax is also sued in classes to declare class methods.
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}class Animal {
speak() {
return this;
}
eat() {
return this;
}
}

How to capture parent class 'this' in a child object getter in ES6 or later?

Here is a simple JavaScript code:
class Test {
constructor(val) {
this._value = val;
this.child = {
get value() { return this._value; },
getValue: () => { return this._value; }
};
}
}
let o = new Test(1);
console.log(o.child.value);
console.log(o.child.getValue());
The output:
undefined
1
In the child object child, I want to make the getter get value() to produce the same value as the lambda getValue(), i.e. to capture the parent's this properly and produce 1 rather than undefined.
Is there an elegant way of doing it within the class? Or should I use the lambda and give up on the getter?
I could probably do this:
this.child = {
parent: this,
get value() { return this.parent._value; },
};
However I don't want to expose child.parent, only child.value.
The value and child are defined in the constructor, and value should be private, and accessed only via child. You can set value as a normal variable in the closure, and access it via the child getter and setter:
class Test {
constructor(val) {
let value = val;
this.child = {
get value() { return value; },
set value(v) { value = v; },
};
}
}
let o = new Test(1);
console.log(o.child.value);
o.child.value = 5;
console.log(o.child.value);
If you need the value inside Test, you can always access it via the child:
class Test {
constructor(val) {
let value = val;
this.child = {
get value() { return value; },
set value(v) { value = v; },
};
}
get value() {
return this.child.value;
}
set value(v) {
this.child.value = v;
}
}
let o = new Test(1);
console.log(o.value);
o.value = 5;
console.log(o.value);
Answering to myself, it can simply be done by capturing this inside constructor:
class Test {
constructor(val) {
const self = this;
this._value = val;
this.child = {
get value() { return self._value; }
};
}
}

How to define getter and setter properties in a subclass

I have the following code for inheritance:
SubClass= function () {
ParentClass.call(this);
}
SubClass.prototype = Object.create(ParentClass.prototype);
SubClass.prototype.constructor = SubClass;
However, I want to define some properties in the subclass as well:
SubClass.prototype = {
get x() {
return this.newX;
},
set x(val) {
this.newX = val;
alert("X has a value of " + this.newX);
}
}
The problem I'm having is combining the two. In other words, in the first code sample I'm saying:
SubClass.prototype = Object.create(ParentClass.prototype);
But then in the second code sample I'm saying:
SubClass.prototype = {...
How can I achieve both? What is the syntax that would allow me to inherit from a parent class and define properties using the same prototype definition?
Thank you :)
Define your properties by passing a property descriptor to Object.defineProperty:
Object.defineProperty(SubClass.prototype, 'x', {
configurable: true,
get: function () {
return this.newX;
},
set: function (val) {
this.newX = val;
alert("X has a value of " + this.newX);
},
});
It’s also possible to pass an object containing property descriptors to Object.create:
function SubClass() {
ParentClass.call(this);
}
SubClass.prototype = Object.create(ParentClass.prototype, {
constructor: {
configurable: true,
writable: true,
value: SubClass,
},
x: {
configurable: true,
get: function () {
return this.newX;
},
set: function (val) {
this.newX = val;
alert("X has a value of " + this.newX);
},
}
});
ES6 classes are nicer if you can use them:
class SubClass extends ParentClass {
get x() {
return this.newX;
}
set x(val) {
this.newX = val;
alert("X has a value of " + this.newX);
}
}
You can also make this sort of helpful function:
function extend(target, source) {
Object.getOwnPropertyNames(source).forEach(function (name) {
var descriptor = Object.getOwnPropertyDescriptor(source, name);
Object.defineProperty(target, name, descriptor);
});
}
and use it like so:
extend(SubClass.prototype, {
get x() {
return this.newX;
},
set x(val) {
this.newX = val;
alert("X has a value of " + this.newX);
},
});

JavaScript: Should a function be able to create instances of itself with Object.create()

My use case is the following: I want to create a factory which produces various kinds of data transfer objects (DTOs). They must be easily serializable and they must have a few additional methods.
My current implementation looks like this (simplified):
window.Dto = function(type, properties)
{
var
self = this,
values = {},
object = Object.create(self);
properties.forEach(function(prop){
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
this.getType = function()
{
return type;
};
this.doSomeMagic = function()
{
// ...
};
return object;
};
// creating a DTO of the Transport.Motorized.Car class
var carObject = new Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
(Note: I do not want to create an explicit class for each of these objects, because there are hundets of them, and they are exported from the server side. Also, what you see as properties parameter above, is actually a map of meta data with validation constraints etc.)
I did a quick performance check with a loop where 50,000 of such objects were created. performance.now() tells me that it took a bit more than 1s – which looks ok, but not too impressive.
My question is mainly: Is it ok that the factory creates an instance from its own prototype (if I understand correctly what that code does) and returns it? What side effects can it have? Is there a better way?
As far as I understand factory functions, their whole point is not needing to create new instances of the function itself. Instead, it just returns a newly created object.
So instead of using instance properties (via this) of the newly created instance (via the new operator), I would just create an object (let's call it factoryProto) and assign all the "instance" methods to that object instead.
Then, you can use factoryProto as the [[Prototype]] for your new object:
window.Dto = function(type, properties) {
var factoryProto = {
getType: function() {
return type;
},
doSomeMagic: function() {
// ...
}
},
values = {},
object = Object.create(factoryProto);
properties.forEach(function(prop) {
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
return object;
};
// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);
If you want to fully profit from the prototype-chain, you could define the factoryProto outside of the factory function. To keep track of type, you could add it as a non-enumerable object property:
window.Dto = (function() {
var factoryProto = {
getType: function() {
return this.type;
},
doSomeMagic: function() {
// ...
}
};
return function(type, properties) {
var values = {},
object = Object.create(factoryProto);
properties.forEach(function(prop) {
Object.defineProperty(object, prop, {
get: function() { return values[prop]; },
set: function(value) { values[prop] = value; },
enumerable: true
});
});
Object.defineProperty(object, 'type', {
value: type,
enumerable: false
});
return object;
};
})();
// creating a DTO of the Transport.Motorized.Car class
var carObject = Dto("Transport.Motorized.Car", ["vendor", "model", "color", "vmax", "price"]);

Categories

Resources