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.
Related
I need to initialize some kind of a prototype on already existing jQuery elements collection. The main problem is that the prototype should be accessible only inside of that collection and on elements produced by built-in jQuery functions like .find() on that collection or on some children objects inside of that collection, for example:
var $a = $('a');
$a.__proto__.foo/*some magic over here*/ = function(){ alert('foo!'); };
$a.foo(); //should show alert('foo!')
$a.find('b').foo(); //should produce the same action
$('a').foo(); //should produce an error (method not found)
If using $a.__proto__ like in example above, the jQuery.prototype is accessed, so all the new elements in outside of that jQuery-collection (for example, $('a')) are granting an access to .foo() method. That behaviour is unacceptable on a problem statement.
Is that actually possible?
Okay, here's the thing, I have a rather complex ES6 solution, so I won't be able to explain it in great depth, but if you have some particular questions, go ahead.
var wrap = (function wrapper() {
var store = {};
function wrap(fn) {
return new Proxy(fn, {
apply(target, thisArg, argumentsList) {
var result = Reflect.apply(target, thisArg, argumentsList);
// `jQuery(...)` returns a "rich" object that always contain `.length`
if (result.length > 0) {
result = new Proxy(result, {
get(target, propertyKey, receiver) {
var value = Reflect.get(target, propertyKey, receiver);
if (Object.keys(store).includes(propertyKey)) {
value = store[propertyKey];
}
return value;
},
set(target, propertyKey, value, receiver) {
// TODO: use `Reflect.set(), somehow`
// return Reflect.set(store, propertyKey, value, receiver);
return (store[propertyKey] = value);
},
});
}
return result;
}
});
}
return wrap;
})();
var $ = wrap(jQuery);
$.prototype.find = wrap(jQuery.prototype.find); // TODO: implement recursively in `wrap()`
var x = $('div');
var xx = x.find('div');
var xxx = x.find('divvv');
xx.foo = 123;
console.log(x.foo); // 123
console.log(xx.foo); // 123
console.log(xxx.foo); // undefined
I want to write my Javascript class like below.
class Option {
constructor() {
this.autoLoad = false;
}
constructor(key, value) {
this[key] = value;
}
constructor(key, value, autoLoad) {
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
I think it would be nice if we can write out class in this way.
Expect to happen:
var option1 = new Option(); // option1 = {autoLoad: false}
var option2 = new Option('foo', 'bar',); // option2 = {foo: 'bar'}
var option3 = new Option('foo', 'bar', false); // option3 = {foo: 'bar', autoLoad: false}
I want to write my Javascript class like below
You can't, in the same way you can't overload standard functions like that. What you can do is use the arguments object to query the number of arguments passed:
class Option {
constructor(key, value, autoLoad) {
// new Option()
if(!arguments.length) {
this.autoLoad = false;
}
// new Option(a, [b, [c]])
else {
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
}
Babel REPL Example
Of course (with your updated example), you could take the approach that you don't care about the number of arguments, rather whether each individual value was passed, in which case you could so something like:
class Option {
constructor(key, value, autoLoad) {
if(!key) { // Could change this to a strict undefined check
this.autoLoad = false;
return;
}
this[key] = value;
this.autoLoad = autoLoad || false;
}
}
What you want is called constructor overloading. This, and the more general case of function overloading, is not supported in ECMAScript.
ECMAScript does not handle missing arguments in the same way as more strict languages. The value of missing arguments is left as undefined instead of raising a error. In this paradigm, it is difficult/impossible to detect which overloaded function you are aiming for.
The idiomatic solution is to have one function and have it handle all the combinations of arguments that you need. For the original example, you can just test for the presence of key and value like this:
class Option {
constructor(key, value, autoLoad = false) {
if (typeof key !== 'undefined') {
this[key] = value;
}
this.autoLoad = autoLoad;
}
}
Another option would be to allow your constructor to take an object that is bound to your class properties:
class Option {
// Assign default values in the constructor object
constructor({key = 'foo', value, autoLoad = true} = {}) {
this.key = key;
// Or on the property with default (not recommended)
this.value = value || 'bar';
this.autoLoad = autoLoad;
console.log('Result:', this);
}
}
var option1 = new Option();
// Logs: {key: "foo", value: "bar", autoLoad: true}
var option2 = new Option({value: 'hello'});
// Logs: {key: "foo", value: "hello", autoLoad: true}
This is even more useful with Typescript as you can ensure type safety with the values passed in (i.e. key could only be a string, autoLoad a boolean etc).
Guessing from your sample code, all you need is to use default values for your parameters:
class Option {
constructor(key = 'foo', value = 'bar', autoLoad = false) {
this[key] = value;
this.autoLoad = autoLoad;
}
}
Having said that, another alternative to constructor overloading is to use static factories. Suppose you would like to be able to instantiate an object from plain parameters, from a hash containing those same parameters or even from a JSON string:
class Thing {
constructor(a, b) {
this.a = a;
this.b = b;
}
static fromHash(hash) {
return new this(hash.a, hash.b);
}
static fromJson(string) {
return this.fromHash(JSON.parse(string));
}
}
let thing = new Thing(1, 2);
// ...
thing = Thing.fromHash({a: 1, b: 2});
// ...
thing = Thing.fromJson('{"a": 1, "b": 2}');
Here's a hack for overloading based on arity (number of arguments). The idea is to create a function from a number of functions with different arities (determined by looking at fn.length).
function overloaded(...inputs) {
var fns = [];
inputs.forEach(f => fns[f.length] = f);
return function() {
return fns[arguments.length].apply(this, arguments);
};
}
var F = overloaded(
function(a) { console.log("function with one argument"); },
function(a, b) { console.log("function with two arguments"); }
);
F(1);
F(2, 3);
Of course this needs a lot of bullet-proofing and cleaning up, but you get the idea. However, I don't think you'll have much luck applying this to ES6 class constructors, because they are a horse of a different color.
you can use static methods,look at my answer to same question
class MyClass {
constructor(a,b,c,d){
this.a = a
this.b = b
this.c = c
this.d = d
}
static BAndCInstance(b,c){
return new MyClass(null,b,c)
}
}
//a Instance that has b and c params
MyClass.BAndCInstance(b,c)
Use object.assigne with arguments with this
This={...this,...arguments}
Its not the overload I wanted, but this is a basic version of how I faked my way through creating an obj1 with some different initialization behavior. I realize I could have expanded the arguments as stated above, but I already had a nasty set of arguments and relatively different data sources to deconstruct that would have really distorted my objectives; this just made it cleaner for my situation...
class obj1{
constructor(v1, v2){
this.a = v1;
this.b = v2;
}
}
class obj1Alt{
constructor(v1, v2){
return new obj1(v1*2,v2*2);
}
}
new obj1(2,4) // returns an obj1
new obj1Alt(2,4) // also returns an obj1
Disclaimer: I've been programming for a long time, but I am fairly new to JS; probably not a best practice.
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
I have the following code (which is actually creating a Set class, or to be more explicit is creating an unordered collection of values, with no duplicates).
Unfortunately it doesn't work ==> running it in a debugger I see that the following line is returning "undefined":
o[prop] = Set._v2s.next++;
I think this is happening because o="o", so that I can't use this Set class with strings.
Any idea how to modify the code such that I can use it with strings (like in my attached example)?
Here is the code:
function Set(){ // the constructor
this.values = {}; // an empty object that will keep all the set elements'
// names as properties
this.n = 0; // #values in the set
this.add.apply(this, arguments); // when initially build the set then add all the arguments of the constructor into the set
}
// Add each of the arguments of the constructor to the set
Set.prototype.add = function(){
for (var i=0; i<arguments.length; i++){ // for each argument of the constructor
var val = arguments[i];
var str = Set._v2s(val); // transform the value to a string
if (!this.values.hasOwnProperty(str)){ // If not already in the set
this.values[str]=val; // Load the element in the set
this.n++;
}
}
return this; // support chained method call
};
// Remove each of the arguments from the set
Set.prototype.remove = function(){
for (var i=0; i<arguments.length; i++){ // for each argument
var str = Set._v2s(arguments[i]);
if (this.values.hasOwnProperty(str)){ // If the element is in the set already
delete this.values[str];
this.n--; // Delete it
}
}
return this;
};
// Return true if the set contains a value; false otherwise
Set.prototype.contains = function(value){
return this.values.hasOwnProperty(Set._v2s(value));
};
// Return the size of the set
Set.prototype.size = function(){
return this.n;
};
// Call function f on the specified context for each element of the set.
Set.prototype.foreach = function(f,context){
for (var s in this.values)
if (this.values.hasOwnProperty(s)) // ignore inherited props
f.call(context,this.values[s]); // call f on the value
};
// This internal function maps any JavaScript value to a unique string.
Set._v2s = function(val){
switch (val){
case undefined: return 'u'; // special primitive
case null: return 'n';
case true: return 't';
case false: return 'f';
default: switch(typeof val){
case 'number': return '#' + val; // numbers get the # prefix
case 'string': return '#' + objectId(val);
}
}
};
// for any object, return a string ( a unique one per object, and if applied repeatedly on the same object will return the same string. The key technique is to add a (nonenumerable and read-only in ES5) property to object o.
function objectId(o){
var prop = "|**objectid**|"; // private property name for storing ids
if (!o.hasOwnProperty(prop)) // if the object has no id
o[prop] = Set._v2s.next++; // assign it the next available
return o[prop];
};
Set._v2s.next = 100; // start assigning objectids at this value.
var my_set = new Set("o","pojo");
alert(my_set.size);
Well, By running your code, I found one small mistake alert(my_set.size);
In the definition,
Set.prototype.size = function(){
return this.n;
};
So, size is a method. So you should call it as method to return the correct result such as
var result = my_set.size()
Apparently you cannot assign new properties to primitives in JS. How about this:
var objectId = (function() {
var next = 100; // start assigning objectids at this value.
var ids = {};
return function(o){
if (!ids.hasOwnProperty(o)) // if the object has no id
ids[o] = next++; // assign it the next available
return ids[o];
};
}) ();
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
}