When I read the book Game Development Patterns, written by a wonderful man named Bob Nystrom (I don’t write his name in Russian, because I don’t have a clue how to pronounce it), in one of the chapters I came across a small ode to the language
Smalltalk as the forefather of all modern object-oriented languages, far ahead of its time. Since I have an irreconcilable affection for all vintage languages in my life, of course, I am useful for Google. And of course, instead of taking something useful out of this experience, I learned the bad.
The peculiarity of the Smalltalk language, for which my opinion was hooked, is the absence of specially trained control structures. Instead, control flow is implemented by sending messages to objects. For example, if you send an
ifTrue message to a
Boolean object with a code block as an additional argument, this code will be executed if and only if the value of the Boolean object is true.
result := a > b ifTrue:[ 'greater' ] ifFalse:[ 'less or equal' ]
The phrase “boolean object” sounds somewhat strange if you don’t know that there are no simple values in Smalltalk: each value is an object. “Wait a minute! - I exclaimed, - something reminds me of it! ”And wrap everything up ...
')
DisclaimerIf you do something similar in the production code, you will go to hell. And there no one will be friends with you. Even Hitler. Hitler, at least, had some kind of goal.
In JavaScript, not every value is an object. However, there is an even more fun thing: automatic type casting. Every time we try to use a simple value as an object (say, access its property), it “wraps” into the corresponding object wrapper. It is because of this that we can write something like
true.toString () . A value of
true does not have a
toString method, it has a
new Boolean (true) object. If you think about it, it is ironic: even when we try to make an explicit type conversion, we implicitly (sorry for the tautology) use an implicit one.
To this object wrapper, more precisely, to its prototype, we can “hook” our own methods. This is not a good idea: if everyone does this, a conflict will arise sooner or later. Some small library for working with the clipboard will override the method
String.prototype.foo , which previously defined some widget for validating user input. I can assure you, the widget will not like it. But since today is Friday, and we are not going (we are not going to?) To use it in the code, which innocent people will work with later, you can afford some Dark Arts.
Let's start with some analogue of the Smoltokovsky
ifTrue .
Boolean.prototype.ifThenElse = function(trueCallback, falseCallback){ return this.valueOf() ? trueCallback() : falseCallback(); }
After that, if we are not afraid to upset mom, we can do things like:
(2 * 2 == 5).ifThenElse(
There are several nuances. First, the interpreter will swear at us with errors if we pass as an argument not a function, but something else (for example, nothing). Secondly, in JS there is a tradition (which came from C, where there was no Boolean type) to use in the
if construction not only logical values, but generally any. In the normal case, automatic casting will do all the dirty work, turning the “falsy” values to
false and the rest to
true , but in our case this will not happen:
(2 * 2).ifThenElse( () => alert("Freedom is Slavery"), () => alert("O brave new world!") )
So you need to climb higher. Instead of adding a method to the
Boolean prototype, add it to the
Object prototype. Sounds like a great plan, doesn't it?
function call(arg){ return typeof arg == "function" ? arg() : arg; } Object.prototype.ifThenElse = function(trueCallback, falseCallback){ if(this.valueOf()){ return call(trueCallback); }else{ return call(falseCallback); } }
Almost good. Now our method has numbers, strings, objects ... but not
undefined and not
null . Fortunately or unfortunately, they have no object wrapper. Unfortunately, these are very common false values. However, this problem is easy to solve:
const nil = { valueOf: () => false }
Let's define a couple more useful methods.
Number.prototype.for = function(callback){ for(let i = 0; i < this.valueOf(); i++){ callback(i); } } function countdown(n){ console.log(10 - n); } 10..for(countdown);
Of course, this is only a faint shadow of this
for loop, which (if you are not afraid to spoil karma and be born an opossum in the next life) you can write with an empty body altogether, taking all the logic into the condition and the final expression. On the other hand, it is the simplest scenario for using the
for loop that occurs most often.
Object.prototype.forIn = function(callback){ for(let key in this){ callback(key, this[key], this); } } Object.prototype.forOwn = function(callback){ for(let key in this){ if(this.hasOwnProperty(key)){ callback(key, this[key], this); } } } var obj = {foo: "bar"}; obj.forIn(key => console.log(key));
This is even like something useful. Do not let this resemblance deceive yourself.
Function.prototype.while = function(callback){ while(this()){ callback(); } } var power = 5; var result = 2; (() => --power).while( () => result *= 2 )
If you need to build a deuce to the fifth degree, but nothing smarter than the code above, you didn’t think up - it’s better to rush into the nearest shop with a gun-like object, take the cashier and all buyers hostage and demand that someone consider it as you. Also the idea is so-so, but from the two evils you need to choose the lesser.
And finally:
String.prototype.switch = function(callbackObject){ var f = callbackObject[this.valueOf()]; return typeof f == "function" ? f() : f; }; ("1" + 2).switch({ "12": () => console.log(" JS"), "3": () => console.log(" JS") })
Compared to the standard JS
switch , this method has several drawbacks. First, it works only for strings. If you wish, you can expand it to arbitrary values, instead of using the
Map object, but my desire to do immoral things has dried up for today, and I give it to an inquisitive reader. Secondly, there is no
default . Thirdly, there is no possibility to do such things:
switch(value){ case 1: case 2: console.log(" "); break; case 3: console.log(" "); case 4: console.log(" "); }
However, many will say that it is rather dignity.
Well, I hope you had the same fun as me. And I have to go back to work - well, to the real work. Where do not write so. Well, you understand me.