How do you create a javascript class with immutable properties - javascript

This question is specifically about preventing unwanted properties from being added to a javascript "class". I have a class called Animal.
function Animal(){
this.name="";
this.type="";
//20 other properties
}
What would be the easiest way for a user to create their own Animal and add 20 properties. I want to also prevent the user from accidentally adding incorrect properties.
my current method:
var myAnimal= new Animal();
myAnimal.name="fluffy";
myAnimal.type="cat";
myAnimal.typo="cat";
//adding 20 more properties would require typing myAnimal 20 more times
Plus if a user makes a typo it would add it as a new property.
I was hoping there would be something like this:
myAnimal=new Animal{
name:"fluffy",
type:"cat";
typo:"cat" //would throw an error
}
I've looked into Object.freeze Object.seal, Object.preventExtensions but not sure how they apply to classes.

You can Object.seal(this) at the end of the constructor to prevent new properties from being added:
function Animal() {
this.name = "";
this.type = "";
// 20 other properties
Object.seal(this);
}
and you can take an object with initial values in the constructor:
function Animal(initial) {
this.name = "";
this.type = "";
// 20 other properties
Object.seal(this);
Object.assign(this, initial);
}
used like so:
myAnimal = new Animal({
name: "fluffy",
type: "cat",
typo: "cat",
});
with the downside that the error doesn’t point directly at typo like it would if you used multiple assignment statements.

You can use Proxy For this.
You can set custom setter, what throw an Error when obj does not has the property.
You can do the same with get too in that case no more undefined cause of typo.
function Animal() {
this.name = "";
this.type = "";
return new Proxy(this, {
set: function(obj, prop, value) {
if (!obj.hasOwnProperty(prop))
{
throw Error("I don`t have this property: " + prop);
}
obj[prop] = value;
}
});
}
var cat = new Animal();
console.log(cat);
cat.name = "Rita";
console.log(cat);
cat.nama = "Rata";
console.log(cat);

Related

undefined result using prototype [javascript]

So I'm learning prototype using javascript, and tried some code :
function Employee(name) { this.name= name; }
var m = new Employee("Bob");
var working= { isWorking: true };
Employee.prototype = working;
alert(m.isWorking);
Unfortunately, I get an undefined message, instead of the true value. Is there a reason to this result?
I have made several tests. I've concluded that reassigning the prototype object causes any previously created instances of the Employee class to be unable to access any properties found inside the newly assigned prototype. Is this accurate?
Changing the prototype will not affect an already created object. It will only affect the objects created based on that object.
There is a property __proto__ which could be used to change the prototype, but its implementation is not required. ES6 does define setPrototypeOf method to change the prototype, but since it's only in ES6 the support may vary.
First off, you have created an instance of Employee before you set the prototype, so that object will not have inherited the new prototype values.
Next, any objects created after you have set the prototype will inherit the new prototype object.
Lastly, the object will have the isWorking property, rather than a working property.
So to redo your example:
function Employee(name) { this.name= name; };
var m1 = new Employee("Bob");
var working= { isWorking: true };
Employee.prototype = working;
var m2 = new Employee("Sam");
alert(m1.isWorking); // undefined
alert(m2.isWorking); // true
Simple fix is to properly assign it.
function Employee(name) {
this.name = name;
}
var m = new Employee("Bob");
var working = {
isWorking: true
};
Employee.prototype.working = working;
alert(m.working.isWorking);
A better fix for MULTIPLE employees is to make a class, then create instances of that: play around with it here: http://jsfiddle.net/MarkSchultheiss/p6jyqbgv/1/
"use strict";
function makeClassStrict() {
var isInternal, instance;
var constructor = function(args) {
if (this instanceof constructor) {
if (typeof this.init == "function") {
this.init.apply(this, isInternal ? args : arguments);
}
} else {
isInternal = true;
instance = new constructor(arguments);
isInternal = false;
return instance;
}
};
return constructor;
}
var EmployeeClass = makeClassStrict();
EmployeeClass.prototype.init = function(employeeName, isWorking) {
var defaultName = 'notbob';
this.name = employeeName ? employeeName : defaultName;
this.working = !!isWorking;
};
// call this to get the name property
EmployeeClass.prototype.getName = function() {
return this.name
};
//note no "new" needed due to the makeClassStrict that does that
var m = EmployeeClass("Bob");
alert(m.working +":"+ m.name);
m.working = true;
alert(m.working +":"+ m.name);
var notbob = EmployeeClass("Charlie",false);
alert(notbob.working +":"+ notbob.name);
alert(notbob.getName()+ m.getName());
You cannot override the entire prototype property and expect already existing instances to work. JavaScript doesn't work that way. But you can loop through the prototype object and unset anything already set, then loop through your new object, and set it to something else.
function Employee(name) { this.name= name; }
var m = new Employee("Bob");
var working= { isWorking: true };
for(var j in Employee.prototype){delete Employee.prototype[j];}//unset all properties, the same as setting to {}
for(j in working){Employee.prototype[j]=working[j];}//set the properties
alert(m.isWorking);

