@Zerosquare said in When the reviewer doesn't understand my Javascript it's his fault:
A textbook case of Javascript Syndrome: the patient think (s)he's more clever than everyone else, while (s)he's in fact creating substandard equivalents of existing stuff (at best) or straight antipatterns (at worst).
(yeah, yeah, I know you can find people like this for any language. But the JS community seems to encourage this behaviour.)
@Gribnit said in When the reviewer doesn't understand my Javascript it's his fault:
@Zerosquare said in When the reviewer doesn't understand my Javascript it's his fault:
the JS community seems to encourage this behaviour.
Arguably the JS community is based on this behavior, starting from the language itself.
JS is actually a unique and fun language to play with, just like Python and Ruby. I played with those languages a lot, does C# and Java in my day job, and occasionally touches Haskell for the type-level drugs. However often what is not mentioned is that the average language proficiency in JS ecosystem is so abysmal and outrageous that even JS memes of all things are consistently of the lowest quality among all programming language memes. I guess it's because people don't want to admit that they don't actually know shit about JS, or modern programming languages in general.
Let's give out some examples about JS, along with the things I learnt about JS itself after playing with it for a long time:
Oh, let me give a warning first: this is a long post.
Prototype, or how to do OO in JS the right way
Let's look at Ruby. Ruby has instance methods and singleton methods (which are defined on a singleton class, an anonymous class that is defined for one and only one particular instance). Class methods are just singleton methods on the class itself. This allows you to simultaneously have all instances share a method, but also replace the same method in some of the instances, which the old class-based OOP language (read: Java and C#) lacks.
JS is a prototype-based language, which means while OO in JS is just as same as class-based OO, it's done differently. The so-called "constructors" in JS is no different from any other functions, and they can have any properties attached to them, which automatically becomes "class properties/methods". Then there are prototype methods, which is the same as instance methods, but defined on the constructor's .prototype
object; and finally, there are instance methods that are defined for one particular instance, just like Ruby's singleton methods:
// constructor method
console.log(Object.toString()); // "function Object() { [native code] }"
// prototype method
var obj = {truth: 'FileNotFound'}; // obj is an instance of Object
console.log(obj.toString()); // "[object Object]"
// this is because obj.toString === Object.prototype.toString,
// which return, roughly speaking, `[Object ${obj[Symbol.toStringTag]}]`
// instance method
var obj = {truth: 'FileNotFound', toString: function() {return JSON.stringify(this);}}
console.log(obj.toString()); // '{"truth":"FileNotFound"}'
The order of precedence for the lookup is: instance > prototype > parent's prototype > ...
Inheritance is done by setting up the prototype chain:
function Animal(name) {this.name = name;}
Animal.prototype = {
greet() {return `${this.name} is an animal.`;},
growl() {return `${this.name}: Grrrr!`;},
}
function Cat(name) {Animal.call(this, name);}
Cat.prototype = Object.assign(
Object.create(Animal.prototype), // this inherits all the prototype methods from Animal
{
greet() {return `${this.name} is a cat.`;},
meow() {return `${this.name}: Meow!`;},
catGrowl() {return super.growl.call(this) + " *Cat Noises*";},
}
);
The ES6 class syntax is just sugar for doing the same thing, but finally looks like can be grasped by the cough Java monkeys.
Now about polymorphism, we've already done the overriding:
var animal = new Animal('A'), cat = new Cat('C');
// overriding
console.log(animal.greet()); // "A is an animal."
console.log(cat.greet()); // "C is a cat."
We can even override just one specific instance:
var myCat = new Cat('FileNotFound');
Object.assign(myCat, {
__proto__: Cat.prototype, // this makes super === Cat.prototype in this object literal
greet() {return super.greet.call(this) + " TRWTF is PHP.";}
// Or super.__proto__.greet.call(this) if you want Animal.prototype.greet.call(this)
});
console.log(myCat.greet()); // "FileNotFound is a cat. TRWTF is PHP."
You can do method hiding too, but not in the usual Java/C# up/down-casting way, as it's even better: you get to choose which method you're using on the instance! In fact, you can even make up one on the fly and it'll work just as fine:
console.log(myCat.greet()); // "FileNotFound is a cat. TRWTF is PHP."
console.log(Cat.prototype.greet.call(myCat)); // "FileNotFound is a cat."
console.log(Animal.prototype.greet.call(myCat)); // "FileNotFound is an animal."
console.log(function() {
return `${this.name} is a mole.`;
}.call(myCat)); // "FileNotFound is a mole."
// Function.prototype.bind is also helpful if you want to lock the context, e.g Object.is.bind(null, -0)
Hence JS actually achieves the ultimate form of OOP paradigm: an actual OO system that completely detaches state and behaviour, and is also fully flexible and configurable. (Not to mention JS does FP very well too. It's multi-paradigm.)
In Ruby you can (sort of) do the same:
class A
def initialize
@id = 'A'
end
end
class B
def initialize
@id = 'B'
end
end
def wTF(suff) "#{@id}.#{suff}" end
p A.new.instance_exec {method(:wTF).call 'WTF'} # "A.WTF"
p B.new.instance_exec {method(:wTF).call 'ABC'} # "B.ABC"
# to add an existing method to a instance you have to either wrap it yourself in the singleton class,
# or put the method into a module and then do obj.extend(Module)
And Python too, which turns out to be a bit easier than Ruby:
class A:
def __init__(self): self.id = 'A'
class B:
def __init__(self): self.id = 'B'
def wTF(self, suff): return f'{self.id}.{suff}'
print(wTF.__get__(A())('WTF')) # "A.WTF"
print(wTF.__get__(B())('ABC')) # "B.ABC"
# to make it an actual "method" of the instance, dump the method to the instance's dict
# after binding it to that instance. It's roughly what Python's class syntax sugar does anyway
a = A()
a.__dict__['wTF'] = wTF.__get__(a)
print(a.wTF('WTF')) # "A.WTF"
But, no, you're not gonna convince enterprise programmers and software engineers that anything that does not look like Java (classical OO) will work, especially since this language is called JavaScript, and since they're too virgin to configure things in code at runtime (just like Ruby rewriting classes), while they're okay to configure configuration XML files at runtime.
Why is this important? In any not brain dead OO language (e.g Kotlin) this is not much of a problem, but in C# and Java there's a huge issue fundamental to themselves: the minimal unit in Java and C# is interface/class, and hence to define a simple function you have to make a class to contain it, to make a method configurable you have to add another layer of abstraction that provides the method as a service for your wrapper method to call it (using private field to emulate flexible method), and to make different such instances you have to add another layer of interface/abstract class to summarize them. We all know what that looks like. It's taught everywhere, for decades.
The result is that solving problems in C# and Java is often not about "taking the shortest path", but "making more layers of abstractions to smooth out the language restriction". If you look at most "design patterns" perpetuated in software engineering courses, they are all "workarounds that are essentially in Java because of how criminally rigid it is". (Protip: GoF is also widely criticized for being a book about C++ workarounds more than actual "design pattern" books.) This includes factory, builder, service, repository, visitor, observer, strategy... After touching and trying many languages I like languages like JS much more than others because I can focus on taking the shortest path when solving problems instead, with none of these redirection nonsense.
(Of course, this would also mean you can't restrict your fellow monkeys enough to write qualitatively minimally conformant code, but it's not like a restrictive language like Java has stopped them from doing so, right? )
Fortunately, the Ruby folks know that Ruby is not Java, and you need to do things the Ruby way. In Ruby, it's a common wisdom that polymorphism is done by aliasing the method and then define a new method with the original name. JS? People are crappy for the most part, and still keeps talking shit (maybe it's exactly because they don't know they're crap). So much that never add prototype methods to built-in objects is withheld as a very vocal "best practice". Typical JS opinions are like this:
- JS has this feature
- But some donkeys have abused it and made unmaintainable (subjectively speaking) code!
- Therefore we should discourage everyone from using this feature!
The irony here is that JS, for what it's worth, copiestakes new features from other languages more often than anything since ES6; while the ecosystem, is full of dummies who are more ignorant about other languages than any other ecosystem. In fact their opinion don't even hold inside the ecosystem, since the likes of JQuery and modernizr rely on adding prototype methods, which means their opinion is clearly not followed by the biggest libraries.
Duck-typing, or how to handle JS objects the right way
Quick quiz: What is considered an array in JS?
Objects that come from Array.from
? Wrong.
Objects that come from []
syntax? Wrong.
Array
as a type? Wrong. (There are no types further than objects in JS anyway)
Anything that Array.isArray
returns true for? Nope, still wrong.
In JS, everything is either one of the primitive data types or objects, and things are evaluated at runtime; if you write a.then(b)
, as long as a
is declared and can be propertied (aka it's not something like null
), has a then
which is callable, and b
has been declared, this statement will run.
What this means is that as long as your object quacks like an array, it effectively is an array. An object quacks like an array if it has a length
property that is an positive integer of [0, 2**32)
, and the respective prototype methods (which, as we established above, is separate from the object itself, so strictly speaking this isn't even needed). This makes for very interesting usages of the language:
- The simplest array-like object is
{length: 0}
. In fact new Array(n)
basically returns an Array
instance with nothing but length: n
and the prototype methods set.
- What
Array.from
does is just converting quack-like-an-arrays to Array
arrays.
Array.isArray
is merely obj instanceof Array
that also checks for all Array
objects in all global
s.
- Array holes makes perfect sense, because arrays are just objects, and objects can have arbitrary properties. Meanwhile, type arrays can't have holes because they're actually backed by binary data buffers, which also makes perfect sense.
- You can directly apply most array prototypes to strings and other objects. Examples include:
Array.prototype.slice.call(arguments)
to get argument Array
, Array.prototype.concat.apply([], arrayOfArrays)
to flatten array by one layer, Array.prototype.filter.call('thedailywtf', e => e === 't').length
to count occurrences of characters instead of the ugly 'thedailywtf'.split('t').length - 1
suggested by the typical StackOverflow JS "coder"s.
- You can chain anything with
Promise
s as long as the thing has a callable then
property, also called "thenable". No need for transient interfaces at all!
(To the pedantic: Array
is actually an exotic object, and some operations in JS uses abstract operations to create arrays so you can't override them with any amount of JS code. However usually it doesn't matter, and it can be emulated with Proxy
s or very closely in pre-ES6 with tons of Object.defineProperty
.)
In fact, JQuery has been doing duck-typing all the time: JQuery objects are just objects with a heavily dosed prototype that quacks like an ever-extending chimera. When you add stuff to $.fn
, you're adding functionality to the duck as $.fn === $.prototype === $(whatever).__proto__
. I've never seen this actually explained by anybody in the JS ecosystem. The people there only know to treat JQuery like a magic wand, and just wave it randomly when they need to use it. Like monkeys fiddling with sticks.
Automatic Semicolon Insertion (ASI), why is this still controversial in 2k19?
ASI is a language feature that has well-defined behaviour, and it's not even hard.
Instead, your typical JS coder loves to shoot themselves in the foot by not reading what the code's grammar is at the spot as they stop writing semicolons, and then yell at others to tell them stop omitting semicolons because "it might introduce bugs in your code". They never mention what causes "bugs in your code", just that "there will be nasty bugs!!!!!11!!1" which is "very bad", and so you should "stay safe" and avoid The Bad (TM). The FUD is super strong.
WAT do you mean it's an overused, completely exaggerated meme?
People love to compare JS to PHP and say "JS has implicit type conversion, hence it's as bad as PHP!" despite that they barely know anything about JS in depth.
Said people are also idiots who can't distinguish between Java and C# and lots of other things:
- JS's implicit type conversion is well-defined according to ECMAScript standards. There are some quirks, yes, and PHP has much more due to backwards compatibility and a worse initial design. But so does other languages: Did you know that
true.ToString()
in C# returns "True"
? And you really can't be bothered to RTFM? Oh wait, they indeed can't. It's why JSPerf is so popular. (See next point for JSPerf.)
- Have you heard of
===
in PHP? Yes, PHP has it too! You know, the real, actual, scientific equality operator that tests for identity! These idiots not only are oblivious of said operator, but are also oblivious of its function or semantic meaning, results in them asking idiotic questions like "why is === faster than ==", in both JS and PHP. There are really no reasons to use ==
except when you're being lazy (which then you deserve being punished for mistakes introduced this way) or when you're code-golfing (which, again, is your fault if you broke your code this way).
- Who the hell still references that WAT talk unironically? It's a parody that exaggerates these "quirks" for comedic value. Only actual idiots would ignore everything else about Ruby in the talk, then repost the same JS snippet (like
{} + []
) everywhere to mock JS even when said cases can be reasoned reasonably easily. /r/ProgrammerHumor is full of them.
Oh, and did I mention Ruby? In Ruby, String
's [] is overloaded with indexing, slicing and sub-string matching:
s = '1357924680'
p s[2] # "5"
p s['2'] # "2"
p s[(1..5)] # "35792"
p s[1,5] # "35792"
p s[7.2] # "6"
p s[/7.2/] "792"
p s[/7(.)2/, 1] "9"
Similarly, JQuery does overloading too, and similarly, nobody bothers to mention the good JS things like this. See the pattern?
JSPerf, the epitome of nobody knows what they're doing
What's the popular method of resolving language-lawyer inquiries in C++ ecosystem? Refer to the C++ language draft.
What's the popular method of resolving language-lawyer inquiries in JS ecosystem? JSPerf!
Because most JS users are incompetent monkeys who do not have the capability to read the language specs/docs, reason about language behaviour or determine code quality, you need to feed them with simple results that is also not "just do this" to withhold the illusion of "making my own judgement" for them. JSPerf produces nice numbers and graphs for them to nibble on, not unlike what the management guys would love to gobble up. The response of "how to do X in JS" is usually:
- People piling in to provide 100 ways of doing the thing because it's JS (which is a good thing, but that's due to JS itself, and not the users)
- People are too crappy to have any ideas which one is cleaner (because they're not Python programmers), how applicable/correct each one is and which one is less of a in general
- Instead they'll put every snippet in JSPerf with a broken/poorly set up test code that does a completely unrealistic tests, like empty loop, and announce that "results are in!"
- But hey, results are out, s are dispensed, the monkeys nibble on the bananas and go home happily thinking they learned something mind-blowing today
Oh, and did I mention most JSPerf tests are broken and poorly set up? Because it's worth mentioning again. Bad benchmarks are worse than no benchmarks. You should take JSPerf tests with a grain of salt in general. Even if the benchmark's not broken, usually they're measuring completely idiotic and irrelevant thing anyway; and lots of these "absolute truths!!!11!" are from 10 years ago where JS engines are basically Ruby before YARV, largely unoptimized and hardly representative of engine performance nowadays, which involves very, very heavy JITing.
Some big examples of idiotic JSPerf tests include:
It's idiotic in the sense of every schizophrenic crank/noob programmer around: thinking that compiler should be smart enough to optimize their code like reading their minds, while also believing that micro-optimizing every tiniest thing should make the code even faster because compiler should not be smart enough to outsmart their attempts to outsmart the compiler! It makes one wonder whether they're coding in JS or in C, where micro-optimization is directly visible for the most part. Well, I guess their syntax are kind of similar (JS == C
?), so...
(But hey, at least JS doesn't emit IL bytecodes, hence completely disallows any monkey's attempt to try to outsmart the JIT process since you cannot observe what's being done down below. This is a very chad thing, compared to JITs like JVM: we all know how desperate some people go just to optimize the hell out of JVM's JIT process. Also it allows for some very crazy things.)
And, in the end, even if Ruby is much slower than JS, the Rubyists are better than the JS monkeys because they actually document their benchmark findings. JS monkeys never document their benchmarks (they instead ask benchmark questions on StackOverflow), and JSPerf is a shitty site with very bad content discovery. You can ensure everyone to repeat the same mistakes if you never aggregate any of the collective knowledge around!
p.s Official ECMAScript specs is completely free. You should take a read at it whenever necessary.
But I don't want to learn async!
JS is fundamentally async, period. This is one of the biggest difference between JS and other languages. We've also since evolved from callback hell (pre-ES6) to Promises (ES6) and then async
/await
(ES8), which all aims to make writing async code easier. In Node event streams are also used very frequently.
It doesn't stop people from wanting to make async code synchronized, or wanting to write sleep
that is not busy. One example is with them adding async: false
in their ajax calls because they never learnt how to write async code properly. They also never knew about the event loop either apparently, as anything async will not run until the next tick, which is almost always after the current block of code has finished execution. And async code/stream error handling? Lots of people never does them or does them right (they think try/catch
will handle everything), which causes Node to crash without any stack traces whenever their crappy app has rejections and errors from a Promise/event stream.
And if you think JS tutorials are already bad, JS async tutorials are even worse! I've almost never seen actually good explanations of how to do these properly in JS. Meanwhile, if you're in C# (which has the same async
/await
syntax that JS presumably takes from), you sort of learn how to write async code very quickly because the resources for learning how to do it is much better IMO.
JS on StackOverflow, more like RepWhoreOverflow
JS on StackOverflow is one of the most massive sources of rep whores. Enough said.
Not convinced? Just look at this:
Can (a== 1 && a ==2 && a==3) ever evaluate to true?
An actually useful answer would be listing all 3 typical ways of doing so (valueOf
, Proxy
and Unicode variable names). Instead everyone is just posting the same, easy way (valueOf
) as their answers.
(There were even more of them back when the question became popular but the mods have purged lots of them. I've also seen at least 5 people decided that it'd be something they'd plagiarize directly to make their own coding puzzles in competitive programming sites. There people are so original.)
The other deeds these rep whores have done include:
- Posting snippets of other languages showing the same idea. WTF? This is a JS question! One answerer even sayd
There are enough good JS answers already. I just thought it would be interesting to show how it can be done in other languages, and possibly give JS developers some ideas
. I for once thought I was browsing Reddit and not SO.
- Posting more questions asking the same thing in *other languages. WTF? You can't figure out your language's features to see if something's possible, and has to open a question to farm more rep?
- Making yet another analogy of one of the popular beginner JS topics, like closures and async code. Because apparently "monad tutorials" are not rep whore enough, and people actually think analogies === helpful explanations?
(At least there's an actually interesting answer, based on race conditions in SharedArrayBuffer
s.)
The typical comments on StackOverflow JS questions/answers are just about as idiotic. It really shows the average quality of the monkeys are the JS ecosystem. Oh, also did I mention that lots of the most voted JS questions are about how to do simple things in the language, and they're also often locked for some reasons, unlike similar questions in other languages? My first guess is the same rep whore monkeys keep adding in useless noise to these questions, which leads to the mods being fed up and just lock the entire question instead.
npm, the snowflake repository
Have you heard of the left-pad
incident?
Or the flatmap-stream
incident?
Or the monstrosity known as node_modules
folder because every snippet deserves to be a module, and everyone is too lazy to write the snippet themselves and uses yet another such module to their projects?
npm ecosystem is a massive conglomerate of snowflakes that seems to lack the basic common senses: they don't take any responsibilities, their security and management is the definition of chaos (pretty much the opposite of Nuget), any major incident requires the official npm team to get it resolved, and whenever disaster happens, they blame the innocent users instead for "not doing everything to ensure they're not fucked by npm's disasters". Wow, thank you for telling us you're the most untrustworthy repository in the entire solar system! It's a pity that there are no alternatives, or I'd have migrated on the spot!
Hmm, not unlike the JS rep whores on StackOverflow. Maybe cancer is infectious after all...
Why does Codecademy even exist?
Enough said. They're powered by venture capital, relies on heavily deceptive, manipulative and sugar-coated marketing practices, to create the entitlement that "everyone can learn how to code easily" in order to net wishful and unsuspecting users. Their courses and tutorials are useless and underwhelming, that doesn't really teach you anything beyond "learn Java in X minutes" level. They also created lots of crappy "JS front-end programmers". They've really contributed a lot into making the JS ecosystem the global WTF as it is right now.
To end the WTF in some good news, there are still good JS resources: MDN, and https://javascript.info, but unfortunately the latter is originally in Russian, and is quite oudated in the english translation. Reading them is more helpful than the nest of chaos and random noise for learning JS.
(Yes, I know PHP resources are even worse. But usually you don't have to touch PHP compared to JS that is everywhere, unless you're working with Wordpress for some reasons, which is a WTF of its own. )