Being a JS Developer, this is What Keeps Me Up at Night.

微信扫一扫,分享到朋友圈

Being a JS Developer, this is What Keeps Me Up at Night.

Here, I will discuss some of the most complicated aspects of JavaScript and their possible solutions.

Mar 26 ·10min read

J avaScript is an oddball of a language. It is inspired by Smalltalk and uses a C-like syntax. It puts together aspects of functional, procedural, and object-oriented programming (OCP) models.

It has several, often redundant approaches to find a solution of nearly any conceivable programming difficulty and is not specifically opinionated about which are preferred.

At its heart, JavaScript is dynamically typed, with a mazelike approach to type coercion that trips up even the experienced developers.

JavaScript also has its traps, warts, and questionable features. Beginners usually struggle with some of its more difficult ideas–think asynchronicity, closures, and hoisting.

Programmers, who’ve experience in other languages, reasonably assume things with identical names and appearances in JavaScript and are often misguided.

We need to know that arrays aren’t actually arrays; what’s the deal with this, what is a prototype, and what does new basically do?

The difficulty with ES6 Classes

As far as I experienced, the worst offender by far is relatively new to JavaScript: ECMAScript 6 (ES6): classes.

Talks around classes stay frankly alarming and reveal a deep-rooted mix-up of how the language really works:

“JavaScript is finally a real object-oriented language now that it features classes!”

Or:

“Classes free us up from being concerned about JavaScript’s broken inheritance model.”

Or even:

“Classes are a safer and easier way to create types in JavaScript.”

I listened to these statements, but they don’t bother me. They imply there’s something mistaken with prototypical inheritance; let’s set aside from such arguments.

These statements bother me in a sense that none of them are true. For instance, they demonstrate the consequences of JavaScript’s “everything for everyone” approach to language design, which impairs a programmer’s understanding of the language more often than it enables.

Before I go any further, let’s exemplify.

JavaScript Pop Quiz#1: What’s the major difference between these code blocks?

The hint here is there isn’t one. These do virtually the same thing; it’s only a query of whether ES6 class syntax was used.

True, the second illustration is more expressive. For that reason only, you might argue that class is a fine addition to the language. Unluckily, the issue is a little more subtle.

JavaScript Pop Quiz #2: What Does the Following Code do?

The right answer is that it prints to console:

> MyClass
> Overridden in Proto
> Overridden in MyClass
> Overridden in instance

If you responded incorrectly, you don’t get what class actually is. This is not your fault. Class , much like Array , isn’t a language feature. Instead, it is syntactic obscurantism. It attempts to hide the prototypical inheritance model and the gauche idioms that come with it. Moreover, it implies that JavaScript is doing something that it’s not.

You might have been told that class was added to JavaScript to make classical OOP developers coming from languages like Java more okay with the ES6 class inheritance model. If you’re among those developers, that example probably horrified you. It should because it reflects that JavaScript class keyword doesn’t come with any of the promises that a class is meant to provide. It also determines one of the key differences in the prototype inheritance model: Prototypes are object instances, not types.

Prototypes vs. classes

The key difference between class–and prototype-based inheritance is that a class expresses a type that can be instantiated at runtime, whereas a prototype is itself an object instance.

A child of an ES6 class is another type of definition that extends the parent with new properties and approaches, which in turn can be observed at runtime. A child of a prototype is another object instance that gives the parent any properties that are not implemented on the baby.

Side note: You might be thinking why I mentioned class methods, but not prototype methods. That is mainly due to the reason that JavaScript doesn’t have a concept of methods. Functions are first-class in JavaScript, and they can possess properties or be properties of other objects.

A class constructor forms an instance of the class. A class constructor is just a JavaScript’s plain old function that returns an object. The single special thing about a JavaScript constructor is that, when invoked with the new keyword, it designates its prototype as the prototype of the returned object. If that feels a little complicated to you, you’re not alone. It’s a significant part of why prototypes are poorly understood.

To put a real-fine point on that, a child of a prototype isn’t a replica of its prototype, not is it an object with a similar shape as its prototype. The child carries a living reference to the prototype, and any prototype property that doesn’t present on the child is a one-way reference to a property of the same name on the prototype.

Consider the following:

Note: You would almost never write code like this in real life–it’s a terrible practice–but it exhibits the principle concisely.

In the preceding example, while child.foo was undefined, it referred to parent.foo . As soon as we described foo on the child , child.foo has the value ‘bar’ , but the parent.foo hold on to its original value. After we delete child.foo , it again refers to parent.foo, which leads us to the idea that when we modify the parent’s value, child.foo refers to the new value.

Let’s take a look at what just took place (for the need of crystal-clear illustration, we’re going to pretend these are Strings and not string literals, the difference does not matter here):

The way this works under the hood, and specifically the particularities of new and this, are a topic for another day, but Mozilla has an in-depth article about JavaScript prototype inheritance chain if you would prefer to read it more.

The chief takeaway is that prototypes don’t define a type; they are themselves instances and are mutable at runtime, with all that implies and entails.

Are you still with me? Let’s get back to separating JavaScript classes.

JavaScript Pop Quiz #3: How do you implement privacy in classes?

Our prototypes and class properties that we’ve mentioned above aren’t so much “encapsulated” as “hanging perilously out the window.” We should fix that, but how?

No code illustrations here. The answer is that you can’t.

JavaScript does not have any concept of privacy, but it does have closures:

Do you get what just happened? If not, you don’t understand closures. That’s acceptable, really–they aren’t as intimidating as they are made out to be, they’re extra useful, and you should apply some time to learn about them .

JavaScript Pop Quiz #4: What’s similar to the above using the class keyword?