How to define a non-extensible javascript object

I'd like to, if possible, define a javascript object that has a few properties along with getters/setters for those properties, but I don't want others to be able to add new properties to objects without extending the object definition (similar to how one would define a class in Java/C#). Is this possible to do with javascript?
You can use the "preventExtensions" method.
var obj = { foo: 'a' };
Object.preventExtensions(obj);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
In the following way, you can freeze the instances of the objects, but leave open to inheriting classes to add their own properties:
function Animal(name, action) {
this.name = name;
this.action = action;
if (this.constructor === Animal) {
Object.freeze(this);
}
}
var dog = new Animal('rover', 'bark')
dog.run = function(){console.log('I\'m running!')} // throws type error
function Dog(name, action, bark) {
Animal.call(this, name, action)
this.bark = bark // Animal not frozen since constructor is different
Object.freeze(this)
}
var puppy = new Dog('sparky', 'run', 'woof')
puppy.isTrained = false; // throws type error
See here: http://www.2ality.com/2013/06/freezing-instances.html
You can use Object.seal(obj):
const obj = Object.seal({
a: 1,
b: "hello"
})
obj.c = "world" // silently fails

Understanding JavaScript Inheritance

Block = function (){
this.type = 'block';
if(arguments[0]) this.type = arguments[0];
this.location = {x: 0, y: 0};
function update(){
}
}
Empty = function(location){
this.prototype = new Block;
this.type = 'empty';
this.location = location;
}
I want to be able to call
var x = new Empty();
x.update();
But I get the error that x.update is not a function.
The prototype property is only useful on a function. In the Empty constructor you're setting it on each instance of Empty, so it basically does nothing.
A much better way to do inheritance with JavaScript than what you're trying to do is to set the prototype of the inheriting class to inherit from the prototype of the base class:
Empty.prototype = Object.create(Block.prototype);
... and in order to inherit the properties set in the base class constructor, you simply call it with the correct arguments in the inheriting class constructor:
Empty = function(location) {
Block.call(this, location); // call base class
// other code, specific to your inheriting class
}
Note that the Empty.prototype line should come after you define Empty:
Empty = function(location) {
Block.call(this, location); // call base class
// other code, specific to your inheriting class
}
Empty.prototype = Object.create(Block.prototype);
Finally, to make methods available to instances you can define them in the constructor for each instance:
Block = function() {
this.update = function() { };
}
... or on the prototype of the constructor (after you define the constructor, obviously):
Block.prototype.update = function() {};
I don't know your particular scenario, but it seems to me that your inheritance is slightly weird. Usually the base is the more generic type (with variable location) and the inheriting class specializes it. The base class would be Doctor (person who treats diseases) and the inheriting class would be Dentist (person who treats certain kinds of diseases).
You don't set the prototype of this inside the constructor (where it would point to the new object, which doesn't have a prototype property with any meaning), you set it on the constructor directly (Block in this case).
Also, update is hidden in your case. You need to assign it to this (not so great practice) or make it part of Block's prototype, which you should, otherwise there is no real point using delegation or inheritance here.
Your code should look more or less like...
var Block = function () {
this.type = 'block';
if (arguments[0]) this.type = arguments[0];
this.location = {
x: 0,
y: 0
};
};
Block.prototype.update = function () {
console.log("updating");
};
var Empty = function (location) {
this.type = 'empty';
this.location = location;
};
Empty.prototype = new Block;
var x = new Empty();
x.update();
jsFiddle.

