Javascript setter override value before setting - javascript

What I essentially want to do is this:
Blog.prototype = {
set content(content) {
this.content = JSON.parse(content);
}
}
However, this results in infinite recursion.
I know I can do something like:
set content(content) {
this._content = JSON.parse(content);
},
get content() {
return this._content;
}
However, when I do JSON.stringify(blog), it doesn't include content, but includes _content, which is undesirable.
How can I go about doing this?

Make the "_content" variable non-enumerable.
Blog.prototype = {
set content(newContent) {
Object.defineProperty(this, "_content", {
value: JSON.parse(newContent),
writable: true
});
},
get content() {
return this._content;
}
};
By default, an the "enumerable" flag for an object property is false if not supplied explicitly in the call to defineProperty().
Someday the Symbol type will be universally supported, and it'd be a better choice for this because you can make a guaranteed unique property key that way. If you don't need IE support and can use Symbols:
Blog.prototype = () => {
const internalContent = Symbol("content key");
return {
set content(newContent) {
this[internalContent] = newContent;
},
get content() {
return this[internalContent];
}
};
}();
Symbol-keyed properties are ignored by JSON.stringify() so you don't have to bother with defineProperty(). The nice thing about the Symbol approach is that you don't have to worry about collisions. Every Symbol instance returned from Symbol() is distinct.

Use Set and Get with _content, and implement .toJson() to provide JSON.stringify with content instead of _content.
toJSON() {
return {
content: this._content
}
}
According to MDN .toJSON() role is:
If an object being stringified has a property named toJSON whose value
is a function, then the toJSON() method customizes JSON
stringification behavior: instead of the object being serialized, the
value returned by the toJSON() method when called will be serialized.
Using with a constructor function
function Blog() {}
Blog.prototype = {
set content(content) {
this._content = JSON.parse(content);
},
get content() {
return this._content;
},
toJSON() {
return {
content: this._content
}
}
};
var blog = new Blog();
blog.content = '{ "a": "5" }';
console.log(blog.content);
console.log(JSON.stringify(blog));
Using with ES6 class
class Blog {
set content(content) {
this._content = JSON.parse(content);
}
get content() {
return this._content;
}
toJSON() {
return {
content: this._content
}
}
};
const blog = new Blog();
blog.content = '{ "a": "5" }';
console.log(blog.content);
console.log(JSON.stringify(blog));

I was able to solve this by building off Pointy's answer:
var Blog = function () {
var content;
Object.defineProperty(this, "content", {
get: function() {
return content;
},
set: function(value) {
content = JSON.parse(value);
},
enumerable: true,
});
};
The trick here is the enumerable flag, which is false by default.

Related

How to prevent constructor parametres changing

I have a class File, and constructor that accept fullname parameter. How can i prevent changing of this? I tried it with setter and getter, but it doesnt work
class File {
constructor(fullName) {
this.fullName = fullName;
}
get fullname() {
return this.fullname;
}
set fullname(newValue) {
if (newValue) {
newValue = this.fullName;
}
}
}
let example = new File("example.txt");
example.fullName = "modified.txt";
console.log(example.fullName); // should be example.txt
You can create a readonly property with Object.defineProperty:
class File {
constructor(fullName) {
Object.defineProperty(this, 'fullName', {
enumerable: true,
writable: false, // not necessary (because default) but more explicit,
value: fullName,
});
}
}
let example = new File("example.txt");
example.fullName = "modified.txt";
console.log(example.fullName);
Note that assigning to the property would throw an error in strict mode.
If you want to be in strict mode but also want to silently ignore the assignment, you could take the getter/setter approach ~but you will still have to store the real value somewhere on the object, which means it could still be accessed and be modified if one knows which property to access.~ but it's a bit more evolved. You'd basically create a getter and setter for every instance, thus avoiding to have to store the original value on the object itself:
"use strict";
class File {
constructor(fullName) {
Object.defineProperty(this, 'fullName', {
enumerable: true,
set: value => {}, // ignore
get: () => fullName,
});
}
}
let example = new File("example.txt");
example.fullName = "modified.txt";
console.log(example.fullName);
Private properties, which are a relatively new feature of JavaScript, might make this a bit "nicer" (subjective):
class File {
#fullName;
constructor(fullName) {
this.#fullName = fullName;
}
get fullName() {
return this.#fullName;
}
set fullName(newValue) {
// ignore new value
}
}
let example = new File("example.txt");
example.fullName = "modified.txt";
console.log(example.fullName);
Using ES2020+ syntax:
class File {
#fullName = null;
constructor(fullName) {
this.fullName = fullName;
}
get fullName() {
return this.#fullName;
}
set fullName(newValue) {
this.#fullName ??= newValue;
}
}
let example = new File("example.txt");
example.fullName = "modified.txt";
console.log(example.fullName); // should be example.txt
There are several places that you misspelled fullName to fullname, mind the cases matter in JavaScript.

