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.
Related
I want to add functions to JSON Objects, but I can't find function for assigning to all objects, only to one.
This code works with Arrays:
Object.defineProperty(Array.prototype, 'random', {
value: () => {
return this[Math.floor(Math.random() * this.length)];
},
});
I've also found this code:
const obj = {name: 'Bob'};
obj.fullName = function() { return this.name }
But that one only works for specific object, not all of them.
Is it event possible to write global functions for all JSON Objects, and if is, then how to do it?
You could add the function to Object.prototype. Note that this is not considered a very good practice because it could impact the rest of the code (like shown in the comments):
Object.prototype.fullName = function() { return this.name; };
const obj = { name: 'Bob' };
console.log(obj.fullName());
You should consider doing this instead:
const baseObject = { fullName: function() { return this.name; } };
const obj = Object.create(baseObject, { name: { value: 'Bob', writable: true } });
console.log(obj.fullName());
And if your target runtime (browser?) supports ECMAScript 6, you could also create a dedicated class for this:
class MyClass {
constructor(name) {
this.name = name;
}
fullName() { return this.name; }
}
const bob = new MyClass('Bob');
console.log(bob.fullName());
Finally, the class syntax for ECMAScript 5:
function MyClass(name) {
this.name = name;
}
MyClass.prototype.fullName = function() { return this.name; }
const bob = new MyClass('Bob');
console.log(bob.fullName());
Iām transforming data I receive from an API. The frontend requires some calculations to be displayed.
What is the proper way to handle the data transformation?
Should I be defining a property to the object being passed? If so, why
Is this a good use case to use setters and getters or would that be unnecessary?
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
class Dog {
constructor(data) {
//do I need to set this.dog to the data object, what's the benefit of doing so?
this.dog = data;
this.name = this.dog.dog_name;
// vs
this.name = data.dog_name;
//Should I use setters and getters?
this.color = this.dog.coat_color;
// vs
this._color = this.dog.coat_color;
this.age = this.calculateAge();
}
calculateAge() {
return Date.now().getTime() - this.dog.born_time;
}
//Is this a good case where I should using getters to access the properties or would that be superfluous?
//should I be setting the properties with setters in this case?
get color() {
return this._color;
}
}
const dog = new Dog(dogData)
Your don't need to make a copy of data into your class.
You can assign the class fields directly (using object destructuring to be more readable).
const data = {
dog_name: 'filo',
born_time: 1530983852,
coat_color: 'brown'
}
class Dog {
// Directly assign values
constructor({ dog_name, born_time, coat_color }) {
this.name = dog_name
this.bornAt = born_time
this.color = coat_color
}
// Getter for computed properties
get age() {
return Date.now() - this.bornAt
}
}
const dog = new Dog(data)
Getters are needed only for computed property (dynamic or formatted values).
Good exemple:
class Person {
constructor({ firstname, lastname }) {
this.firstname = firstname
this.lastname = lastname
}
get fullname() {
return `${this.firstname} ${this.lastname}`
}
}
class Dog {
constructor(data) {
const {
dog_name: name,
born_time: age,
coat_color: color
} = data;
Object.assign(this, {
name,
age,
color
});
}
}
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
const dog = new Dog(dogData);
console.log(dog.name);
Q:
Shall I nevertheless throw in a possible read only approach? ā Peter Seliger
A:
It wouldn't hurt. I appreciate the different approaches. ā Matthew Moran
... here we go ...
// module start ... e.g. file: "Dog.js"
// locally scoped helper function
function calculateAge(dateOfBirth) {
return (Date.now() - dateOfBirth);
}
/*export default */class Dog {
constructor(initialValue) {
Object.defineProperties(this, {
valueOf: { // just in order to hint what `initialValue` might still be good for.
value: function () {
return Object.assign({}, initialValue);
}
},
name: {
value: initialValue.dog_name,
enumerable: true
},
color: {
value: initialValue.coat_color,
enumerable: true
},
age: {
get() {
return calculateAge(initialValue.born_time);
},
enumerable: true,
}
});
}
}
// module end.
// test
const dogData = {
dog_name: "filo",
born_time: 1530983852,
coat_color: "brown"
};
const dog = new Dog(dogData);
console.log('Object.keys(dog) : ', Object.keys(dog));
console.log('dog.valueOf() : ', dog.valueOf());
console.log('dog.age : ', dog.age);
console.log('dog.name : ', dog.name);
console.log('dog.color : ', dog.color);
console.log('(dog.age = 298146912) : ', (dog.age = 298146912) && dog.age);
console.log('(dog.name = "spot") : ', (dog.name = "spot") && dog.name);
console.log('(dog.color = "black") : ', (dog.color = "black") && dog.color);
.as-console-wrapper { max-height: 100%!important; top: 0; }
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.
I have a factory with a getter and setter
.factory('myService', function() {
var car = null;
return {
car: car,
get: function get() {
return car;
},
set: function set(newCar) {
car = newCar;
}
};
});
I am writing test for it but I cannot call the set method and have it actually set car to newCar
myService.set = sinon.spy();
myService.get = sinon.spy()
it('should set car to new car', function () {
var newCar = ['a','b','c'];
expect(myService.car).toEqual(null); //pass
myService.set(newCar);
dump(myService.car); //null
expect(myService.set).toHaveBeenCalledWith(newCar);//pass
expect(myService.get).toHaveReturned(newCar);//fail
});
Any advice on what I am doing wrong here?
There are more problems here.
One is that the .car property will always be null.
var car = null;
return {
car: car,
get: function get() {
return car;
},
set: function set(newCar) {
car = newCar;
}
};
Here you initialize it with car which is null. There will be no reference between them. This will always be null since you never change that property on the object:
dump(myService.car); //null
You might do something like:
return {
car: null,
get: function get() {
return this.car;
},
set: function set(newCar) {
this.car = newCar;
}
};
But with this you might run into some this context issues later. Why are you trying to expose car if you have a getter for it?
The other thing is that you replace the entire get and set functions with this:
myService.set = sinon.spy();
myService.get = sinon.spy();
Sinon knows nothing about your original get and set.
You should do it like this:
sinon.spy(myService, 'set');
So sinon can wrap your function with a spy while preserving it's original behavior. Check Sinon documentation
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 ;