How do I set up a javascript Class with inheritance + private properties + getters / setters

I wish to use https://github.com/mozilla/BrowserQuest/blob/master/server/js/lib/class.js with private inheritable properties and also some getters and setters in there.
Basically I want the getter / setter to modify a private property and subclasses to inherit the setter, getter and the private property of course.
This is what I got so far:
var Person = Class.extend({
init: function( name )
{
var name = "~";
this.myName = name;
return {
set myName( value )
{
name = value + " +?";
},
get myName()
{
return name + "^^^^^^^^^";
}
}
}
});
var c = new Person( "cheese" );
console.log(c.myName);
c.myName = "zoom";
console.log(c.myName);
Trace:
undefined
zoom
Its weird, my editor (Webstorm) sees c.myName as the setter/getter but the compilers consider it an undefined public property :(
Any help would be appreciated. This is Nodejs but I think the issues is javascript.
I'm assuming Node.js or any other environment where the whole EcmaScript 5 is available.
The only way to have true private data in JavaScript these days is to keep a variable in the constructor, which is what you're doing. However, you're confusing the use of return in the init function. While the init function is pretty much the constructor, it is not being called exactly as such, so return does nothing. Even if it did something, what you want is to add a property to this, not return a new object. So basically you'd have to change that return to Object.defineProperty:
init: function (name) {
// notice that the private variable has a different name
// than the parameter
var privateName = '~';
Object.defineProperty(this, 'myName', {
set: function (value) {
privateName = value + " +?";
},
get: function () {
return privateName + "^^^^^^^^^";
}
});
this.myName = name;
}
This still has a limitation in inheritance. If you wanted to inherit from the Person class and modify the getters and setters you'd have to get the property descriptor, modify it and redefine the property. That's painful. And it doesn't support accessing the "super getter/setter". In order to avoid that what we usually do in JavaScript is to forget about having true privates and use privates by convention, starting the private property's name with _. Then you can simply define your getters and setters in the prototype like this:
var Person = Class.extend({
init: function(name) {
// the private name property
this._name = '~';
this.myName = name;
},
set myName(value) {
this._name = value + " +?";
},
get myName() {
return this._name + "^^^^^^^^^";
}
});
This will let you access the super getter/setter when using inheritance.
There is a cutting edge feature that would let you have both true privates and inherit getters and setters: WeakMap. WeakMap is basically an object with creates a relationship between two other objects that doesn't count for garbage collection. That last part is important for memory management and that first part is the one that lets you have true privates. You can try WeakMaps in Beta versions of Firefox and in Node.js with a --harmony_collections flag. Here's how it would work:
function privatize() {
var map = new WeakMap();
return function (obj) {
var data = map.get(obj);
if (!data) {
map.set(obj, data = {});
}
return data;
};
}
var Person = (function () {
var _private = privatize();
return Class.extend({
init: function(name) {
// the private name property
_private(this).name = '~';
this.myName = name;
},
set myName(value) {
_private(this).name = value + " +?";
},
get myName() {
return _private(this).name + "^^^^^^^^^";
}
});
}());

is it possible to assign a prototype on an existing object in javascript?

if i have:
function Base (){
this.sayHi = function(){
alert('hi');
}
}
function Thing (val){
this.value = val;
}
var bob = new Thing("bob");
Is there some way I can now say that bob inherits from Base so that I could call:
bob.sayHi();
Basically have all the methods and properties of the Base class available on that instance?
A more relevant example with solution based on CMS's post:
Here Items could represent data returned by the server..
var Items = [
{ sku: 123, type:'buggy', title:'This is a title', description: 'this is a description' },
{ sku: 234, type: 'baby-monitor', title: 'This is a title 2', description: 'this is a description 2' }
]
function ItemMethods() {
this.BannerHTML = function () {
return '<div class="banner _item_' + this.type + '_' + this.sku + '"><h2>' +
this.title + '</h2><p>' +
this.description + '</p></div>';
};
}
Items.GetBySKU = function (code) {
for (var i = 0; i < Items.length; i++) {
if (Items[i].sku == code) {
return Items[i];
}
}
};
$.each(Items, function (i, item) {
ItemMethods.apply(item);
});
alert(Items.GetBySKU(234).BannerHTML());
Any further comments or solutions gladly accepted.. always interested in potential solutions to a problem ;-)
No. There is no way to assign a different [[prototype]] of an existing object, at least according the the specification. The [[prototype]] is the object resulting from the evaluation (aka "contained in") of the constructors prototype property at time of new object creation and cannot be reassigned later. (I wish it could be officially changed, but alas it is an unsupported operation and can generally be emulated via different methods.)
Some browsers/environments may choose to expose post-creation [[prototype]] assignment with non-standard approaches. The [[prototype]] object(s) can be modified (e.g. adding to String.prototype), or singleton functions can be added to the target object (see CMS's answer), or existing objects can be wrapped (essentially a dynamic "subclass") -- depending upon requirements and restrictions.
Also, there are no "classes" in Javascript: while "classical single-inheritance objected oriented classes" can be implemented in Javascript, I find it limiting to restrict oneself to the model or use such terminology in general. A language wants to be what it is.
Happy coding.
In javascript you can create a subclass of an other by setting the subclass' prototype to an instance of the base class:
function BaseClass() {
this.baseFunction = function() {
};
};
function SubClass() {
};
SubClass.prototype = new BaseClass;
SubClass.prototype.someFunction = function() {
};
// create an instance of SubClass
var obj = new SubClass;
// SubClass truly extends BaseClass
obj instanceof BaseClass // true
obj instanceof SubClass // true
// the instance has both methods of BaseClass and SubClass
typeof obj.someFunction // Function
typeof obj.baseFunction // Function
This is the equivalent of class SubClass extends BaseClass in java.
If you also modify the prototypes after that.
If you add functions and properties to the prototype of the object's constructor, the functions and properties will be available on all the instances.
Here is an example:
function Thing (val){
this.value = val;
}
var bob = new Thing("bob");
console.log(bob.foo); // undefined
Thing.prototype.foo = function() {
console.log('foo!');
};
console.log(bob.foo); // function()
bob.foo(); // foo!
Now if you want to extend all Thing instances with Base, you can do it like this:
var base = new Base;
for (var k in base) {
Thing.prototype[k] = base[k];
}
Or if you want to make Thing instances extend Base: (i.e. don't override methods that are already in Thing)
var base = new Base;
for (var k in base) {
if (Thing.prototype[k]) continue;
Thing.prototype[k] = base[k];
}
If you only want to extend a unique object instance, just assign to it:
var bob = new Thing("bob");
var base = new Base();
bob.sayHi = base.sayHi;
bob.sayHi();
You can also call a function in the context of an object, without even assign the function to the object:
var base = new Base();
base.sayHi.call(bob);
Note that the properties that you create within the constructor, have nothing to do with the constructor's prototype, they are own properties of the object you create using new Base();, they are not inherited. However, I think what you want to do it to apply the Base constructor function on the newly created object of Thing:
function Base (){
this.sayHi = function(){
alert('hi');
}
}
function Thing (val){
Base.apply(this, arguments);
this.value = val;
}
var bob = new Thing("bob");
bob.sayHi();
Note that bob will not inherit from Base (it won't have access to properties added to Base.prototype)
bob instanceof Base; // false
bob instanceof Thing; // true
Yes.
1) You can add Base's constructor definition directly to the bob instance
Base.call(bob);
bob.sayHi(); //hi
2) Or you can augment Thing.prototype with Base's constructor definition. The bob instance can access its prototype's new properties even if it was created before they were added
var bob = new Thing("bob");
Base.call(Thing.prototype);
bob.sayHi(); //hi

Categories

Resources