How to define getter and setter properties in a subclass - javascript

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);
},
});

Related

Define getter using defineProperty

In our application we compress our JavaScript classes using UglifyJS which doesn't support being able to compress syntax like onBlur = (event) => {} as it returns Unexpected token: operator (=).
To solve this problem we have used the following function to define them:
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
And then we use it inside our constructor like so:
class OurClass {
constructor(...args) {
super(...args);
_defineProperty(this, "onBlur", event => {
});
}
}
Which works great! however it doesn't work for defining getters:
static get values() {
return { index: Number }
}
Like so:
_defineProperty(this, 'values', () => {
return { index: Number };
});
The getter is never defined and isn't accessible in the rest of the class in the same way that we were able to define the other methods...
How can we define the getter using this same function with defineProperty?
Object.defineProperty accepts two different formats for its third argument:
data descriptor, which you currently use
accessor descriptor, which allows for defining a getter/setter
So, for instance, you could extend your own _defineProperty function with an optional argument to indicate that a getter/setter is intended:
function _defineProperty(obj, key, value, accessor) {
if (accessor == "getter") {
Object.defineProperty(obj, key, {
get: value,
enumerable: true,
configurable: true,
});
} else if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// simple demo
class MyClass {
constructor() {
_defineProperty(this, "num", () => 3, "getter");
}
}
console.log(new MyClass().num);
If you want it as a static method, then define it on the class (i.e. on the constructor):
function _defineProperty(obj, key, value, accessor) {
if (accessor == "getter") {
Object.defineProperty(obj, key, {
get: value,
enumerable: true,
configurable: true,
});
} else if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
// simple demo
class MyClass {}
_defineProperty(MyClass, "num", () => 3, "getter");
console.log(MyClass.num);

JavaScript prototype inheritance with defineProperty

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);

Access object from javascript property decorator (babel legacy decorator)

Is it any way to access created object from property decorator?
function decorator(target, name, descriptor) {
// somehow get object when it will be created
return {
configurable: true,
enumerable: true,
get: function () {
// ...
},
set: function (value) {
// ...
},
}
}
class MyClass {
#decorator
property = 0;
}
I found one solution but it is not fitting my use case. Babel transforms upper code to:
var _desc, _value, _class, _descriptor;
function _initDefineProp(target, property, descriptor, context) {
if (!descriptor) return;
Object.defineProperty(target, property, {
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
writable: descriptor.writable,
value: descriptor.initializer ? descriptor.initializer.call(context) : void 0
});
}
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
var desc = {};
Object['ke' + 'ys'](descriptor).forEach(function (key) {
desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;
if ('value' in desc || desc.initializer) {
desc.writable = true;
}
desc = decorators.slice().reverse().reduce(function (desc, decorator) {
return decorator(target, property, desc) || desc;
}, desc);
if (context && desc.initializer !== void 0) {
desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
desc.initializer = undefined;
}
if (desc.initializer === void 0) {
Object['define' + 'Property'](target, property, desc);
desc = null;
}
return desc;
}
function _initializerWarningHelper(descriptor, context) {
throw new Error('Decorating class property failed. Please ensure that transform-class-properties is enabled.');
}
function decorator(target, name, descriptor) {
// somehow get object when it will be created
return {
configurable: true,
enumerable: true,
get: function get() {
// ...
},
set: function set(value) {
// ...
}
};
}
var MyClass = (_class = function MyClass() {
_classCallCheck(this, MyClass);
_initDefineProp(this, "property", _descriptor, this);
}, (_descriptor = _applyDecoratedDescriptor(_class.prototype, "property", [decorator], {
enumerable: true,
initializer: function initializer() {
return 0;
}
})), _class);
So if you return descriptor with initializer field then _initDefineProp will call it with created object. The problem is setter and getter will be missed in this case.
Is it any other way? Keep in mind I have access to decorator only (not class).

How do you polyfill Javascript ES6 `new.target`?

Some ES6 features are really easy to polyfill:
if(!Array.prototype.find){
Array.prototype.find=...
}
How would you polyfill new.target? It triggers a syntax error when it's used in an unsupported browser. try/catch doesn't work because it's a syntax error. I don't have to use new.target, I'm mostly just curious.
As Jaromanda commented, you cannot polyfill new syntax, but you can easily work around some new.target use cases for now
Taking a look at the new.target docs you'll see some examples that can easily be written with es5
with new.target
function Foo() {
if (!new.target) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
without
function Foo() {
if (!(this instanceof Foo)) throw "Foo() must be called with new";
console.log("Foo instantiated with new");
}
Foo(); // throws "Foo() must be called with new"
new Foo(); // logs "Foo instantiated with new"
with new.target
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A { constructor() { super(); } }
var a = new A(); // logs "A"
var b = new B(); // logs "B"
without
class A {
constructor() {
// class forces constructor to be called with `new`, so
// `this` will always be set
console.log(this.constructor.name);
}
}
class B extends A { constructor() { super(); } }
var a = new A(); // logs "A"
var b = new B(); // logs "B"
Hope this helps a little
Here's a way using Function::bind:
const requireNew = (() => {
const kFake = {};
const CtorMap = new WeakMap();
const FuncToString = function toString () {
const info = CtorMap.get(this);
return Function.prototype.toString.apply(
info ? info.ctor : this, arguments);
};
const GetProto = function prototype() {
const info = CtorMap.get(this);
return info ? info.ctor.prototype : undefined;
};
const SetProto = function prototype(prototype) {
const info = CtorMap.get(this);
return !info ? prototype
: info.wrapper.prototype = info.ctor.prototype = prototype;
}
return (Ctor) => {
const wrapper = function () {
if (this === kFake) {
throw new TypeError("Please use 'new' to call this");
}
return Ctor.apply(this, arguments);
}
wrapper.prototype = Ctor.prototype;
const bound = wrapper.bind(kFake);
CtorMap.set(bound, { ctor: Ctor, wrapper });
Object.defineProperties(bound, {
prototype: { get: GetProto, set: SetProto,
enumerable: false, configurable: true },
name: { value: Ctor.name, writable: false,
enumerable: false, configurable: true },
length: { value: Ctor.length, writable: false,
enumerable: false, configurable: true },
toString: { value: FuncToString, writable: true,
enumerable: false, configurable: true }
});
return bound;
}
})();
And here's a simple demo:
function Abc (a) {
this.a = a;
console.log("this =", this, "; .a =", this.a, "; new.target:", new.target);
}
const A = requireNew(Abc);
const a = new A(1);

How to create a constructor creating constructor in javascript?

Sometimes in JavaScript I need many constructors and objects for pseudo-classes because I like objective very much so I do something like:
var anyClass = (function (settings) {
var staticPrivate = {}, staticPublic = function () {
var public = this, private = {};
(function constructor (here, the, args) {
this.hta = [here, the, args, arguments];
}).apply(this, arguments);
arguments = undefined;delete arguments;
private.stuff = function () {}
Object.defineProperties(public, {
"e.g. length": {
get: function () {
return private.length;
},
set: function (newValue) {
return;
},
enumerable: false
}
});
};
Object.defineProperties(staticPublic, {
"staticFinalHiddenString": {
get: function () {
return "YEAH, I'm static and final and hidden...";
},
set: function (newValue) {
return "You cannot set me.. :P";
},
enumerable: false
}
});
staticPrivate.some = function (init) {
if (settings.some == "settings") init();
}
window.requestAnimationFrame(function () {
staticPrivate.some(function (I) {
run(on, first, render);
});
});
return staticPublic;
})({
some: "settings",
here: null
});
And that every time, so now I want a constructor that creates a new class for me. I think on this:
new Class({
constructor: function (here) {
is(my + constructor);
},
properties: {
name: {
getter: function () {},
setter: function (newValue) {},
hidden: false,
static: false,
final: false
},
version: {
getter: function () {
return 0.3;
},
setter: function (newValue) {},
hidden: true,
static: true,
final: true
}
}
});
but my problem is that I have no idea how to create an prototype/constructor with an constructor class.prototype.prototype does not work.
I just tried that:
var Class = (function () {
var Class = (function () {
var constructor = function () {
return (function (information) {
this.prototype = {};
var properties = {};
for(var key in information.properties) {
properties[key] = {
get: information.properties[key].getter,
set: information.properties[key].setter,
enumerable: !information.properties[key].hidden || true
};
};
Object.defineProperties(this.prototype, properties);
return this;
}).apply(this, arguments);
};
return constructor;
})();
return Class;
})();
That does not work for me :C
I hope you can help me. Thanks...
I understood my mistake and now I can return somthing when I have an constructor.
The var Class = function () {}; and the Class.prototype in the closure function with .apply(this, arguments) does the thing, that is why I can return Class in the constructor function, if I would just do
var Class = function () {
var ClassICreate = function () {};
...
return ClassICreat;
}
it would not work, because you cannot return from a constructor, because it is an object.
That is how it works for my:
var Class = (function () {
var Class = function () {
return (function (information) {
var Class = function () {};
var properties = {};
for(var key in information.properties) {
properties[key] = {
get: information.properties[key].getter,
set: information.properties[key].setter,
enumerable: !information.properties[key].hidden || true
};
};
Object.defineProperties(Class.prototype, properties);
return Class;
}).apply(this, arguments);
};
return Class;
})();
After I found the answer it looked so easy to me, and thanks for the comments, they helped my to find the right answer...

Categories

Resources