I need some help. I want to implement Enum with modern javascript. I want it to be immutable and think it will looks something like that:
class AlphabetEnum{
static get A(){
return 'a';
},
static get B(){
return 'b';
}
...
}
However, it's a bit annoying to write all this getters. So I'm curious - is there a chance to optimize this with compute method names and maybe some other es2015 features.
In result I dream to have something like that:
let alph = [a, b, c, ..., z];
class AlphabetEnum{
static get [some__clever_way_to_resolve_names_from_<alph>](){
return some_clever_way_to_understand_what's_called();
},
}
A class makes just no sense. You don't a want a function with static getters. You just want an immutable object - and that's easy:
const AlphabetEnum = Object.freeze({
A: "a",
B: "b",
…
});
Of course you can also dynamically create that if listing all properties is too cumbersome:
const AlphabetEnum = {};
for (let i=0; i<26; i++)
AlphabetEnum[String.fromCharCode(65+i)] = String.fromCharCode(97+i);
Object.freeze(AlphabetEnum);
You'd do it the same way is in ES5, with Object.defineProperty:
class AlphabetEnum {}
['a', 'b', 'c', ..., 'z'].forEach(letter => {
Object.defineProperty(AlphabetEnum, letter.toUpperCase(), {
get: () => letter,
configurable: true, // remove this line if not needed / wanted
});
});
However, using a class just for static properties is an anti-pattern IMO. In that case you can as well use a plain object:
var AlphabetEnum = {};
Related
So I have a an array of functions (or actually an object of functions but it doesn't matter) which returns a different objects such as this:
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
and now I want to get a type that contains all the merged values such as:
{
a: string;
b: string;
}
If tried some reduce solutions but all I've gotten to is a type that looks like:
{ a: string } | { b: string }
which isn't what I'm looking for.
Any ideas?
Update 1
The array in the example is a simplification and the actual return values of the functions are unique and is therefore needed to be kept as is => I cannot use a generalized interface such as
interface ReturnValues {
[key: string]: string;
}
Update 2
The problem is not of a JS kind but of TS and it's types. Ultimately I want to achieve this kind of functionality:
const result = arr.reduce((sum, fn) => Object.assign(sum, fn()), {})
and I want the type of result to be { a: string, b: string } so that I can call result.a and typescript will know that this is a string. If the result is { a: string } | { b: string }, calling result.a typescript says this is of the type any.
Also, for the ease of it, one can assume that there is no overlapping of the returning values of the functions.
you can use Array.reduce
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
const obj = arr.reduce((acc, cur) => ({ ...acc, ...cur() }), {});
console.log(obj);
Since TypeScript doesn't have proper variadic type support yet (See this issue), the only real way to achieve what you're looking for is this:
const a = [{a:1},{b:2}] as const;
function merge<TA, TB>(a: TA, b: TB): TA & TB;
function merge<TA, TB, TC>(a: TA, b: TB, c: TC): TA & TB & TC;
function merge<TA, TB, TC, TD>(a: TA, b: TB, c: TC, d: TD): TA & TB & TC & TD;
function merge(...list: Array<any>): any {}
const b = merge(...a);
There are 3 primary methods of "mixing" javascript objects.
The process your looking to achieve is called a "mixin".
The older and more widely used method is to use whats called an extend function.
There are many ways to write an extend function, but they mostly look something like this:
const extend = (obj, mixin) => {
Object.keys(mixin).forEach(key => obj[key] = mixin[key]);
return obj;
};
here "obj" is your first object, and "mixin" is the object you want to mix into "obj", the function returns an object that is a mix of the two.
The concept here is quite simple. You loop over the keys of one object, and incrementally assign them to another, a little bit like copying a file on your hard drive.
There is a BIG DRAWBACK with this method though, and that is any properties on the destination object that have a matching name WILL get overwritten.
You can only mix two objects at a time, but you do get control over the loop at every step in case you need to do extra processing (See later on in my answer).
Newer browsers make it somewhat easier with the Object.Assign call:
Object.assign(obj1, mix1, mix2);
Here "obj1" is the final mixed object, and "mix1", "mix2" are your source objects, "obj1" will be a result of "mix1" & "mix2" being combined together.
The MDN article on "Object.Assign" can be found here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Like the extend function above "Object Assign" WILL overwrite properties in the destination object, but it does have the advantage of doing many at a time. My example above only shows 2 "mix" objects, but you can in theory have as many as you like, and that comes in really useful when you have them all in array as you have.
In an array you can either map the objects into one function, and then use the spread operator available in newer browsers, or you can use for..in to loop over the collection.
If your using JQuery, you can use it's foreach method, and underscore.js has dozens of ways of looping.
Since your using TypeScript you can also combine a lot of this with typescripts looping operators too.
There is a 3rd way of merging objects, it's not widely used but it is gaining traction, and that's the "Flight-Mixin" approach that uses the Array prototype, it looks something like this:
const EnumerableFirstLast = (function () { // function based module pattern.
const first = function () {
return this[0];
},
last = function () {
return this[this.length - 1];
};
return function () { // function based Flight-Mixin mechanics ...
this.first = first; // ... referring to ...
this.last = last; // ... shared code.
};
}());
EnumerableFirstLast.call(Array.prototype);
The idea here is that the two objects all ready have the functionality you require on them, so instead of "mixing" them, your just providing a single interface that delegates to them behind the scenes.
Beacuse your adding to the array prototype, you can now do things like the following:
const a = [1, 2, 3];
a.first(); // 1
a.last(); // 3
This might seem as if it's of no use, until you consider what you've in effect just done is added two new functions to a datatype you cannot normally control, this MIGHT if applied to your own objects allow you to add functions dynamically, that simply just grab the values you need to merge in a loop without too much trouble, it would however require a bit of extra planning which is why I'm adding this as more of an idea for further exploration rather than part of the solution.
This method is better suited for objects that are largely function based rather than data based as your objects seem to be.
Irrespective of which mixin method you use though, you will still need to iterate over your array collection with a loop, and you will still need to use spread to get all the keys and properties in one place.
If you consider something like
const myarr = [
{name: "peter", surname: "shaw"},
{name: "schagler", surname: "kahn"}
]
The way the spread operator works is to bust those array entries out into individual parts. So for example, IF we had the following function:
function showTwoNames(entryOne, entryTwo) {
console.log(entryOne.name + " " + entryOne.surname);
console.log(entryTwo.name + " " + entryTwo.surname);
}
You could call that function with the spread operator as follows:
showTwoNames(...myarr);
If your array had more than 2 entries in it, then the rest would be ignored in this case, the number of entries taken from the array is directly proportional to the number of arguments for the function.
You could if you wanted to do the following:
function showTwoNames(entryOne, entryTwo, ...theRest) {
console.log(entryOne.name + " " + entryOne.surname);
console.log(entryTwo.name + " " + entryTwo.surname);
console.log("There are " + theRest.length + " extra entries in the array");
}
Please NOTE that I'm not checking for nulls and undefined or anything here, it should go without saying that you should ALWAYS error check function parameters especially in JavaScript/TypeScript code.
The spread operator can in it's own right be used to combine objects, it can be simpler to understand than other methods like "ObjectAssign" beacuse quite simply you use it as follows:
var destination = { ...source1, ...source2, ...source3); // for as many sources as needed.
Like the other methods this will overwrite properties with the same name.
If you need to preserve all properties, even identically named ones, then you have no choice but to use something like an extend function, but instead of just merging directly using a for-each as my first example shows, you'll need to examine the contents of "key" while also looking in the destination to see if "key" exists and renaming as required.
Update RE: the OP's updates
So being the curious kind I am, I just tried your updated notes on one of my Linux servers, Typescript version is 3.8.3, Node is 12.14.1 and it all seems to work just as you expect it to:
I'm using all the latest versions, so it makes me wonder if your problem is maybe a bug in an old version of TS, or a feature that has only just been added in the newest build and is not present in the version your using.
Maybe try an update see what happens.
It seems that TypeScript doesn't have a native solution for this. But I found a workaround.
As mentioned in the question, using the reduce-method one gets a TS type of { a: string } | { b: string } (and to be clear, of course also a resulting object of { a: "a", b: "b" }.
However, to get from { a: string } | { b: string } to { a: string, b: string } I used the following snippet to merge the types:
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends (k: infer I) => void
? I
: never;
So this would be my resulting code:
const arr = [
() => ({ a: "a" }),
() => ({ b: "b" })
]
const result = arr.reduce((sum, fn) => Object.assign(sum, fn()))
// Result is now { a: "a", b: "b" }
// but the TS type is '() => ({ a: string } | { b: string })'
type ResultUnion = ReturnType<typeof result>
// ResultUnion = { a: string } | { b: string }
type ResultIntersection = UnionToIntersection<ResultUnion>
// This is where the magic happens
// ResultIntersection = { a: string } & { b: string}
// It's not _exactly_ what I wanted, but it does the trick.
// Done
I currently have an object literal in Typescript. Something like:
const MyNamesStrings = {
a: {
b: "hello",
c: "bye"
}
d: {
e: "qwerty"
}
}
But I want to wrap them in some strings every time I access them. The strings are the same, and would look very ugly and repetitive if it's in the literal. This would allow me to have a much easier time maintaining the strings.
I want to create a MyNames class that acts like a proxy with the functionality to do this:
const ab = MyNames.a.b //"[${hello}]" where the extra characters surround the text.
const ac = MyNames.a.c //"[${bye}]"
Is this feasible in Javascript/Typescript? If it's not, I can always just do it in more mundane ways.
If you really need the behavior you describe, you could use an actual Proxy, if your JavaScript engine supports ECMAScript 2015 or later. This might be overkill for your use case; if MyNamesStrings is immutable, a one-time transformation from MyNamesStrings to MyNames would be more efficient. But let's assume you do need a proxy. Here's one way to implement it:
function makeStringWrappingProxy<T extends object>(t: T): T {
return new Proxy(t, {
get<K extends keyof T>(target: T, prop: K) {
const val = target[prop];
if (typeof val === 'string') {
return '[${' + val + '}]';
} else if (typeof val === 'object') {
return makeStringWrappingProxy(val as T[K] & object);
} else {
return val;
}
}
});
}
The idea is return a proxy which that intercepts all property retrievals to the object. If you are getting a string property, return the wrapped string instead. If you are getting an object property, return a proxy for that property instead (this allows you to drill down into properties of properties and still see them wrapped). Otherwise, just return the property.
Let's see it in action:
const MyNames = makeStringWrappingProxy(MyNamesStrings);
const ab = MyNames.a.b //"[${hello}]" as expected
const ac = MyNames.a.c //"[${bye}]" as expected as well.
So it works! Again, this assumes you actually want to do it this way. Proxies aren't particularly performant (each property access kicks off function calls), backward compatible (ES5 doesn't support it), or intuitive (you can modify a property but when you read the property afterward it isn't what you set it to). It's up to you.
Hope that helps; good luck!
basically you could do a custom getter function and instead of accessing to MyNamesStrings.a.b you can do MyNamesStrings.propGetter('a.b')
var MyNamesStrings = {
a: {
b: "hello",
c: "bye"
},
d: {
e: "qwerty"
},
propGetter: function(loc, start = "[${", end = "}]") {
var split = loc.split('.');
var value = this;
for (var i = 0; i < split.length; i++) {
if (value) {
value = value[split[i]];
}else{
//stop looping because the previous value doesn't exist.
break;
}
}
return value ? start + value + end : undefined;
}
}
var result = MyNamesStrings.propGetter('a.b');
var result2 = MyNamesStrings.propGetter('d.e');
var result3 = MyNamesStrings.propGetter('f.g.h.i');
console.log(result);
console.log(result2);
console.log(result3);
Update: These checks are meant for compile time, not at runtime. In my example, the failed cases are all caught at compile time, and I'm expecting similar behaviour for the other should-fail cases.
Suppose I'm writing a table-like class where I want all members of the class to be arrays of the same length, something like:
class MyClass {
tableHead: string[3]; // expect to be a 3 element array of strings
tableCells: number[3]; // expect to be a 3 element array of numbers
}
The closest solution I've found so far is:
class MyClass {
tableHead: [string, string, string];
tableCells: [number, number, number];
}
let bar = new MyClass();
bar.tableHead = ['a', 'b', 'c']; // pass
bar.tableHead = ['a', 'b']; // fail
bar.tableHead = ['a', 'b', 1]; // fail
// BUT these also pass, which are expected to fail at compile time
bar.tableHead = ['a', 'b', 'c', 'd', 'e']; // pass
bar.push('d'); // pass
bar.push('e'); // pass
Any better ideas?
Update 2: From version 3.4, what the OP asked for is now fully possible with a succinct syntax (Playground link):
class MyClass {
tableHead: readonly [string, string, string]
tableCells: readonly [number, number, number]
}
Update 1: From version 2.7, TypeScript can now distinguish between lists of different sizes.
I don't think it's possible to type-check the length of a tuple. Here's the opinion of TypeScript's author on this subject.
I'd argue that what you're asking for is not necessary. Suppose you define this type
type StringTriplet = [string, string, string]
and define a variable of that type:
const a: StringTriplet = ['a', 'b', 'c']
You can't get more variables out of that triplet e.g.
const [one, two, three, four] = a;
will give an error whereas this doesn't as expected:
const [one, two, three] = a;
The only situation where I think the lack of ability to constrain the length becomes a problem is e.g. when you map over the triplet
const result = a.map(/* some pure function */)
and expect that result have 3 elements when in fact it can have more than 3. However, in this case, you are treating a as a collection instead of a tuple anyway so that's not a correct use case for the tuple syntax.
From Typescript: Can I define an n-length tuple type?, programmatically, with dynamic length:
type Tuple<TItem, TLength extends number> = [TItem, ...TItem[]] & { length: TLength };
type Tuple9<T> = Tuple<T, 9>;
Here is a simple example of a class to control the length of its internal array. It isn't fool-proof (when getting/setting you may want to consider whether you are shallow/deep cloning etc:
https://jsfiddle.net/904d9jhc/
class ControlledArray {
constructor(num) {
this.a = Array(num).fill(0); // Creates new array and fills it with zeros
}
set(arr) {
if (!(arr instanceof Array) || arr.length != this.a.length) {
return false;
}
this.a = arr.slice();
return true;
}
get() {
return this.a.slice();
}
}
$( document ).ready(function($) {
var m = new ControlledArray(3);
alert(m.set('vera')); // fail
alert(m.set(['vera', 'chuck', 'dave'])); // pass
alert(m.get()); // gets copy of controlled array
});
In es2015, if I have base class to represent a List that looks like this:
class List {
constructor(data){
this.data = data
}
sortBy(attribute){
return this.data.sort((a,b) => {
return (a[attribute] < b[attribute]) ? 1 : -1;
})
}
get count() { return this.data.length }
}
Then I might want to subclass that base class with a less generic kind of data, namely, if I am an elf, toys:
class ToyList extends List {
constructor(toys){
super(toys);
this.toys = toys;
}
}
At this point ToyList is no different from List, except for the name. But if you look at an instantiation of ToyList, it has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.
If I make a ToyList, I have both .data and a .toys attributes:
tl = new ToyList(['truck', 'plane', 'doll'])
Object { data: Array[3], toys: Array[3] }
Then my tl has both a data and a toys attribute. They’re both references to the same array, but what I would like is for the subclass to only have the toys reference.
Here’s another example which utilizes a method on the base class:
class Todos extends List {
constructor(todos){
super(todos);
this.todos = todos;
}
get byPriority(){
return this.todos.sortBy('priority')
}
}
var thingsToDo = [
{task: 'wash the dog', priority: 10},
{task: 'do taxes', priority: 1},
{task: 'clean the garage', priority: 0}
]
var todos = new Todos(thingsToDo);
todos.byPriority
This would be nice, because then I could just refer to .byPriority to get a sorted version of the list which is very specific to this particular kind of data. But I can’t see how I can make that happen, because
But what I get is:
TypeError: this.todos.sortBy is not a function
So to summarize, what I want is a way to refer to to base class properties with a name which is specific to the semantics of the subclass, without losing the methodology of the base class.
referencing our discurrion in the comments, a better implementation (imo), extensible and avoiding the problem you asked about
var AP = Array.prototype; //just lazy
class List {
constructor(elements){
for(var i = 0, j = (elements && elements.length)|0; i<j; ++i)
this[i] = elements[i];
//use length instead of count, stay compatible with the Array-methods
//will make your life easier
this.length = i;
}
length: 0
sortBy(attr){
return this.sort(function(a,b){
return (a[attribute] < b[attribute]) ? 1 : -1
});
}
//some functions have to be wrapped, to produce a List of the right type
filter(fn){
return new (this.constructor)(AP.filter.call(this, fn));
}
clone(){ return new (this.constructor)(this) }
}
//some functions can simply be copied from Array
//no need to re-implement or even wrap them.
List.prototype.sort = AP.sort;
List.prototype.push = AP.push;
List.prototype.pop = AP.pop;
the subclass
class ToyList extends List {
constructor(toys){
//maybe you want to filter the input, before you pass it to the list
//or convert it, or whatever, it's all up to you
super(toys && AP.filter.call(toys, v=>v instanceof Toy));
}
//... additional functionality
}
and an example usage
class Toy {
constructor(name){
this.name = name;
}
}
var a = new ToyList([
new Toy("foo"),
new Toy("bar"),
"not a toy",
new Toy("baz")
])
console.log(a instanceof ToyList, a);
var b = a.filter(toy => toy.name.charAt(0) === "b");
console.log(b instanceof ToyList, b);
Edit: added your Example with the Todos
class Todos extends List {
//don't even need a constructor, since I simply want to pass
//the array to the parent-constructor
//don't use getter for functionality, use methods!
byPriority(){
return this.sortBy('priority');
}
}
var thingsToDo = [
{task: 'wash the dog', priority: 10},
{task: 'do taxes', priority: 1},
{task: 'clean the garage', priority: 0}
]
var todos = new Todos(thingsToDo);
todos.byPriority()
ToyList has both data and toys properties. These refer to the same array, in terms of conceptualizing the point of a ToyList, data doesn’t make much sense.
There's your actual problem: your ToyList doesn't make sense as a subclass of List.
If (for any reasons) your class should be similar to List, but not have a data property, then it's not a subclass any more. It would violate the Liskov substitution principle.
Now what are your options?
as you already considered, you can make it a subclass in which the more specific .toys property is an alias for .data. This is perfectly fine, but you can't avoid having that data property there as well.
you might want to outiright scrap that data property and store elements directly on the object. Your List class looks like "Array but with useful helper functions". If that was your intention, you should consider making it an actual subclass of Array. #Thomas's answer goes in that direction.
you might want to favor composition over inheritance. You've already used the concept - your List instances contain Arrays in their data properties. If you have a Wishlist or Toylist or whatever, that deal specifically with whishes or toys and have corresponding methods for them, you can simply store a List instance in their .toys slot.
You actually seemed to expect your TodoList to work like that, given the invocation of this.todos.sortBy('priority') (where this.todos would be a List). On an subclass, just this.sortBy('priority') would do the job.
I didn't really get how your ToyList is a specialisation of List. If there is nothing special about it but the name, maybe you actually don't need a different class alltogether. If JavaScript had generics or type variables, you'd use a List<Toy>, but it doesn't so you can just use Lists directly.
I think you have a lot of different problems.
Your list has a problem with the definition of sortBy, you need to take 3 cases in account, like this:
class List {
constructor(data){ this.data = data; }
sortBy(attribute){
console.log("sortBy");
return this.data.sort( (a,b) => {
if (a[attribute] < b[attribute]) return -1;
if (a[attribute] > b[attribute]) return 1;
return 0;
});
}
get count() { return this.data.length; }
}
Now you can extend the List, and if you want to name data as toys then define a get method named toys() to return the data. It may strange to you, but if you subclass List then you should use data (if not, don't subclass it). There is an alternative: you can delete data attribute and then create toys but alas, designing a sortBy method in List would be difficult (or use a second parameter to name the array to sort?). So, let's use the first suggestion:
class ToyList extends List {
constructor(toys){ super(toys); }
get toys() { return this.data; }
}
Let do the same for Todos:
class Todos extends List {
constructor(todos){ super(todos); }
get todos() { return data; }
get byPriority(){
return this.sortBy('priority');
}
}
The definition of byPriority is a little bit weird as it has a border effect (sorting the elements). I (personally) would write it as a standard method.
Then let's make some tests:
var thingsToDo = [
{task: 'wash the dog', priority: 10},
{task: 'do taxes', priority: 1},
{task: 'clean the garage', priority: 3}
];
var tl = new ToyList(['truck', 'plane', 'doll']);
for (var i=0; i<3; i++) {
console.log(tl.toys[i]); // access the *pseudo* toys attribute
}
var todos = new Todos(thingsToDo);
var r = todos.byPriority; // access the *pseudo* byPriority attribute (border effect: sorting internal data)
for (var i=0; i<3; i++) {
console.log(todos.data[i].priority);
}
May I suggest you to have a little more read about OOP and inheritance? The point of need to subclass but removing data attribute is certainly a bad design.
IMO, a better approach, if possible, would be to treat the List class are pure virtual, Meaning you will never create an instance of that class, but only just extend from it.
Pure virtual classes are not supposed to have constructors, and the methods are to assume certain properties exist. However, you could infact use the constructor to set the name that the base class should use for the 'data' property.
class List {
constructor(keyName) { this.keyName = keyName }
sortBy(attr) { return this[this.keyName].sort(...) }
}
class ToyList extends List {
constructor('toys') {
super(toys)
this.toys = toys;
}
}
If we write this-
var myVariable={
propertyA:"valueA",
propertyB:"valueB",
}
We can call propertyB like this-
myVariable.propertyB
Keeping this in mind, when we write-
document.getElementById('myDiv').style.visibility='hidden'
We can write this, like this-
document.getElementById('myDiv').style={
visibility:'hidden',
display:'inline',
}
Well, if that was correct then we may do this-
document.getElementById('myDiv')={
innerHTML:'this is a div',
style:{
visibility:'hidden',
display:'inline',
}
}
Now, if those were correct then may be this also-
document={
getElementById('myDiv'):{
innerHTML:'this is a div',
style:{
visibility:'hidden',
display:'inline',
}
}
getElementById('mySpan'):{
innerHTML:'this is a span',
style:{
visibility:'visible',
display:'table',
}
}
}
So, how many of them are wrong/correct? If wrong, why and what was wrong? Can you give me any more information related to this?
Thanks in advance :-)
All Javascript objects are hashes, and vice versa -- so your assumption is based in truth. However, Javascript assignment is a replacement operation, not augmentation. This can be easily seen in a simple example:
obj = { a: { b: 2 } }
obj.a.b // => 2
obj.a = { c: 3 }
obj.a.c // => 3
obj.a.b // => undefined
Barring magic in the implementation of a HTMLElement's style attribute, assigning to it will not simply merge the values. The further up that chain you assign, the more damage you do.
Finally, any value given before a : in an object literal is expected to be either a string or a bareword (which will be quoted) -- variables and function calls (like getElementById('mySpan')) cannot be used as keys in an object literal.
You can, however, get the behavior you were looking for with something like the following:
var divStyles = document.getElementById('myDiv').style;
var styles = { visibility:'hidden', display:'inline' };
for (key in styles) {
if (styles.hasOwnProperty(key)) {
divStyles[key] = styles[key];
}
}
The overhead may or may not be worth it, depending on how many properties you are changing.
Whenever you do
myVar.someObject = { ... }
You completely override "someObject", throwing away any poperties it had before the assignment.
var myvar = { obj: {a:1, b:2} };
myvar.obj = {a:3}
console.log(myvar.obj.a) // 3; ok
console.log(myvar.obj.b) // undefined; oh no!