A javascript function that has a defined function - javascript

I am trying you get a better understanding of JavaScript, especially the prototype functionality. I am having trouble with this case:
I am trying to define a function someObject with a type function so that it will behave like the following:
var myTestObject = someObject();
If I call:
myTestObject() ===> "The object is initailType"
and then when this is called
myTestObject.type() ===> "InitialType"
Then if I make this call
myTestObject.type("newtype")
myTestObject.type() ===> "newType"
A call to
myTestObject() ===> "The Object is newType".
I have tried both this How does JavaScript .prototype work?
and this How do you create a method for a custom object in JavaScript?
,but I am getting several different errors depending on how it is implemented, mostly this though (Uncaught TypeError: Object myTestObject has no method 'type'). I feel like I am making this harder then it should be.
edit: more code.
function box(){
var _current = "initialType"
Object.defineProperty(this, "current", {
get: function(){return _current;},
set: function(value){
if(arguments.length === 1){
_current = value;
} }
})
return "The Object is " + this.type(this.current)
}
box.prototype.type = function(newValue){
var type = null;
if(arguments.length == 0){
type = "initialType";
}else {
type = newValue
}
return type
}

I would use something like this:
function Box(){}
Box.prototype.type = "initialType";
Box.prototype.toString = function() {
return "The Object is " + this.type + ".";
};
And use it like this:
var b = new Box();
b.type; // "initialType"
b + ''; // "The Object is initialType."
b.type = 'otherType'; // "otherType"
b.type; // "otherType"
b + ''; // "The Object is otherType."

This does what you've asked, but I don't understand what you want to do with the prototype, so this code doesn't use that. For example, the sample code doesn't use new, so the return value of someObject won't use its prototype.
function someObject()
{
var currentType = "initailType";
var formatter = function() {
return "The object is " + currentType;
};
formatter.type = function(value) {
if (arguments.length == 0) {
return currentType;
} else {
currentType = value;
}
};
return formatter;
}
var myTestObject = someObject();
myTestObject(); // => "The object is initailType"
myTestObject.type(); // => "initialType"
myTestObject.type("newType");
myTestObject.type(); // => "newType"
myTestObject(); // => "The object is newType".
see demo
Edit: example using prototype and new.
function Box() { // class name starts with a capital letter
this._type = "initialType"; // set up default values in constructor function
} // no "return" in constructor function, using "new" handles that
Box.prototype.type = function(value) { // adding method to the prototype
if (arguments.length == 0) { // magic arguments local variable...
return this._type; // initially returns the value set in the constructor
} else {
this._type = value; // update the stored value
}
};
Box.prototype.format = function() // another method on the box, rather than a return from the constructor
{
return "The object is " + this.type(); // could use this._type instead
};
var box = new Box(); // instance variable with lowercase name
console.log(box.type()); // read the default value
console.log(box.format()); // print the message with the initial value of type
box.type("another type"); // set the type property, no return value
console.log(box.format()); // print the new message

Related

execute function when setting an object

