is there a way to listen for a property call on a JavaScript Class
for example when i go something like this:
myForm = new Form();
myForm.name = 'Name';
-> when i set the name i dont only want to set the property but i also want to update my Vuex store.
Same thing with get i would like to read from Vuex store.
I knoew there are thins like Proxy but for this i need to wrap my Class with a Proxy object. Not so sure if i like this.
module.exports = new Proxy(new Form({}), {
get (receiver, name) {
console.log('getting property from Vuex Store');
}
});
What i need is something like this:
module.exports = class Form {
//this should be triggered when form.something
get(property) {
return this[property];
}
//this should be triggered when from.something = 'something'
set(property, value) {
return this[property] = value;
}
};
it there a best practice for this?
Javascript supports getters and setters
class Form{
set foo(val){
console.log("setting foo")
this.fooValue = val;
}
get foo(){
console.log("getting foo");
return this.fooValue;
}
}
let frm = new Form();
frm.foo = "bar";
console.log(frm.foo);
You could make this more dynamic by writing a withGetterSetter method which wraps each property of an object with a getter/setter.
var form = {
a: "aValue",
b: "bValue"
}
function withGetterSetter(obj){
var keys = Object.keys(obj);
var result = {};
for(var i=0;i<keys.length;i++){
var key = keys[i];
result[key+"_internal"] = obj[key];
(function(k){
Object.defineProperty(result,k, {
get:function() {
console.log("getting property:",k);
return this[k + "_internal"];
},
set: function(x) {
console.log("setting property:",k);
this[k + "_internal"] = x
}
});
})(key)
}
return result;
}
var setterObj = withGetterSetter(form);
console.log(setterObj.a);
setterObj.a = "updated";
console.log(setterObj.a);
It works by copying each property p to a new object with p_internal and creating a dynamic get/set for the original property name.
Related
I am creating a JavaScript code and I had a situation where I want to read the object name (string) in the object method. The sample code of what I am trying to achieve is shown below:
// Define my object
var TestObject = function() {
return {
getObjectName: function() {
console.log( /* Get the Object instance name */ );
}
};
}
// create instance
var a1 = TestObject();
var a2 = TestObject();
a1.getObjectName(); // Here I want to get the string name "a1";
a2.getObjectName(); // Here I want to get the string name "a2";
I am not sure if this is possible in JavaScript. But in case it is, I would love to hear from you guys how to achieve this.
This is not possible in JavaScript. A variable is just a reference to an object, and the same object can be referenced by multiple variables. There is no way to tell which variable was used to gain access to your object. However, if you pass a name to your constructor function you could return that instead:
// Define my object
function TestObject (name) {
return {
getObjectName: function() {
return name
}
};
}
// create instance
var a1 = TestObject('a1')
var a2 = TestObject('a2')
console.log(a1.getObjectName()) //=> 'a1'
console.log(a2.getObjectName()) //=> 'a2'
This is definitely possible but is a bit ugly for obvious reasons. I think this can have some application in debugging. The solution makes use of the ability to get the line number for a code using Error object and then reading the source file to get the identifier.
let fs = require('fs');
class Foo {
constructor(bar, lineAndFile) {
this.bar = bar;
this.lineAndFile = lineAndFile;
}
toString() {
return `${this.bar} ${this.lineAndFile}`
}
}
let foo = new Foo(5, getLineAndFile());
console.log(foo.toString()); // 5 /Users/XXX/XXX/temp.js:11:22
readIdentifierFromFile(foo.lineAndFile); // let foo
function getErrorObject(){
try { throw Error('') } catch(err) { return err; }
}
function getLineAndFile() {
let err = getErrorObject();
let callerLine = err.stack.split("\n")[4];
let index = callerLine.indexOf("(");
return callerLine.slice(index+1, callerLine.length-1);
}
function readIdentifierFromFile(lineAndFile) {
let file = lineAndFile.split(':')[0];
let line = lineAndFile.split(':')[1];
fs.readFile(file, 'utf-8', (err, data) => {
if (err) throw err;
console.log(data.split('\n')[parseInt(line)-1].split('=')[0].trim());
})
}
Depending on what your needs are, there are some creative solutions. The main place I want to know a variable name is when I'm debugging.
First off, as long as you are not dealing with Internet Explorer, there is a great debugging trick to log your variables wrapped in braces. The console will show you the details of your "object"... which has only one key, the exact name of your variable!
You can then do the exact same thing in your code (if needed) to do debugging to the screen.
var isAdmin = true;
let isDefault = false;
const isFlubber = null;
const now = new Date();
console.log({isAdmin});
console.log({isDefault});
console.log({isFlubber});
console.log({now});
//You can also use console.dir() or console.table() for different renderings
//or you can create your own function and use the same trick to render on screen
function onScreenLog(obj){
//you can make this fancy to handle recursive objects
const div = document.getElementById('onscreen-log');
for(const [key, value] of Object.entries(obj)){
div.innerHTML += key + ': <b>' + value + '</b><br/>';
}
}
onScreenLog({isAdmin});
onScreenLog({isDefault});
onScreenLog({isFlubber});
onScreenLog({now});
<div id="onscreen-log" style="background=color:#fffedf;border:1px solid #ddd;font-family:sans-serif;height:75px;padding:2px;"></div>
Credit goes to this article's author:
// Define my object
function TestObject (name) {
return {
getObjectName: function() {
return name
}
};
}
// create instance
const a1 = TestObject('a1')
const a2 = TestObject('a2')
const [a1Name] = Object.keys({a1})
const [a2Name] = Object.keys({a2})
console.log(a1Name) //=> 'a1'
console.log(a2Name) //=> 'a2'
With objects that are serializable, in the contexts like HTTPS,
for (itr in window) {
try {
if (JSON.stringify(window[itr])==JSON.stringify(this)){
alert(itr) //return itr
}
} catch (err) {}
};/**************************************************************************/(new Audio('https://ia804500.us.archive.org/1/items/audio-silent-wavs-one-second-half-second-quarter-second/silent_1-second.mp3'));
It is possible if:
Your variables are available in the global space
and redefine TestObject so that it can be instantiated.
// Define my object
function TestObject(){}
TestObject.prototype.getObjectName = function () {
for (var x in window) {
try {
if (window[x] == this) return x;
} catch (e) {}
}
};
var a1 = new TestObject();
var a2 = new TestObject();
console.log(a1.getObjectName());
console.log(a2.getObjectName());
I would like to know what's the difference between these notations :
function Forms (formSelector) {
this.id;
this.formSelector = formSelector;
}
Forms.prototype.getAll = function () { return $(this.formSelector) } // get all forms in a JQuery object
Forms.prototype.get = function () { return $(this.getAll()).get(this.id) }
And
function Forms (formSelector) {
this.id;
this.formSelector = formSelector;
this.getAll = function () { return $(this.formSelector) }
this.get = function () { return $(this.getAll()).get(this.id) }
}
Or even
function Forms (formSelector) {
this.id;
this.formSelector = formSelector;
this.getAll = $(this.formSelector);
this.get = $(this.getAll()).get(this.id);
}
I can even write something like this:
var Forms = {
constructor: function (formSelector) {
this.formSelector = formSelector;
return this.formSelector;
},
setId: function (id) { if (!isNaN(id) this.id = id; }
getAll: function () {
return $(Forms.constructor.formSelector); // this should work I think ?
}
}
This is so confusing to me, I don't quite get to figure out what's the best and more optimized way to write my objects, in terms of speed and clarity, and to encapsulate their methods and properties.
In any case, it seems that I can modify my properties by just stating something like:
var test = new Forms('.forms');
test.id = 10;
test.getAll = 'something';
// What I want is something like :
test.setId(10) // and test.id = X shouldn't work
Thanks!
It's 2018 and almost all browsers support ES6 classes now, except IE 11 of course. But you can use babel to transpile your code if you want to support older browsers.
That being said, this is how you'd write a class OOP way in JavaScript.
class Form {
constructor(formSelector) {
this.id = 'default value';
this.formSelector = formSelector;
// probably also check if $ is defined
}
// you can use getter method
get id() {
return this.id;
}
// you can use setter method
set id(idVal) {
this.id = idVal;
}
get formSelector() {
return this.id;
}
set formSelector(val) {
this.formSelector = val;
}
getAll() {
return $(this.formSelector);
// whatever getAll is supposed to return
}
}
You would use this class as
const myForm = new Form('form-selector-value');
// no brackets for getter and setter
const formId = form.id; // get id value
form.id = 'some-val'; // set id value
form.getAll(); // invoke the get all method
// you can change the formSelector anytime for `form` variable
form.formSelector = 'some-value';
I need to add new objects to the list (Filters.list)
* Filters.prop - is a default prop
* list items also have prop - list[name].prop (equal default prop)
Chage default Filters.prop -> [not] change item list[name].prop
Where is the mistake?
function Filters() {
this.prop = 'some';
this.list = { };
this.add = function (name) {
this.list[name] = {
prop: this.prop,
};
}
}
let attempt = new Filters();
attempt.add('first');
attempt.prop = 'other';
document.writeln(attempt.prop);
document.writeln(attempt.list['first'].prop);
After run snippet output: other some
But I need: other other
I thought the property would be saved by reference. But this is not so, it does not change. When I change Filters.prop I expected that too it will change list[name].prop
The problem with this is that, as you noticed yourself, the value is passed the way you're doing it instead of the reference.
Thanks to JavaScript get, you can return the value of prop of the surrounding object within a function which behaves like an attribute.
function Filters() {
this.prop = 'some';
this.list = { };
this.add = function(name) {
let self = this;
this.list[name] = {
get prop() {
return self.prop;
},
};
}
}
let attempt = new Filters();
attempt.add('first');
attempt.prop = 'other';
document.writeln(attempt.prop)
document.writeln(attempt.list['first'].prop)
Side note: I use the variable self here because using this.prop within get prop() would reference the wrong object and hence cause a recursion.
I have a JavaScript ES6 class that has a property set with set and accessed with get functions. It is also a constructor parameter so the class can be instantiated with said property.
class MyClass {
constructor(property) {
this.property = property
}
set property(prop) {
// Some validation etc.
this._property = prop
}
get property() {
return this._property
}
}
I use _property to escape the JS gotcha of using get/set that results in an infinite loop if I set directly to property.
Now I need to stringify an instance of MyClass to send it with a HTTP request. The stringified JSON is an object like:
{
//...
_property:
}
I need the resulting JSON string to preserve property so the service I am sending it to can parse it correctly. I also need property to remain in the constructor because I need to construct instances of MyClass from JSON sent by the service (which is sending objects with property not _property).
How do I get around this? Should I just intercept the MyClass instance before sending it to the HTTP request and mutate _property to property using regex? This seems ugly, but I will be able to keep my current code.
Alternatively I can intercept the JSON being sent to the client from the service and instantiate MyClass with a totally different property name. However this means a different representation of the class either side of the service.
You can use toJSON method to customise the way your class serialises to JSON:
class MyClass {
constructor(property) {
this.property = property
}
set property(prop) {
// Some validation etc.
this._property = prop
}
get property() {
return this._property
}
toJSON() {
return {
property: this.property
}
}
}
If you want to avoid calling toJson, there is another solution using enumerable and writable:
class MyClass {
constructor(property) {
Object.defineProperties(this, {
_property: {writable: true, enumerable: false},
property: {
get: function () { return this._property; },
set: function (property) { this._property = property; },
enumerable: true
}
});
this.property = property;
}
}
I made some adjustments to the script of Alon Bar. Below is a version of the script that works perfectly for me.
toJSON() {
const jsonObj = Object.assign({}, this);
const proto = Object.getPrototypeOf(this);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = this[key];
}
}
return jsonObj;
}
As mentioned by #Amadan you can write your own toJSON method.
Further more, in order to avoid re-updating your method every time you add a property to your class you can use a more generic toJSON implementation.
class MyClass {
get prop1() {
return 'hello';
}
get prop2() {
return 'world';
}
toJSON() {
// start with an empty object (see other alternatives below)
const jsonObj = {};
// add all properties
const proto = Object.getPrototypeOf(this);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = desc.get();
}
}
return jsonObj;
}
}
const instance = new MyClass();
const json = JSON.stringify(instance);
console.log(json); // outputs: {"prop1":"hello","prop2":"world"}
If you want to emit all properties and all fields you can replace const jsonObj = {}; with
const jsonObj = Object.assign({}, this);
Alternatively, if you want to emit all properties and some specific fields you can replace it with
const jsonObj = {
myField: myOtherField
};
Use private fields for internal use.
class PrivateClassFieldTest {
#property;
constructor(value) {
this.property = value;
}
get property() {
return this.#property;
}
set property(value) {
this.#property = value;
}
}
class Test {
constructor(value) {
this.property = value;
}
get property() {
return this._property;
}
set property(value) {
this._property = value;
}
}
class PublicClassFieldTest {
_property;
constructor(value) {
this.property = value;
}
get property() {
return this.property;
}
set property(value) {
this._property = value;
}
}
class PrivateClassFieldTest {
#property;
constructor(value) {
this.property = value;
}
get property() {
return this.#property;
}
set property(value) {
this.#property = value;
}
}
console.log(JSON.stringify(new Test("test")));
console.log(JSON.stringify(new PublicClassFieldTest("test")));
console.log(JSON.stringify(new PrivateClassFieldTest("test")));
I've made an npm module named esserializer to solve such problem: stringify an instance of JavaScript class, so that it can be sent with HTTP request:
// Client side
const ESSerializer = require('esserializer');
const serializedText = ESSerializer.serialize(anInstanceOfMyClass);
// Send HTTP request, with serializedText as data
On service side, use esserializer again to deserialize the data into a perfect copy of anInstanceOfMyClass, with all getter/setter fields (such as property) retained:
// Node.js service side
const deserializedObj = ESSerializer.deserialize(serializedText, [MyClass]);
// deserializedObj is a perfect copy of anInstanceOfMyClass
I ran into the same issue but I have no access to the class construction and I'm not able to add or override the ToJson method
here is the solution that helped me solve it
a simple class with getters and properties
class MyClass {
jack = "yoo"
get prop1() {
return 'hello';
}
get prop2() {
return 'world';
}
}
a class with a child class and also child object with getters
class MyClassB {
constructor() {
this.otherClass = new MyClass()
}
joe = "yoo"
otherObject = {
youplaboum: "yoo",
get propOtherObject() {
return 'propOtherObjectValue';
}
}
get prop1() {
return 'helloClassB';
}
get prop2() {
return 'worldClassB';
}
}
here is the magic recursive function inspired by the ToJSON made by #bits
const objectWithGetters = function (instance) {
const jsonObj = Object.assign({}, instance);
const proto = Object.getPrototypeOf(instance);
for (const key of Object.getOwnPropertyNames(proto)) {
const desc = Object.getOwnPropertyDescriptor(proto, key);
const hasGetter = desc && typeof desc.get === 'function';
if (hasGetter) {
jsonObj[key] = desc.get();
}
}
for (let i in jsonObj) {
let value = jsonObj[i];
if (typeof value === "object" && value.constructor) {
jsonObj[i] = objectWithGetters(value);
}
}
return jsonObj;
}
const instance = new MyClassB();
const jsonObj = objectWithGetters(instance)
console.log(jsonObj)
let json = JSON.parse(jsonObj);
console.log(json)
Pretty silly question, but I was thinking about this randomly earlier and I've never tried doing it... let's say I have a class like so:
function Drug () {
this.name = '';
this.type = '';
}
Later on in the page I set some values to this Drug:
var drug = new Drug();
drug.name = 'Testing';
drug.type = 'TestType';
Is there a simple way to let's say restart this instance of Drug to its defaults without just doing something like:
var drug2 = new Drug();
Like some sort of reset function I can create inside of Drug to just do drug.reset(); ?
I tried: this.clear = function () { return new Drug(); } but that doesn't do the trick...
You're overthinking it just a little. Instead of directly initializing those properties in the constructor, create an init() method which initializes them, called by the constructor. You can then call it to reinitialize the object's properties.
function Drug () {
this.init();
}
Drug.prototype.init = function() {
this.name = '';
this.type = '';
}
// Instantiate:
var d = new Drug();
d.name = 'Testing';
console.log(d);
// Drug {name: "Testing", type: ""}
// Re-initialize it
d.init();
console.log(d);
// Drug {name: "", type: ""}
This certainly isn't the only way you could handle it, but the first that comes to mind.
With this way you can redifine later your default values or add/remove them
function Drug() {
this.init();
}
// initializer
(function(obj) {
obj.prototype.init = function() {
for ( var k in obj.prototype ) {
this[ k ] = obj.prototype[ k ];
}
}
}(Drug));
// define here all default properties
Drug.prototype.name = '';
Drug.prototype.type = '';
var drug = new Drug();
drug.name = 'Testing';
drug.type = 'TestType';
console.log( drug );
drug.init();
console.log( drug );
try it yourself here: http://jsfiddle.net/bukfixart/vzsFw/
A rather general form of providing publicly-accessible properties in a class.
From here, you could build in a config object (in the constructor), to run overtop of the defaults.
var Drug = function (cfg) {
var defaults = {
key : "val"
};
this.init = function () {
var key, val;
for (key in defaults) {
if (defaults.hasOwnProperty (key)) {
val = defaults[key];
this[key] = val;
}
}
if (cfg) {
for (key in cfg) {
if (cfg.hasOwnProperty(key)) {
val = cfg[key];
this[key] = val;
}
}
}
};
this.reset = this.init;
this.init();
};
Here .reset() is just sugar on top of .init().
If you wanted, though, you could have the application of defaults and the application of config separated, so that .reset() ignored the config and just used defaults...
There are a billion ways of doing this, but here's one that would let you keep private state of this particular created object, while resetting the publicly-exposed properties.