Naming
What you are implementing is a Symbol Table. How you implement the symbol table is nobody's business, it is a private internal implementation detail that nobody should need to care about.
Yet, your name is exactly the opposite: what your class actually is, is hidden behind an obscure acronym and tacked on at the end, whereas the part that I shouldn't even know about in the first place, screams at me from the front.
I would name the class SymbolTable
:
class SymbolTable {
// …
}
Triple equals
There are two places where you use the Abstract Equality Comparison Operator ==
or more precisely its negation !=
. It is generally best if you forget about its existence and never use it.
Always use the Strict Equality Comparison Operator ===
or its negation !==
instead.
Consistency
Sometimes you use the Abstract Equality Comparison Operator and sometimes the Strict Equality Comparison Operator. Sometimes you use semicolons and sometimes you don't.
Class fields
There is a stage-3 proposal for class fields. Whether you can use them or not depends on your environment. I personally quite like them. In this case, it allows us to get rid of the constructor.
class SymbolTable {
first = null;
get(key) {
// …
}
put(key, val) {
// …
}
}
Private fields
There is a stage-3 proposal for private fields. Whether you can use them or not depends on your environment. I personally quite like them.
class SymbolTable {
#first = null;
get(key) {
for(let x = this.#first; x !== null; x = x.next) {
if(x.key === key) {
return x.val
}
}
return null;
}
put(key, val) {
for(let x = this.#first; x !== null; x = x.next) {
if(x.key === key) {
return x.val = val
}
}
this.#first = new Node(key, val, this.#first);
}
}
You don't necessarily have to initialize them, they will default to undefined
which arguably is "more correct" than null
anyway in this case.
Data classes / records / structs
I agree with CertainPerformance that simple data classes / records / structs / whatever you want to call them are unnecessary in ECMAScript. In fact, confusingly, what ECMAScript calls an "object" is actually technically a record and not an object in the sense of "OOP". If you want an object in the sense of OOP, you use a function (more precisely, a closure).
So, for simple records, just use objects. There is no need for Node
. IFF you want to keep Node
, it would make sense to hide it inside SymbolTable
, since it shouldn't be exposed to clients. Something like this:
class SymbolTable
static #Node = class Node {
key;
val;
next;
constructor(key, val, next) {
this.key = key;
this.val = val;
this.next = next;
}
};
}
Map
Note that the ECMAScript core library does contain a Map
datatype which more or less does what you want already.
Also, since you are implementing a symbol table, the restriction that object keys can only be strings or symbols is not really much of a restriction, and you can simply use objects for your symbol tables.
I am assuming this is actually an exercise for implementing a linked list, and not an exercise about writing a compiler, though.
The Result
This is what it looks like, merging in also CaptainPerformance's improvement providing an iterator. I left the Node
class in, just as an example, but normally, I would eliminate it and simply use an object, as in CaptainPerformance's answer.
class SymbolTable {
#first;
static #Node = class Node {
key;
val;
next;
constructor(key, val, next) {
this.key = key;
this.val = val;
this.next = next;
}
};
*[Symbol.iterator]() {
for (let node = this.#first; node; node = node.next) {
yield node;
}
}
get(key) {
for (const node of this) {
if (node.key === key) {
return node.val;
}
}
}
put(key, val) {
for (const node of this) {
if (node.key === key) {
node.val = val;
return;
}
}
this.#first = new SymbolTable.#Node(key, val, this.#first);
}
}
const st = new SymbolTable();
st.put('s', 0);
st.put('e', 1);
st.put('a', 2);
st.put('r', 3);
st.put('c', 4);
st.put('h', 5);
st.put('e', 6);
st.put('x', 7);
st.put('a', 9);
st.put('m', 10);
st.put('p', 11);
st.put('l', 12);
st.put('e', 13);
console.dir(st.get('x'));
for (const { key, val } of st) {
console.dir(`${key} : ${val}`);
}
Ruby
Compared to Ruby, two main differences stand out:
ECMAScript's core library is much smaller. (For example, there isn't even a way to get user input or generate output!) This is by design. ECMAScript is designed to be embedded into an application, and the core objects and operations are provided by that application. E.g. embedded into a web browser, the browser provides the DOM, with e.g the console.log
method.
Object literals. Often, you don't need to create a class just so you can instantiate an object of the right shape. You can just write down the object you want. (See the nodes in CaptainPerformance's answer.)