Using getter and setter on an object property that has his own properties

I'm fairly new to getters and setters and am looking for a way to listen for changes in an object to store the data immediately, without calling a Save() function everytime a value gets changed. This is how I do it right now:
var myObject = {
Data: {
enabled: true,
show: false
},
Save: function () {
//store myObject.Data to local storage
},
Load: function () {
//load data from local storage and assign it to myObject.Data
},
doSomething: function () {
myObject.Load();
if (myObject.Data.enabled) {
myObject.Data.show = true;
myObject.Save();
}
}
Now I would like to optimize this code so everytime a property in myObject.Data is changed, myObject.Save() is executed. The problem I'm experiencing is that it seems only possible to define a getter for a property that has just one value, but not for a property that is an object itself.
var myObj = {
_Data: {
a: 0,
b: 1,
c: 3
},
set Data (a) {
console.log(a);
}
};
myObj.Data.a = 2;
This obviously doesn't work since myObj.Data is not an object and doesn't have the same properties as myObj._Data.
Thanks in advance for any help.
You are likely interested in the Proxy object.
I used a very simple debounce function callHandler in order to avoid calling the onSet method dozens of times during array modifications. Otherwise, [1, 2, 3].splice(0, 1) would call the set handler once per item in the original array.
'use strict';
var myObject = {
Data: {
a: [1, 2, 3],
b: {c: ['test']}
},
Save: function() {
console.log('Save called');
},
}
function recursiveProxy(target, onSet) {
// For performance reasons, onSet will only be called one millesecond
// after the set handler has last been called.
var timeout;
function callHandler() {
clearTimeout(timeout);
timeout = setTimeout(onSet, 1);
}
var recursiveHandler = {
get: function(target, property) {
// If the property is something that could contain another object,
// we want to proxy it's properties as well.
if (typeof target[property] == 'object' && target[property] != null) {
return new Proxy(target[property], recursiveHandler);
}
return target[property];
},
set: function(target, property, value) {
console.log('Set called - queueing onSet');
callHandler();
target[property] = value;
return true;
}
}
return new Proxy(target, recursiveHandler);
}
myObject.Data = recursiveProxy(myObject.Data, myObject.Save);
myObject.Data.a.splice(0, 1);
myObject.Data.b.c[0] = 'test 2';
I believe you are looking for Defining a getter on existing objects using defineProperty
To append a getter to an existing object later at any time, use
Object.defineProperty().
var o = { a:0 }
Object.defineProperty(o, "b", { get: function () { return this.a + 1; } });
console.log(o.b) // Runs the getter, which yields a + 1 (which is 1)
For e.g:
var Data = {
enable: true,
show: false
};
Object.defineProperty(Data, 'doSomething', {
get: function() {
// get something;
},
set: function(something) {
// set something
}
});

What's the syntax to add a function other than an accessor?

I'm learning the JavaScript with Node. I like the idea of creating objects with factories, and after reading a lot on this subject, I chose to create objects with this code:
// ES6 only
'use strict';
// The base object, "object literal" syntax
let animal2 = {
// public member
animalType: 'animal',
// public method
describe() {
return `This is "${this.animalType}"`;
}
};
// factory function which serves 2 purposes:
// - encapsulation, that's where private things are declared thanks to closures
// - the "real" object creation, this prevents to use "new" which is not really Js-ish
let afc = function afc() {
// private member
let priv = "secret from afc";
return Object.create(animal2, {
// Object customisation
animalType: { value: 'animal with create'},
// object extension. The new objects created here get 3 new members:
// - a private member
// - a new property
// - a new method to access the private member
// new public member
color: { value: 'green' },
secret: {
get: function () { return priv; },
set: function (value) { priv = value; },
},
KO1() {
console.log("KO1");
},
KO2: function() {
console.log("KO2");
}
});
}
// creation of an animal instance
let tac = afc();
My problem is I can't figure out what's the syntax to add a function which can manipulate private data while not being just an accessor. I put here 2 examples which came to my mind (KOx), but as their names suggest, this syntax leads to: "KOx is not a function".
Object.create expects an object of property descriptors as its second argument. This is why you have to use {value: …} or {set: …, get: …} everywhere.
And in fact you'd have to do the same for a method - which is just a standard property with a function as its value:
…
KO3: {value: function() {
…
}},
…
However, I'd avoid using property descriptors when you don't need them. Object.assign is a better fit:
return Object.assign(Object.create(animal2, {
secret: {
get() { return priv; },
set(value) { priv = value; },
}
}), {
animalType: 'animal with create',
color: 'green',
KO1() {
console.log("KO1");
},
KO2: function() {
console.log("KO2");
}
});
Why don't just use the getter syntax?
return {
__proto__: animal2, // To be honest __proto__ is not a good thing to use
animalType: 'animal with create',
color: 'green',
get secret() { return priv; },
set secret(value) { priv = value; },
get KO3() { console.log("KO3"); },
// or just the following, if you would like it to be a regular member function
// KO3() { console.log("KO3"); },
};
Or without explicit __proto__:
const result = {
animalType: 'animal with create',
color: 'green',
get secret() { return priv; },
set secret(value) { priv = value; },
get KO3() { console.log("KO3"); },
};
Object.setPrototypeOf(result, animal2);
return result;

Javascript private functions and object.prototype extend

I have the following angularJS service
define(["angular"], function(Angular) {
var dataStorageService = function() {
var serviceConstructor = function() {
var _getColor = function(color) {
return this.config.categoryColorMapping.colors[color];
}
}
var serviceInstance = new serviceConstructor();
angular.extend(serviceInstance.prototype, {
config: {
numberOfMessagesDisplayed: 5,
maxTitleLength: 48,
maxPreambleLength: 140,
categoryColorMapping: {
colors : {
nyheter: '#2B2B2B',
sport: '#F59331',
underholding: '#F9B00D'
},
categories: {
nyheter: _getColor('nyheter'),
sport: _getColor('sport'),
underholding: _getColor('underholding')
}
}
},
get: function(param) {
if(this.config.hasOwnProperty(param)) {
return this.config[param];
} else {
console.warn('Playlist::configService:no "' + param + '" config found');
return false;
}
},
set: function(param, value) {
this.config[param] = value;
}
});
return serviceInstance;
};
return dataStorageService;
});
now my goal is to make public the following methods:
get
set
and I want '_getColor' method private but I want to use it within the JSON object config. When I run the code I have
"ReferenceError: _getColor is not defined"
is it possibie to achievie it this way? (to have _getColor private and use it within the JSON object within angular.extend?)
Functions can be shared and still be private, instance specific private members have to be defined in the constructor though. Since your private function doesn't need to access instance specific private members you can do the following:
define(["angular"], function(Angular) {
var dataStorageService = function() {
var serviceConstructor = function() {
}
var serviceInstance = new serviceConstructor();
//IIFE returning object that will have private members as closure
// privileged methods have to be in the same function body as the
// private fucnction
serviceInstance.prototype = (function() {
var _getColor = function(instance, color) {
return instance.config.categoryColorMapping.colors[color];
};
return {
constructor: serviceConstructor
,config: {
numberOfMessagesDisplayed: 5,
maxTitleLength: 48,
maxPreambleLength: 140,
categoryColorMapping: {
colors : {
nyheter: '#2B2B2B',
sport: '#F59331',
underholding: '#F9B00D'
},
categories: {
//since categories is a sub object of serviceinstance.categorycolormapper
// it is not possible to get the instance of serviceinstance
// at this time unless you set it in the constructor
// solution could be that each serviceinstance has it's own categorycolormaper
// and when categorycolormapper is created pass the serviceinstance instance
nyheter: _getColor(this,'nyheter'),
sport: _getColor(this, 'sport'),
underholding: _getColor(this, 'underholding')
}
}
},
get: function(param) {
if(this.config.hasOwnProperty(param)) {
return this.config[param];
} else {
console.warn('Playlist::configService:no "' + param + '" config found');
return false;
}
},
set: function(param, value) {
this.config[param] = value;
}
}
}());
return serviceInstance;
};
return dataStorageService;
});
More info on constructor functions and prototype can be found here: https://stackoverflow.com/a/16063711/1641941
Functions added to the prototype are defined outside the lexical scope of the constructor, and therefore have no access to "private" methods.
The former are shared between all instances, and the latter are per-instance. The only way to get around this is to explicitly export the (per-instance) function as a property of the instance, making it non-private.
Within the definition of serviceConstructor add following line, after definition of _getColor
serviceConstructor.prototype._getColor = _getColor ;

Is it possible to create read only members in JavaScript Object Literal Notation?

I have following JavaScript Object Literal Notiation object
var Parameters= {
modal_window:{
backdrop:true,
keyboard:true,
show:true,
remote:false,
type:{
normal:function(){
this.footer.button.accept.type='btn btn-primary';
this.header.type='modal-header';
},
success:function(){
this.footer.button.accept.type='btn btn-success';
this.header.type='modal-header alert alert-success';
},
info:function(){
this.footer.button.accept.type='btn btn-info';
this.header.type='modal-header alert alert-info';
},
error:function(){
this.footer.button.accept.type='btn btn-danger';
this.header.type='modal-header alert alert-error';
},
warning:function(){
this.footer.button.accept.type='btn btn-warning';
this.header.type='modal-header alert';
}
}
},
header:{
title:undefined,
type:this.window.type.normal.header
},
footer:{
button:
{
accept:{
title:'Accept',
click:undefined,
type:undefined
},
cancel:{
title:'Cancel',
click:undefined
}
}
}
};
Is it possible to make header.type and footer.button.accept.type read only variables which can be changed only through window.type.normal, window.type.success and etc.?
Clarifications:
I want to make some clarifications here. My Parameters.header.type
should be read only and should have default value. And when user
selects for example Parameters.modal_window.type.normal
Parameters.header.type must be changed.
Despite what everyone says, you can create read-only properties in modern browsers that supports Object.defineProperty.
var obj = {};
Object.defineProperty(obj, 'someProp', {
configurable: false,
writable: false,
value: 'initial value'
});
obj.someProp = 'some other value';
console.log(obj.someProp); //initial value
EDIT:
After reading your question again, I understand that you meant true private members or private variables. That can be accomplished by making use of closures and custom getters/setters.
Note: I simplified your object's structure for the sake of the example.
var Parameters = (function () {
var headerType = 'some value'; //private variable
return {
modal_window: {
type: {
normal: function () {
//custom logic
headerType = 'some new value'; //set private variable
}
}
},
header: {
get type() { return headerType; } //define a getter only
//for older browsers, you could just define a normal function
//which you would have to access like Parameters.header.type()
//type: function () { return headerType; }
}
};
})();
var header = Parameters.header;
console.log(header.type); //some value
header.type = 'some other val';
console.log(header.type); //some value
Parameters.modal_window.type.normal();
console.log(header.type); //some new value
Now that we know it is possible to enforce true privacy, I am not sure it's really worth it. Enforcing true privacy complicates the design and reduces testability (depending on the case). An approach that is far popular as well is to simply identify private members using a naming convention such as _myPrivateVar. This clearly indicates the itention and tells the programmers that they should treat that member like a private one.
You could make them functions, like this:
header:{
title:undefined,
type: function(){
return Parameters.modal_window.type.normal.header;
}
}
You can create a property and set it as non-writable. Your constructor would have to replace the values with the properties. If the variable that the property is returning is captured in a closure and not exposed to anything else, it will be as good as read-only. If it is not changed, you don't even need a closure, just use the value configuration option.
EDIT: As per your demand,
var Properties = function(obj) {
var makePropRecursive = function(prop) {
var old_prop = obj[prop];
delete obj[prop];
var prop_obj = {};
for (var attr in old_prop) {
if (old_prop.hasOwnProperty(attr)) {
Object.defineProperty(prop_obj, attr, {
value: old_prop[attr],
writable: false,
enumerable: true
});
}
}
Object.defineProperty(obj, prop, {
value: prop_obj,
writable: false,
enumerable: true
});
};
makePropRecursive('header');
makePropRecursive('footer');
return obj;
};
var props = new Properties({
modal_window:{
backdrop:true,
keyboard:true,
show:true,
remote:false,
type:{
normal:function(){
this.footer.button.accept.type='btn btn-primary';
this.header.type='modal-header';
},
success:function(){
this.footer.button.accept.type='btn btn-success';
this.header.type='modal-header alert alert-success';
},
info:function(){
this.footer.button.accept.type='btn btn-info';
this.header.type='modal-header alert alert-info';
},
error:function(){
this.footer.button.accept.type='btn btn-danger';
this.header.type='modal-header alert alert-error';
},
warning:function(){
this.footer.button.accept.type='btn btn-warning';
this.header.type='modal-header alert';
}
}
},
header:{
title:"Whatever",
type:"Type"
},
footer:{
button:
{
accept:{
title:'Accept',
click:undefined,
type:undefined
},
cancel:{
title:'Cancel',
click:undefined
}
}
}
});
console.log(props.header);
props.header = 17;
props.header.type = 18;
props.header.title = 19;
console.log(props.header);
props.header is unchanged: output shows
Object {title: "Whatever", type: "Type"}
Object {title: "Whatever", type: "Type"}
It's 3am and the recursive function isn't, so you can only "fix" one level of one object; also, it would be better if the values were copied onto this rather than returning obj; but it should not be too hard to polish it up.
If you need to have the values changeable, you can set up a private copy of the whole object inside the constructor, then make a getter (get: function(name) { return stuff.from.the.original.object }).
If you need to support IE 8 or earlier, you could create an accessor method that retrieves the value and then use a private variable to store the actual data. If you define your methods appropriately, the private variable could be set from them, but not set by the outside world. In IE8, there is no ability to define a read-only property so you'd have to use an accessor instead.
See Crockford's treatise on private member data: http://javascript.crockford.com/private.html for details on how to set up the private data that your accessor could be an interface for.
If you're willing to require IE9 or greater, then you could use a getter via Object.defineProperty() combined with a private variable in a closure. If there was no setter, then it couldn't be set from the outside world, but methods defined within the closure (described in Crockford's article) could still set the value of the private variable. You would have a read-only property that could also be set by a few of your own methods.
How about: Object.freeze()
You can find out more here: MDN Object.freeze
So:
Object.freeze(Parameters.modal_window.header);
...
Then in your your function that you want to be able to set them, you unfreeze them, change them, and re-freeze them.
You absolutely won't be able to change the frozen objects, by mistake anywhere else in your program.
This works in IE9+ chrome, firefox, and safari.
In newer versions of JavaScript, you can define how property access works:
var yourObj = function() {
var readOnly = "cannot be changed";
return {
get readOnly() { return readOnly; },
set readOnly(v) { return; },
specialSetter: function(something) {
if (something == "magic number") {
readOnly = "oops maybe it can";
}
}
};
}();
Now code can get the value like this:
var theValue = yourObj.readOnly;
without having to make a function call. However, if it tries to change the value:
yourObj.readOnly = "hello world";
then nothing will happen.
Either the setter or any other function can, when it wants to, still update the value that'll be returned when accessing the "readOnly" property. However, any attempt to just set the property directly will do nothing (unless the setter function decides it likes the value).
edit you'd want to make "specialSetter" be read-only probably, though nothing will be able to "break in" to the closure. Also, you might want to use Object.defineProperty to make "readOnly" not be writable, but I don't know whether that'd work out properly.
You can use the following revealing module pattern to hide variables and keep them from being changed but this wont stop anyone from changing the accessible "type" function.
Below in the code, the header property was changed to _header and made into a function. The property type was changed to _type and hidden by wrapping a return with an object notation to return "type" as a function instead of the property. Someone can change the type function to anything they want by over writing it, but they can't change the value of _type.
var Parameters = function () {
var _modal_window = function modal_window() {
var backdrop = true,
keyboard = true,
show = true,
remote = false;
return {
type: {
normal: function () {
this.footer.button.accept.type = 'btn btn-primary';
this.header.type = 'modal-header';
},
success: function () {
this.footer.button.accept.type = 'btn btn-success';
this.header.type = 'modal-header alert alert-success';
},
info: function () {
this.footer.button.accept.type = 'btn btn-info';
this.header.type = 'modal-header alert alert-info';
},
error: function () {
this.footer.button.accept.type = 'btn btn-danger';
this.header.type = 'modal-header alert alert-error';
},
warning: function () {
this.footer.button.accept.type = 'btn btn-warning';
this.header.type = 'modal-header alert';
}
}
};
}();
var _header = function header() {
var _type = 'This causes error';//this.window.type.normal.header;
return {
title: undefined, type: function () { return _type; }
};
}();
var _footer = function footer() {
return {
button:
{
accept: {
title: 'Accept',
click: undefined,
type: undefined
},
cancel: {
title: 'Cancel',
click: undefined
}
}
};
}();
return {
modal_window: _modal_window,
header: _header,
footer: _footer
};
}();

Categories