Sorry, this is another trick question. You can do primarily the same thing, but it seems like this:

Let me know if that feels any easier or clearer than in SecretiveProto . In my personal opinion, it is somewhat worse — it breaks idiomatic implementation of class declarations in JavaScript, and it doesn’t do much like you’d suppose coming from, say, Java. This will be made unambiguous by the following:

JavaScript Pop Quiz #5: What does SecretiveClass::looseLips() do?

Let’s find out:

Well… that was inconvenient.

JavaScript Pop quiz #6: Which do experienced JavaScript developers choose–prototypes or classes?

You got it, that’s another trick question–experienced JavaScript developer incline to avoid both when they can. Here’s a decent way to do the above with idiomatic JavaScript:

This is not merely about sidestepping the inherent ugliness of inheritance or imposing encapsulation. Think about what else you could do with secretFactory and leaker that you might not certainly do with a prototype or a class.

For one reason, you can destructure it because you don’t need to worry about the context of this :

That’s pretty admiring. Besides avoiding new and this silliness, it allows us to use objects interchangeably with CommonJS and ES6 modules. It also makes composition a bit easier:

Clients of blackHat need no uneasiness about where exfiltrate came from, and spyFactory doesn’t have to mess around with Function::bing context juggling or profoundly nested properties.

Mind you, we don’t have to care much about this in simple synchronous procedural code but leads to all kinds of complications in asynchronous code that is better off avoided.

With a slight thought, spyFactory could be developed into a convincingly sophisticated espionage tool that could deal with almost all kinds of infiltration targets–or in humble words, a façade .

You could indeed do that with a class too, or rather, a range of classes, all of which inherit from an abstract class or interface …except that JavaScript does not keep any concept of abstracts or interfaces.

Now let’s return to the greater illustration to observe how we would implement it with a factory:

You probably have noticed these factories are turning terser as we go along, but don’t panic–they do the same thing. The training wheels are coming off, folks!

That’s already less boilerplate than either the prototype or the class version of the similar code. Furthermore, it accomplishes the encapsulation of its peculiarities more effectively. Also, it has a lower memory and performance footprint in some cases (it may not appear like it initially, but the JIT compiler is noiselessly working behind the scenes to pare down replication and infer types).

So it’s easier, it’s often faster, and it’s safer to write code like this. Now, the question is that why do we need classes yet again? Oh, of course, reusability. What happens if we want doomed and enthusiastic greeter variants? Well, if we’re applying the ClassicalGreeting class, we possibly jump straight into dreaming up a class hierarchy. We know we’ll have to parameterize the punctuation, so we’ll do a minor refactoring and add some children:

It’s a feasible approach until someone reaches along and asks for a feature that does’ not fit appropriately into the hierarchy, and the whole thing stops making any sense. Put a pin in that thought while we go for writing the same functionality with factories:

It is not obvious that this code is better, even though it’s a little shorter. In fact, you could claim that it is harder to read, and maybe this an obtuse approach. Couldn’t we simply have an unhappyGreeterFactory and an enthusiasticGreeterFactory ?

Then your client comes along and says, “I need a new greeter that is unhappy and wants the whole room to know about it!”

If we required to use this eagerly unhappy greeter more than a single time, we could make it easier on ourselves:

There are approaches to this style of composition that function with prototypes or classes. For instance, you could rethink UnhappyGreeting and EnthusiasticGreeting as decorators . It would still take more boilerplate than the functional-style approach used above, but that’s the cost we pay for the safety and encapsulation of real classes.

In fact, in JavaScript, you’re not going to get that automatic safety. Its frameworks that stress class usage does a lot of “magic” to paper over these kinds of problems and push classes to behave themselves. Have a look at Polymer’s ElementMixin source code sometimes. I dare you. Its arch-wizard levels of JavaScript arcana and I mean that without satire or sarcasm.

Obviously, we can fix some of the issues discussed above with Object.freeze or Object.defineProperties to greater or lesser effect. But why mimic the form without the function, while overlooking the tools JavaScript does primarily provide us that we might not see in languages like Java? Would you use a hammer labeled “screwdriver” to drive a screw, when your toolbox had an actual screwdriver sitting right next to it?

Finding the good parts in JavaScript

There are reasonable arguments about which parts of JavaScript are really good. But I hope I’ve convinced you that class isn’t one of them. Failing that, hopefully, you comprehend that inheritance in JavaScript can be a baffling mess, and that class neither fixes it nor spares you having to understand prototypes.

I’m not saying to avoid class entirely. Often, you need inheritance, and class offers a cleaner syntax for doing that. In general, class X extends Y is much finer than the old prototype approach. In addition to that, many front-end frameworks favor its use, and you should possibly sidestep writing non-standard code on principle alone. I just do not like where this is going.

In my nightmares, I sensed a whole generation of JavaScript libraries written using class , with the hope that it will behave the same as other popular languages. Completely new classes of bugs (pun intended) are revealed. Earlier ones are resurrected that could simply have been left in the Graveyard of Malformed JavaScript if we hadn’t hastily fallen into the class trap.

Experienced JavaScript developers are plagued by these freaks because what’s popular is not always what’s good.

In the end, we all give up in frustration and begin reinventing wheels in Rust, Go, Haskell, or who knows what else, and then compiling to Wasm for the web, and new web frameworks and libraries multiply into multilingual infinity.

It really does keep me waking at night.

Stay Safe: Get new coronavirus updates: CNN Live Stream on Livenewsof.com .

Understanding Pointer to Array in C++

上一篇

Verifying self-signed JWT Tokens with AWS HTTP APIs

下一篇

你也可能喜欢

Being a JS Developer, this is What Keeps Me Up at Night.

长按储存图像,分享给朋友