I want to execute a function when the value of props.test is changed. I have seen in another question Object.defineProperty can be used. But in my case it will props.test, how to make it work
props.test = props.test || {};
//if props.test value changes it need to execute set function
props.test = "abc";
function setTest(){
console.log("set function is executed");
props.test = "abc";
}
This could be achieved by defining getter and setters on an object such as,
var props = {
get value() {
return this._value;
},
set value(val) {
this._value = val;
alert("value is: " + val);
}
}
For the above example, when you set the value on the props object, an alert function is executed,
props.value; // returns undefined
props.value = "hello"; // alert box will appear, "value is: hello"
props.value; // returns "hello".
This can be replaced with the function you wish to execute when the value changes.
When adding properties onto an existing object you can use Object.defineProperty like so,
var me = {name:'Anthony', location:'RI'};
// let's extend onto the object a new property called, 'age'
// with custom getter and setter
Object.defineProperty(me, 'age', {
get: function() {
return this._age;
},
set: function(newAge) {
this._age = newAge;
alert("I am " + newAge + " now!");
}
});
me.age; // undefined
me.age = 28; // alert box will appear, "I am 28 now!"
me.age; // returns 28.
To take it one-step further if the pre-existing object contains an object, you can perform the following,
Note: Object.defineProperty takes as arguments an, object, key, and descriptor (ie, our custom setters and getters) respectively. That object argument can be any object type, even the nested object of the parent object.
// my pet rules my life, so let's include her in my example object,
var me = {name:'Anthony', location:'RI', pet:{name:'Binks'}};
// let's give her an identifier because that's what good owners do,
// Notice the first argument of, 'me.pet' which is the inner object.
Object.defineProperty(me.pet, 'id', {
get: function() {
return this._id;
},
set: function(newId) {
this._id = newId;
alert("id changed! " + newId);
}
});
me.pet['id']; // undefined
me.pet['id'] = 1; // alert box will appear, "id changed! 1"
me.pet['id']; // returns 1
Update: 07/12/2015
If you are trying to add dynamic values onto an Object, you can extend the above functionality into a generalized function,
var defineProp = function(obj, value) {
Object.defineProperty(obj, value, {
get: function() {
return this['_' + value];
},
set: function(newValue) {
this['_' + value] = newValue;
alert(value + " changed! " + newValue);
}
});
}
The most logical way of doing this is to create a function that is responsible for setting the property. To the best of my knowledge, there is no hook available to monitor when the property of an object has been changed. Alternatively, you object could use a timer to periodically check to see if the property has changed.

JS: How to do function.function(param).function?

