DEV Community

Valeria
Valeria

Posted on

Short Introduction to JavaScript

In C++ you can write text files with special syntaxis and point a compiler to those files to create an executable binary program. You can operate fixed-sized chunks of operational memory using built-in types like int (4 bytes) or char(1 byte), create your own types, or just point to a segment in RAM and go nuts operate with dynamic objects. More so, you can store logic in functions and call them from any part of your code. You can also call functions, written by somebody else, for example from a library, provided by an operating system, or from an external file.

What it has to do with JavaScript? Well, everything. JavaScript, in most cases, is executed by a C++ application directly or using bindings to a C++ library. The most popular one is called V8 JavaScript engine. If you're on a Chromium-based browser (like Chrome or Edge), that's the one executing JavaScript on every single webpage you open. There are many others, Safari's JavaScript Core or Mozilla's SpiderMonkey to name a few. But V8 stands out because it's the engine used in NodeJS and its younger brother from the same father - Deno. But worry not, most of them follow the same specification, called ECMAScript, giving us, developers, a certainty that our code will work the same way on all of them (in most cases).

(Almost) Everything is an object

Historically, JavaScript was built to make web pages dynamic, and hence, it excels at handling events from a user marking a checkbox to a server receiving a message. Such versatility is possible because nearly everything in JavaScript is an object. Well, C++ speaking, it's either a pointer to some object or a double(8 bytes), boolean(either 4 or 1 byte), or one of JavaScript's special values e.g. undefined or Symbol. But, yeah, when you hear something like "JavaScript is a prototype-based language", it means that you can treat pretty much everything as an object, differentiating things by their initial prototype. For example, you can add a property to a function:

function fn(){
 console.log('Hello, World!')
}
fn.foo = 'bar'

fn() // logs "Hello, World!"
console.log(fn.foo) // logs "bar"
console.log(fn.prototype) // logs "{}" in NodeJS 
// and "{ constructor: f} in Chrome
Enter fullscreen mode Exit fullscreen mode

By the way, you can try it yourself by right-clicking anywhere on the page and choosing "Inspect" or "Inspect Element" and choosing the "Console" tab. Or, if you have NodeJS installed, by running just node in your terminal.

Of course, in most cases, you'll simply create a file with code and either include it on the web page with:

<script src="link/to/script"></script>
<!-- or, simply: -->
<script>console.log('executing script')</script>
Enter fullscreen mode Exit fullscreen mode

Or, with NodeJS or Deno, you'll type in your terminal node path/to/script.js or deno path/or/url/to/script.js respectfully.

As you could've probably guessed, in a browser, you can simply include other scripts with <script> tags, but in NodeJS, you can use require:

// Include node built-in library to deal with files
const fs = require('fs')
// Include ./myModule.js
const myModule = require('./myModule') 
// Export something for other modules to import
module.exports = { foo: 'bar' }
Enter fullscreen mode Exit fullscreen mode

In modern JavaScript, you can also use import and export, but we'll come back to that in a bit.

What is so good about NodeJS?

I do remember the time when NodeJS looked like a joke to serious backend engineers. Oh, boy were they wrong. So what made NodeJS enter the tech stacks of giants like Netflix, Uber, Yahoo, Microsoft, and so many others?

Number One: streams. Remember events? Well, NodeJS has streams of them. You can stream chunks of data, and react to special events like error or end. For example:

const fs = require("fs");
const stream = fs.createReadStream("/path/to/filename.ext");
stream.on("data", (chunk) => console.log(chunk));
stream.on("error", console.error);
stream.on("end", () => {
  console.log("done");
});
// logs something like:
// <Buffer 63 6f 6e 73 74 20 7b 20 57 72 69 74 61 62 6c 65 2c 20 52 65 61 64 61 62 6c 65 20 7d 20 3d 20 72 65 71 75 69 72 65 28 22 73 74 72 65 61 6d 22 29 3b 0a ... 298 more bytes>
// done
Enter fullscreen mode Exit fullscreen mode

And the best part is that you can pipe streams into one another, creating a chain reaction of events:

const { Writable, Readable } = require("stream");
let dump = "";
const target = new Writable({
  write: (chunk, encoding, done) => {
    console.log({ chunk });
    dump += chunk.toString();
    done();
  },
});
const source = Readable.from(["a", "b", "c"]);
source.pipe(target);
source.on("end", () => {
  console.log("data received:", dump);
});
// logs:
// { chunk: <Buffer 61> }
// { chunk: <Buffer 62> }
// { chunk: <Buffer 63> }
// data received: abc
Enter fullscreen mode Exit fullscreen mode

You could've probably guessed by now that this approach makes data transfer using HTTP, TCP, UDP, and other internet protocols a walk in the park:

const http = require("http");
const server = new http.Server((req, res) => {
  res.write("You sent: ");
  req.on("data", (chunk) => {
    res.write(chunk);
  });
  req.on("end", () => {
    return res.end("\r\n");
  });
});
server.listen(3000, () => console.log("Listening on http://localhost:3000"));
// curl -X POST localhost:3000 -d "Hi"
// You sent: Hi
Enter fullscreen mode Exit fullscreen mode

Reason number two: async/callbacks on the same thread. NodeJS always runs on one thread, and hence can only do one thing at a time, but it has no problems switching between tasks. Everything that needs to be executed is added in a queue called "Event Stack" and processed accordingly, allowing us to use asynchronous events without cross-threading nightmares:

const asyncTask = async ()=>{await doSomething()}
const doAndTellMe = (done)=>{
 doSomething()
 doSomethingElse()
 done()
}
Enter fullscreen mode Exit fullscreen mode

Reason number three: NPM. Node Package Manager has a lot of packages. I hesitate to tell you how many packages are published at the moment, but in 2017 there were around 350,000. You can find anything from simple functions and database drivers to whole frameworks there.

Last, but not least, shared full-stack codebase. NodeJS powered web services can share common code between frontend and backend, as both of them can execute JavaScript.

What's the catch?

Well, NodeJS sucks at CPU and memory consuming operations due to the lack of threading, strong types, and memory limitations. Some of these issues do have workarounds:

  • NodeJS can start child processes and communicate with them
  • It's possible to create C++ addons for NodeJS
  • Or use WebAssembly for computation-intensive tasks
  • With TypeScript you can have your types checked on "compilation", but not in runtime

But the worst thing about NodeJS is... NPM. Have you yet heard a joke that a black hole is by far less deep than the node_modules folder? Well, it's not far from the truth. Whenever you install a package from NPM, you also install dependencies of this package and dependencies of their dependencies, and so on. So, while you think you install one module, you can easily pull hundreds of them. Deno attempts to solve it by allowing only direct imports through HTTPS while the NodeJS community (myself included) hopes there will be more zero-dependency packages.

TypeScript and others

Like most programming languages, JavaScript code is just text, that's parsed by a program and converted to some executable instructions. Well, TypeScript is not the first JavaScript enhancement, e.g. CoffeeScript was quite a dandy back in the day. Much like its predecessor, TypeScript "compiler" simply converts the code to JavaScript, checking that provided types match each other. Here's an example of how TypeScript looks like:

const a: number = 1
const b: bool = true
a + b
// Operator '+' cannot be applied to types 'number' and 'boolean'
Enter fullscreen mode Exit fullscreen mode

Apart from checking the types, TypeScript doesn't mind converting ESNext to another ECMAScript version or even bundling all files into a single .js file, among other things. Remember I mentioned import and export statements? While you do can use them without TypeScript, it does have some limitations, for example, you need to explicitly enable it in package.json or use <script type="module" src="index.js"></script> in modern browsers.

But because TypeScript (and other tools like Babel) can convert modern-day ECMAScript to cave-aged ES5 and polyfill the difference, you don't need to worry about compatibility. You can even use features from draft-, proposal- and non-existing specifications like decorators or JSX (HTML-ish blocks in JavaScript):

import React from 'react'
export default function App(){
 return <div>Hello, JSX!</div>
}
Enter fullscreen mode Exit fullscreen mode

There is a lot more to JavaScript and related tooling, but I hope that this short introduction will make you want to look into it on your own.

If so, here are a couple of links to get you started:

Good luck and thank you for reading!

Discussion (1)