Thanks for reading.
So I am working on a my first node.js app. I'm relatively familiar with javascript but not well enough.
I have declared a class FOO with a method called bars(index, value} that accepts 2 params. In order do use this, after creating an instance, I have the following fooInstance.bars(3, 2)
I would like to call this method a bit differently. How can I change my FOO definition so that I can use it like this fooInstance.bars(3).value?
My current code is below
var util = require('util'),
events = require('events');
var FOO = function(opts) {
this.ipAddress = opts.ipAddress;
this.port = opts.port;
};
FOO.prototype = new events.EventEmitter;
module.exports = FOO;
FOO.prototype.bars = function (index, value) {
switch(index) {
case 1:
console.log("Apple " + " at " + value)
break;
case 2:
console.log("Banana, " + " at " + value)
break;
case 3:
console.log("Cherry, " + " at " + value)
break;
case 4:
console.log("Date, " + " at " + value)
break;
default:
break;
}
}
thanks in advance!
It is called Method Chaining or sometimes Fluent interface. The main idea behind the 'chaining' is to return an object (often times self) as a result, enabling direct invocation on the returned value.
I copied a sample code from here (attribute goes to the original author) that returns self as a return value.
var obj = {
function1: function () {
alert("function1");
return obj;
},
function2: function () {
alert("function2");
return obj;
},
function3: function () {
alert("function3");
return obj;
}
}
obj.function1().function2().function3();
For your FOO implementation, try returning this at the end of bars function.
FOO.prototype.bars = function(index,value){
// your previous code here;
this.value = value;
return this;
}
You are not asking for method chaining. More like
> console.log(fooInstance.bars(3).value)
> Cherry
then do the following:
var util = require('util'),
events = require('events');
var FOO = function(opts) {
this.ipAddress = opts.ipAddress;
this.port = opts.port;
};
FOO.prototype = new events.EventEmitter;
module.exports = FOO;
FOO.prototype.bars = function (index) {
var undef;
switch(index) {
case 1:
return { value : 'Apple' };
case 2:
return { value : 'Bannana' };
case 3:
return { value : 'Cherry' };
case 4:
return { value : 'Date' };
default:
return { value : undef };
}
}
I'm not exactly sure if you wanted a string back as a value but just guessing. This will return an object as an answer which then can be used like ".value".
What I do to case statements that is simpler is this:
var easierThanCase = {
'1' : 'Apple',
'2' : 'Bannana',
'3' : 'Cherry',
'4' : 'Date'
};
return { value : easierThanCase[index+''] };
You have two possibilities:
You can simply pass the arguments you need. Javascript will set arguments not used to undefined. Then, in the function, you can check by if (!value) or if (typedef value === "undefined") to find the state. (Javascript is in general very flexible here. You can also get arguments you pass but you didn't declare in the function definition).
You can create a new function bars() even though the name has already been used. But doing so will destroy the old function.
In order to check 1, try this:
var test = new FOO();
console.log(test.bars(3));
It'll anwer: Cherry, at undefined
In order to check 2, add after the definition of bars:
FOO.prototype.bars = function(index) {
console.log("In new bars!");
}
Here are more infos, also about the keyword arguments:
How to get function parameter names/values dynamically from javascript
Here is a better way to implement your bars method:
FOO.prototype.bars = function (index, value) {
var fruitArray = ["Apple", "Banana", "Cherry", "Data"];
console.log(fruitArray[index - 1] + " at " + value);
}
If you are wanting to do this: fooInstance.bars(3).value. You are calling bars with one parameter (index === 3) and then calling the property of value on this result. This logic does not make much sense in this example. Hope this helps.

Variable is instance of Enum

I have the following javascript code:
function testClass() {
this.SaveValue = function (value) {
var isInstance = value instanceof TestEnum;
if (!isInstance) {
return;
}
}
}
TestEnum = {
VALUE_0: 0,
VALUE_1: 1,
VALUE_2: 2
}
I create an instance of this object in the following way:
$(function () {
var a = new testClass();
a.SaveValue(TestEnum.VALUE_1);
});
All I'd like to do is test that the value passed to the SaveValue function is actually the type of TestEnum. However, when I run this code I get the following error: Uncaught TypeError: Expecting a function in instanceof check, but got 1
Am I going about this the right way? I tried typeof but it only returns number which is not particularly useful to me.
You could create the values as instances of the "class":
function TestEnum(value) {
this._value = value;
}
TestEnum.prototype.valueOf = function() {
return this._value;
}
TestEnum.prototype.toString = function() {
return 'TestEnum_' + this._value;
}
TestEnum.VALUE_0 = new TestEnum(0);
TestEnum.VALUE_1 = new TestEnum(1);
The following would work then:
TestEnum.VALUE_0 instanceof TestEnum
But it also means you'd have to explicitly access the numerical value of one value with .valueOf. In some cases JS will do this automatically for you (like in 5 + TestEnum.VALUE_1). Overriding toString so that you can use a value as property might also be necessary.
It really depends on your use case whether this is a viable solution.
Alternatively, if just want to test whether a value is part of the enum, you can have an additional property which holds all possible values:
TestEnum.values = {0: true, 1: true, ...};
And then test it with
value in TestEnum.values
// or more reliable (fails for inherited `Object` properties)
TestEnum.values.hasOwnProperty(value);
You could even automate this:
function collectValues(obj) {
var values = {}; // or Object.create(null) if available
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
values[obj[prop]] = true;
}
}
return values;
}
TestEnum.values = collectValues(TestEnum);
This will only reliably work for primitive values though and won't distinguish between the string "1" and the number 1.
You are passing a number to the function in
a.SaveValue(TestEnum.VALUE_1);
Since TestEnum is simply an Object, and you are referencing a number property on that object, you're calling your function with a number. You should instead create a TestEnumValue object and use that for your Object's properties:
JSFiddle link for below
function testClass() {
this.SaveValue = function (value) {
var isInstance = value instanceof TestEnumValue;
if (!isInstance) {
return;
}
}
}
TestEnumValue = function(arg) {
arg = arg ? arg : 0; // sensible default
this.key = 'VALUE_' + arg;
this.val = arg;
}
Level = {
NumSpiders : new TestEnumValue(0),
NumCreepers: new TestEnumValue(1),
NumZombies : new TestEnumValue(2),
NumChickens: new TestEnumValue // uses default enum value
};
$(function() {
var a = new testClass();
a.SaveValue(Level.NumSpiders);
$('#hi').text(Level.NumSpiders.key);
});
Playing around with this, I noticed that you can leverage the fact that an enum compiles into an object that binds the values both ways combined with a hasOwnProperty check.
export enum TEST_ENUM{
ZERO, // 0
ONE, // 1
TWO, // 2
}
let a = 1;
let b = TEST_ENUM.TWO // 2
let c = 5 // incorrect value
TEST_ENUM.hasOwnProperty(a); // TRUE
TEST_ENUM.hasOwnProperty(b); // TRUE
TEST_ENUM.hasOwnProperty(c); // FALSE
This comes with a few caveats though;
// An object's keys are always strings...
// Although this shouldn't not matter usually (e.g. parsed user input)
TEST_ENUM.hasOwnProperty("2"); // TRUE
// And the enum is bound two-way so:
let input = "TWO";
if (TEST_ENUM.hasOwnProperty(input) { // TRUE
let result = input // "TWO"
// result is now the enum's value, instead of the key.
result = TEST_ENUM[input]; // this would be the correct assignment
};
Of course you can fix both of these with a typeof check, in case of a string assign it TEST_ENUM[mystring].
Note that my intellisense didn't autocomplete the hasOwnProperty function on an enum, but it doesn't complain about it either, and it's available on all browsers.
Edit
Here's an example of how you could do it.
function TestEnum(val) {
this.vals = this.vals || [];
if (this.vals.indexOf(val) == -1) console.log('nope: ' + val);
else console.log('ok: ' + val);
}
(function() {
var vals = {
VALUE_0: 0,
VALUE_1: 1,
VALUE_2: 2
};
TestEnum.prototype.vals = [];
for (var key in vals) {
TestEnum[key] = vals[key];
TestEnum.prototype.vals.push(vals[key]);
}
})();
Now new TestEnum(TestEnum.VALUE_0); is OK, but if you try, say, new TestEnum(3), then it throws an exception.
This is a bit backwards -- x instanceof y means that x has been created as x = new y(). Since TestEnum isn't even a function, you can't create an instance of it, so this isn't going to work.
What you could do is maybe something like this:
function MyEnum(enumVal) { this.val = enumVal; }
a.SaveValue( new MyEnum(TestEnum.VALUE_1) );
Then check using isInstance = value instanceof MyEnum.

Turning JSON strings into objects with methods

I have an app that allows users to generate objects, and store them (in a MySQL table, as strings) for later use. The object could be :
function Obj() {
this.label = "new object";
}
Obj.prototype.setLabel = function(newLabel) {
this.label = newLabel;
}
If I use JSON.stringify on this object, I will only get the information on Obj.label (the stringified object would be a string like {label: "new object"}. If I store this string, and want to allow my user to retrieve the object later, the setLabel method will be lost.
So my question is: how can I re-instantiate the object, so that it keeps the properties stored thanks to JSON.stringify, but also gets back the different methods that should belong to its prototype. How would you do that ? I was thinking of something along "create a blank object" and "merge it with the stored one's properties", but I can't get it to work.
To do this, you'll want to use a "reviver" function when parsing the JSON string (and a "replacer" function or a toJSON function on your constructor's prototype when creating it). See Section 15.12.2 and 15.12.3 of the specification. If your environment doesn't yet support native JSON parsing, you can use one of Crockford's parsers (Crockford being the inventor of JSON), which also support "reviver" functions.
Here's a simple bespoke example that works with ES5-compliant browsers (or libraries that emulate ES5 behavior) (live copy, run in Chrome or Firefox or similar), but look after the example for a more generalized solution.
// Our constructor function
function Foo(val) {
this.value = val;
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.toJSON = function() {
return "/Foo(" + this.value + ")/";
};
// An object with a property, `foo`, referencing an instance
// created by that constructor function, and another `bar`
// which is just a string
var obj = {
foo: new Foo(42),
bar: "I'm bar"
};
// Use it
display("obj.foo.value = " + obj.foo.value);
display("obj.foo.nifty = " + obj.foo.nifty);
display("obj.bar = " + obj.bar);
// Stringify it with a replacer:
var str = JSON.stringify(obj);
// Show that
display("The string: " + str);
// Re-create it with use of a "reviver" function
var obj2 = JSON.parse(str, function(key, value) {
if (typeof value === "string" &&
value.substring(0, 5) === "/Foo(" &&
value.substr(-2) == ")/"
) {
return new Foo(value.substring(5, value.length - 2));
}
return value;
});
// Use the result
display("obj2.foo.value = " + obj2.foo.value);
display("obj2.foo.nifty = " + obj2.foo.nifty);
display("obj2.bar = " + obj2.bar);
Note the toJSON on Foo.prototype, and the function we pass into JSON.parse.
The problem there, though, is that the reviver is tightly coupled to the Foo constructor. Instead, you can adopt a generic framework in your code, where any constructor function can support a fromJSON (or similar) function, and you can use just one generalized reviver.
Here's an example of a generalized reviver that looks for a ctor property and a data property, and calls ctor.fromJSON if found, passing in the full value it received (live example):
// A generic "smart reviver" function.
// Looks for object values with a `ctor` property and
// a `data` property. If it finds them, and finds a matching
// constructor that has a `fromJSON` property on it, it hands
// off to that `fromJSON` fuunction, passing in the value.
function Reviver(key, value) {
var ctor;
if (typeof value === "object" &&
typeof value.ctor === "string" &&
typeof value.data !== "undefined") {
ctor = Reviver.constructors[value.ctor] || window[value.ctor];
if (typeof ctor === "function" &&
typeof ctor.fromJSON === "function") {
return ctor.fromJSON(value);
}
}
return value;
}
Reviver.constructors = {}; // A list of constructors the smart reviver should know about
To avoid having to repeat common logic in toJSON and fromJSON functions, you could have generic versions:
// A generic "toJSON" function that creates the data expected
// by Reviver.
// `ctorName` The name of the constructor to use to revive it
// `obj` The object being serialized
// `keys` (Optional) Array of the properties to serialize,
// if not given then all of the objects "own" properties
// that don't have function values will be serialized.
// (Note: If you list a property in `keys`, it will be serialized
// regardless of whether it's an "own" property.)
// Returns: The structure (which will then be turned into a string
// as part of the JSON.stringify algorithm)
function Generic_toJSON(ctorName, obj, keys) {
var data, index, key;
if (!keys) {
keys = Object.keys(obj); // Only "own" properties are included
}
data = {};
for (index = 0; index < keys.length; ++index) {
key = keys[index];
data[key] = obj[key];
}
return {ctor: ctorName, data: data};
}
// A generic "fromJSON" function for use with Reviver: Just calls the
// constructor function with no arguments, then applies all of the
// key/value pairs from the raw data to the instance. Only useful for
// constructors that can be reasonably called without arguments!
// `ctor` The constructor to call
// `data` The data to apply
// Returns: The object
function Generic_fromJSON(ctor, data) {
var obj, name;
obj = new ctor();
for (name in data) {
obj[name] = data[name];
}
return obj;
}
The advantage here being that you defer to the implementation of a specific "type" (for lack of a better term) for how it serializes and deserializes. So you might have a "type" that just uses the generics:
// `Foo` is a constructor function that integrates with Reviver
// but doesn't need anything but the generic handling.
function Foo() {
}
Foo.prototype.nifty = "I'm the nifty inherited property.";
Foo.prototype.spiffy = "I'm the spiffy inherited property.";
Foo.prototype.toJSON = function() {
return Generic_toJSON("Foo", this);
};
Foo.fromJSON = function(value) {
return Generic_fromJSON(Foo, value.data);
};
Reviver.constructors.Foo = Foo;
...or one that, for whatever reason, has to do something more custom:
// `Bar` is a constructor function that integrates with Reviver
// but has its own custom JSON handling for whatever reason.
function Bar(value, count) {
this.value = value;
this.count = count;
}
Bar.prototype.nifty = "I'm the nifty inherited property.";
Bar.prototype.spiffy = "I'm the spiffy inherited property.";
Bar.prototype.toJSON = function() {
// Bar's custom handling *only* serializes the `value` property
// and the `spiffy` or `nifty` props if necessary.
var rv = {
ctor: "Bar",
data: {
value: this.value,
count: this.count
}
};
if (this.hasOwnProperty("nifty")) {
rv.data.nifty = this.nifty;
}
if (this.hasOwnProperty("spiffy")) {
rv.data.spiffy = this.spiffy;
}
return rv;
};
Bar.fromJSON = function(value) {
// Again custom handling, for whatever reason Bar doesn't
// want to serialize/deserialize properties it doesn't know
// about.
var d = value.data;
b = new Bar(d.value, d.count);
if (d.spiffy) {
b.spiffy = d.spiffy;
}
if (d.nifty) {
b.nifty = d.nifty;
}
return b;
};
Reviver.constructors.Bar = Bar;
And here's how we might test that Foo and Bar work as expected (live copy):
// An object with `foo` and `bar` properties:
var before = {
foo: new Foo(),
bar: new Bar("testing", 42)
};
before.foo.custom = "I'm a custom property";
before.foo.nifty = "Updated nifty";
before.bar.custom = "I'm a custom property"; // Won't get serialized!
before.bar.spiffy = "Updated spiffy";
// Use it
display("before.foo.nifty = " + before.foo.nifty);
display("before.foo.spiffy = " + before.foo.spiffy);
display("before.foo.custom = " + before.foo.custom + " (" + typeof before.foo.custom + ")");
display("before.bar.value = " + before.bar.value + " (" + typeof before.bar.value + ")");
display("before.bar.count = " + before.bar.count + " (" + typeof before.bar.count + ")");
display("before.bar.nifty = " + before.bar.nifty);
display("before.bar.spiffy = " + before.bar.spiffy);
display("before.bar.custom = " + before.bar.custom + " (" + typeof before.bar.custom + ")");
// Stringify it with a replacer:
var str = JSON.stringify(before);
// Show that
display("The string: " + str);
// Re-create it with use of a "reviver" function
var after = JSON.parse(str, Reviver);
// Use the result
display("after.foo.nifty = " + after.foo.nifty);
display("after.foo.spiffy = " + after.foo.spiffy);
display("after.foo.custom = " + after.foo.custom + " (" + typeof after.foo.custom + ")");
display("after.bar.value = " + after.bar.value + " (" + typeof after.bar.value + ")");
display("after.bar.count = " + after.bar.count + " (" + typeof after.bar.count + ")");
display("after.bar.nifty = " + after.bar.nifty);
display("after.bar.spiffy = " + after.bar.spiffy);
display("after.bar.custom = " + after.bar.custom + " (" + typeof after.bar.custom + ")");
display("(Note that after.bar.custom is undefined because <code>Bar</code> specifically leaves it out.)");
You can indeed create an empty instance and then merge the instance with the data. I recommend using a library function for ease of use (like jQuery.extend).
You had some errors though (function ... = function(...), and JSON requires keys to be surrounded by ").
http://jsfiddle.net/sc8NU/1/
var data = '{"label": "new object"}'; // JSON
var inst = new Obj; // empty instance
jQuery.extend(inst, JSON.parse(data)); // merge
Note that merging like this sets properties directly, so if setLabel is doing some checking stuff, this won't be done this way.
So far as I know, this means moving away from JSON; you're now customizing it, and so you take on all of the potential headaches that entails. The idea of JSON is to include data only, not code, to avoid all of the security problems that you get when you allow code to be included. Allowing code means that you have to use eval to run that code and eval is evil.
If you want to use the setters of Obj :
Obj.createFromJSON = function(json){
if(typeof json === "string") // if json is a string
json = JSON.parse(json); // we convert it to an object
var obj = new Obj(), setter; // we declare the object we will return
for(var key in json){ // for all properties
setter = "set"+key[0].toUpperCase()+key.substr(1); // we get the name of the setter for that property (e.g. : key=property => setter=setProperty
// following the OP's comment, we check if the setter exists :
if(setter in obj){
obj[setter](json[key]); // we call the setter
}
else{ // if not, we set it directly
obj[key] = json[key];
}
}
return obj; // we finally return the instance
};
This requires your class to have setters for all its properties.
This method is static, so you can use like this :
var instance = Obj.createFromJSON({"label":"MyLabel"});
var instance2 = Obj.createFromJSON('{"label":"MyLabel"}');
From ECMAScript 6 onwards you can just do:
Object.assign(new Obj(), JSON.parse(rawJsonString))
Note: You create a new empty object of the defined type first and then override its properties with the parsed JSON. Not the other way around.
The methods define behaviour and contain no variable data. They are "stored" as your code. So you don't actually have to store them in the database.
You would have to write your own stringify method that stores functions as properties by converting them to strings using the toString method.
JavaScript is prototype based programming language which is classless language where object orientation achieved by process of cloning existing objects that serve as prototypes.
Serializing JSON would be considering any methods, for instance if you have an object
var x = {
a: 4
getText: function() {
return x.a;
}
};
You will get just { a:4 } where getText method is skipped by the serializer.
I ran into this same trouble a year back and I had to maintain a separate helper class for each of my domain object and used $.extend() it to my deserialized object when need, just more like having methods to a base class for the domain objects.
Try to use toString on the method.
Update:
Iterate over the methods in obj and store them as string, and then instantiate them with new Function.
storedFunc = Obj.prototype.setLabel.toString();
Obj2.prototype['setLabel'] = new Function("return (" + storedFunc + ")")();

How would you overload the [] operator in javascript

I can't seem to find the way to overload the [] operator in javascript. Anyone out there know?
I was thinking on the lines of ...
MyClass.operator.lookup(index)
{
return myArray[index];
}
or am I not looking at the right things.
You can do this with ES6 Proxy (available in all modern browsers)
var handler = {
get: function(target, name) {
return "Hello, " + name;
}
};
var proxy = new Proxy({}, handler);
console.log(proxy.world); // output: Hello, world
console.log(proxy[123]); // output: Hello, 123
Check details on MDN.
You can't overload operators in JavaScript.
It was proposed for ECMAScript 4 but rejected.
I don't think you'll see it anytime soon.
The simple answer is that JavaScript allows access to children of an Object via the square brackets.
So you could define your class:
MyClass = function(){
// Set some defaults that belong to the class via dot syntax or array syntax.
this.some_property = 'my value is a string';
this['another_property'] = 'i am also a string';
this[0] = 1;
};
You will then be able to access the members on any instances of your class with either syntax.
foo = new MyClass();
foo.some_property; // Returns 'my value is a string'
foo['some_property']; // Returns 'my value is a string'
foo.another_property; // Returns 'i am also a string'
foo['another_property']; // Also returns 'i am also a string'
foo.0; // Syntax Error
foo[0]; // Returns 1
foo['0']; // Returns 1
Use a proxy. It was mentioned elsewhere in the answers but I think that this is a better example:
var handler = {
get: function(target, name) {
if (name in target) {
return target[name];
}
if (name == 'length') {
return Infinity;
}
return name * name;
}
};
var p = new Proxy({}, handler);
p[4]; //returns 16, which is the square of 4.
We can proxy get | set methods directly. Inspired by this.
class Foo {
constructor(v) {
this.data = v
return new Proxy(this, {
get: (obj, key) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key]
else
return obj[key]
},
set: (obj, key, value) => {
if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
return obj.data[key] = value
else
return obj[key] = value
}
})
}
}
var foo = new Foo([])
foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]
As brackets operator is actually property access operator, you can hook on it with getters and setters. For IE you will have to use Object.defineProperty() instead. Example:
var obj = {
get attr() { alert("Getter called!"); return 1; },
set attr(value) { alert("Setter called!"); return value; }
};
obj.attr = 123;
The same for IE8+:
Object.defineProperty("attr", {
get: function() { alert("Getter called!"); return 1; },
set: function(value) { alert("Setter called!"); return value; }
});
For IE5-7 there's onpropertychange event only, which works for DOM elements, but not for other objects.
The drawback of the method is you can only hook on requests to predefined set of properties, not on arbitrary property without any predefined name.
one sneaky way to do this is by extending the language itself.
step 1
define a custom indexing convention, let's call it, "[]".
var MyClass = function MyClass(n) {
this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
...
var foo = new MyClass(1024);
console.log(foo["[]"](0));
step 2
define a new eval implementation. (don't do this this way, but it's a proof of concept).
var MyClass = function MyClass(length, defaultValue) {
this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
value: function(index) {
return this.myArray[index];
}
});
var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));
var mini_eval = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = eval(values[0]);
var i = eval(values[2]);
// higher priority than []
if (target.hasOwnProperty('[]')) {
return target['[]'](i);
} else {
return target[i];
}
return eval(values[0])();
} else {
return undefined;
}
} else {
return undefined;
}
} else {
return undefined;
}
};
mini_eval("foo[33]");
the above won't work for more complex indexes but it can be with stronger parsing.
alternative:
instead of resorting to creating your own superset language, you can instead compile your notation to the existing language, then eval it. This reduces the parsing overhead to native after the first time you use it.
var compile = function(program) {
var esprima = require("esprima");
var tokens = esprima.tokenize(program);
if (tokens.length == 4) {
var types = tokens.map(a => a.type);
var values = tokens.map(a => a.value);
if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
if (values[1] == '[' && values[3] == ']') {
var target = values[0];
var i = values[2];
// higher priority than []
return `
(${target}['[]'])
? ${target}['[]'](${i})
: ${target}[${i}]`
} else {
return 'undefined';
}
} else {
return 'undefined';
}
} else {
return 'undefined';
}
};
var result = compile("foo[0]");
console.log(result);
console.log(eval(result));
You need to use Proxy as explained, but it can ultimately be integrated into a class constructor
return new Proxy(this, {
set: function( target, name, value ) {
...}};
with 'this'. Then the set and get (also deleteProperty) functions will fire. Although you get a Proxy object which seems different it for the most part works to ask the compare ( target.constructor === MyClass ) it's class type etc. [even though it's a function where target.constructor.name is the class name in text (just noting an example of things that work slightly different.)]
So you're hoping to do something like
var whatever = MyClassInstance[4];
?
If so, simple answer is that Javascript does not currently support operator overloading.
Have a look at Symbol.iterator. You can implement a user-defined ##iterator method to make any object iterable.
The well-known Symbol.iterator symbol specifies the default iterator for an object. Used by for...of.
Example:
class MyClass {
constructor () {
this._array = [data]
}
*[Symbol.iterator] () {
for (let i=0, n=this._array.length; i<n; i++) {
yield this._array[i]
}
}
}
const c = new MyClass()
for (const element of [...c]) {
// do something with element
}

Categories