links: JS MOC


Surveying JS

The best way to learn JS is to start writing JS

Each file is a program

  • In JS each standalone file is it’s own separate program
  • ES6 introduced modules, which JS can treat that whole as single program
  • The only way multiple standalone .js files acts as single program is by sharing their state via “global scope”. They mix together in global scope namespace and acts as whole
  • The reason it matters because of error handling. If one of 5 js files has error, js will ignore that one file and continue executing the rest of them. when this happens you will be left with partial working application

TODO:

Values

Values are the fundamental unit of information in a program. They’re how the program maintains state.

In JS there are two types of values

  1. primitive - JS Primitives
  2. Object - JS Object Value Types

Values are embedded in program using Literal

greeting("Hello, my name is Subramanya")

In this program “Hello, my name is Subramanya” is a primitive string literal

The double-quote " character is to delimit (surround, separate, define) the string value. You can use `

console.log(`My name is ${firstName}`) // My name is Subramanya

Assuming firstName is already defined with the String value “Subramanya”, the ` delimited string resolves the variable expression (indicated with ${…}) to it’s current value. It’s called Interpolation

Value type Determination

For distinguishing values, typeof operator tells you it’s built in type

typeof 42;                  // "number"
typeof "abc";               // "string"
typeof true;                // "boolean"
typeof undefined;           // "undefined"
typeof null;                // "object" -- oops, bug!
typeof { "a": 1 };          // "object"
typeof [1,2,3];             // "object"
typeof function hello(){};  // "function"

Converting from one value type to another, such as string to number, is called coercion

Primitive values and Object values differently when they are assigned and passed around

Declaring and using variables

  • Think of variables as containers of values var x = 2 (variable x holds value 2)
    var name = "Subramanya"
    var age
    
  • variables have to be declared(created) to be used.
  • There are different syntax forms to declare variables(aka “identifiers”), and each form has different implied behavior

Another similar keyword is let:

let name = "Subramanya"
let age

The let keyword has some differences to var.

  • let is block scoped which means the lifetime of let variable is between the braces
  • var is function scoped which means the lifetime of var variable is the lifetime of the function (aka inside the function)
var adult = true

if(adult) {
	var name = "Subramanya"
	let age = 26
	console.log("Shhh this is a secret")
}

console.log(name)
// Subramanya

console.log(age)
// Error!

Even though many suggest to use let over var, Both declaration forms can be appropriate in any given part of a program, depending on the circumstances

A third declaration form is const, it’s like let but had additional limitation that it must be given a value at the moment it’s declared, and cannot be reassigned a value later

const myBirthday = true;
let age = 39;

if (myBirthday) {
    age = age + 1;    // OK!
    myBirthday = false;  // Error!
}

myBirthday can’t be reassigned

const declared variables are not “unchangeable”. It’s better not to use const for object values(like array or object) because those values can still be changed even though the variable can’t be reassigned. This leads to potential confusion down the line, so it’s better to avoid situations like these

const actors = [
    "Morgan Freeman", "Jennifer Aniston"
];

actors[2] = "Tom Cruise";   // OK :(
actors = [];                // Error!

The best semantic use of const is when you have a simple primitive value that you want to give a useful name

TIP: If you stick to using const only with primitive values, you avoid any confusion of re-assignment (not allowed) vs. mutation (allowed)! That’s the safest and best way to use const.

Besides var/let/const, there are other syntactic forms that declare identifiers(variables) in various scopes. For example

function hello(name) {
	console.log(`Hello ${name}`)
}

hello("Subramanya")
// Hello Subramanya

The identifier hello is created in the outer scope, and it’s also automatically associated so that it references the function. But the named parameter name is created only inside the function, and thus only accessible inside the function’s scope. hello and name generally behave as var declared

Another syntax that declares a variable is catch clause:


try{
	someError()
} catch(err) {
	console.log(err)
}

the err is block scope that exists only inside catch clause, as if it had been declared with let

Functions

  • The word “function” has a variety of meanings in programming. For example, in the world of functional programming, “function” has a precise mathematical definition and implies a strict set of rules to abide by

More about JS Functions

In JS, we should consider function to a broader meaning of another related term procedure. Procedure is a set of statements that can be invoked one or more times, maybe provided some inputs and maybe provided some outputs

Example:

function awesomeFunction(coolThings) {
	// ..
	return amazingStuff
}

This is called function declaration

Comparisons

Making decisions in programming requires comparing values to determine their identity and relationship to each other. JS has several mechanisms to do that

Equal…ish

The most comparison in JS is program asks the question “Is this X value same as that Y value?“. What exactly “the same as” mean in JS though?

For ergonomic and historical reasons, the meaning is more complicated than the obvious exact identity sort of matching. Sometimes an equality comparison intends to exact matching, but other times the desired comparison is a bit broader, allowing closely similar or Interchangeable matching. In other words, we must be aware of the nuanced differences between the equality comparison and Equivalence comparison

The strict equality compares both value and type

3 === 3.0;              // true
"yes" === "yes";        // true
null === null;          // true
false === false;        // true
 
42 === "42";            // false
"hello" === "Hello";    // false
true === 1;             // false
0 === null;             // false
"" === null;            // false
null === undefined;    
Note:
All value comparisons in JS consider the type of the values being compared, not just the operator. Specifically disallows any sort of type conversion(a.k.a coercion) in it’s comparison, where other JS comparisons do allow coercion.

The operator has some nuances. The operator is designed to lie in two cases of special values NaN and -0

NaN === NaN;            // false
0 === -0;               // true

In the case of NaN, the operator lies and says that an occurrence of NaN is not equal to another NaN. In the case of -0 (yes, this is a real, distinct value you can use intentionally in your programs), the operator lies and says it’s equal to the regular 0 value.

Since the lying about such comparisons can be bothersome. It’s best to avoid using for them

  1. For NaN comparisons, use the Number.isNan(...)
  2. For -0 use Object.is(...) as the Quadruple equals ”====”, the really really strict comparison

There are deeper historical and technical reasons for these lies, but that doesn’t change the fact that is not actually strictly exactly equality comparison, in the strictest sense

The story gets even more complicated when we consider comparison of objects

[ 1, 2, 3 ] === [ 1, 2, 3 ];    // false
{ a: 42 } === { a: 42 }         // false
(x => x * 2) === (x => x * 2)   // false

The reason they return false because, JS uses reference for comparing objects. i.e., if two objects point to same reference then they are equal or else they are not equal.

In JS all object values are held by reference, are assigned and passed by reference copy

JS doesn’t define as structural equality for object values, Instead uses reference equality for object values

Read more about equality: Types of Equality in Programming languages

In JS Objects comparison content don’t matter only reference identity

JS does not provide a mechanism for structural equality comparison of object values. To do structural equality, you will have to implement checks yourself.

But beware, it’s more complicated than you’ll assume. For example, how might you determine if two function references are “structurally equivalent”? Even stringifying to compare their source code text wouldn’t take into account things like closure. JS doesn’t provide structural equality comparison because it’s almost intractable to handle all the corner cases!

Coercive comparisons

Coercion means converting value of one type being converted to its respective representation in another type (to whatever extent possible). Coercion is a core pillar of JS

Both and do exactly the same thing, the only difference is does coercion before comparison and does not

42 == "42" // true
1 == true // true

In both comparisons, the value types are different, so the == causes the non-number values (“42” and true) to be converted to numbers (42 and 1, respectively) before the comparisons are made.

Just being aware of this nature of —that it prefers primitive numeric comparisons—helps you avoid most of the troublesome corner cases, such as staying away from a gotchas like "" 0 or 0 false.

If two values of same type are compared with and both give same results.

The confusing part happens when comparing values of different types. One cannot avoid coercive equality () because whenever < or > or <= or >= use coercive comparison.

There is no way to get these relational operators(<, >) to avoid coercive comparisons, other than never to use mismatched types in comparisons

Even the creator of language himself, Brendan Eich said that was designed as a mistake

The wiser approach is not to avoid coercive comparisons, but to embrace it’s ins and outs

How we Organize in JS

Two major patterns for organizing code (data and behavior) are used broadly across the JS ecosystem: classes and modules. These patterns are not mutually exclusive; many programs can do and use both. Other programs will just stick to one pattern or even neither

In some respects, these patterns are very different. But interestingly, in other ways they just different sides of same coin. Being proficient in JS requires understanding both patterns and where they are appropriate (and not)

Classes

A class in a program is a definition of a “type” of custom data structure that includes both data and behaviors that operate on that data.

Classes define how such data structure works, but classes are not themselves concrete values. To get a concrete value that you can use in the program, a class must be instantiated(using new keyword) one or more times.

class Page {
	constructor(text) {
		this.text = text
	}
	
	print() {
		console.log(this.text)
	}
}


class Notebook {
	constructor() {
		this.pages = []
	}
	
	addPage(text) {
		var page = new Page(text)
		this.pages.push(page)
	}
	
	print() {
		for(let page of this.pages) {
			page.print()
		}
	}
}

var mathNotes = new Notebook()
mathNotes.addPage("Arithmetic: + - * / ...");
mathNotes.addPage("Trigonometry: sin cos tan ...");

mathNotes.print();
// ..
Class Inheritance
class Publication {
	constructor(title, author, pubDate) {
		this.title = title
		this.author = author
		this.pubDate = pubDate
	}
	
	print() {
		console.log(`
            Title: ${ this.title }
            By: ${ this.author }
            ${ this.pubDate }
        `);
	}
}

This Publication defines a set of common behavior any publication can use

Now let’s consider more specific type of publication Book and BlogPost

class Book extends Publication {
	constructor(bookDetails) {
		super(
			bookDetails.title,
			bookDetails.author,
			bookDetails.publishedOn
		)
		this.publisher = bookDetails.publisher
		this.ISBN = publisher.ISBN
	}
	
	print() {
		super.print();
        console.log(`
            Publisher: ${ this.publisher }
            ISBN: ${ this.ISBN }
        `);
	}
}

class BlogPost extends Publication {
	constructor(title, author, pubDate, URL) {
		super(title, author, pubDate)
		this.URL = URL
	}
	
	print() {
		super.print()
		console.log(this.URL)
	}
}

Both Book and BlogPost uses the extends clause to extend the general definition of Publication to include additional behavior. The super(...) call in each constructor delegates to parent Publication class’s constructor for it’s initialization work, and then they do more specific things according to respective publication type (a.k.a “sub-class” or “child 👶 class”)

Now consider using these child classes:

var YDKJS = new Book({
	title: "You Don't Know JS",
    author: "Kyle Simpson",
    publishedOn: "June 2014",
    publisher: "O'Reilly",
    ISBN: "123456-789"
})

YDKJS.print();
// Title: You Don't Know JS
// By: Kyle Simpson
// June 2014
// Publisher: O'Reilly
// ISBN: 123456-789

var forAgainstLet = new BlogPost(
	"For and against let",
    "Kyle Simpson",
    "October 27, 2014",
    "https://davidwalsh.name/for-and-against-let"
)

forAgainstLet.print()
// Title: For and against let
// By: Kyle Simpson
// October 27, 2014
// https://davidwalsh.name/for-and-against-let

Notice the both the child class instances have the print() method, which was an override of an inherited print() method from the parent Publication class. Each of those child classprint() methods call super.print() to invoke the inherited version of the print() method

The fact that both the inherited and overridden methods can have the same name and co-exist is called polymorphism

Inheritance is a powerful tool for organizing data and behavior in separate logical units(classes), allowing child class to cooperate with the parent by accessing/using its behavior and data

Modules

The module pattern essentially has the same goal as the class pattern, which is to group data and behavior into logical units. Also like classes, modules can “include” or “access” the data and behavior of other modules

Classic Modules

Before ES6, there is no module system in JS, and it’s achieved without dedicated syntax

The key hallmarks of a classic modules are an outer function (that runs atleast once), which returns an “instance” of the module with one or more functions exposed that can operate on the module instance’s internal data

Because a module of this form is just a function, and calling it produces an “instance” of the module, another description for these functions is module factories

Example:

function Publication(title, author, pubDate) {
	var publicApi = {
		print() {
			console.log(`
                Title: ${ title }
                By: ${ author }
                ${ pubDate }
            `);
		}
	}
	
	return publicApi
}

function Book(bookDetails) {
	var pub = Publication(
		bookDetails.title,
		bookDetails.author,
		bookDetails.publishedOn,
	)
	
	var publicApi = {
		print() {
			pub.print()
			console.log(`
                Publisher: ${ bookDetails.publisher }
                ISBN: ${ bookDetails.ISBN }
            `);
		}
	}
	
	return publicApi
}

function BlogPost(title, author, pubDate, URL) {
	var pub = Publication(title, author, pubDate)
	
	var publicApi = {
		print() {
			pub.print()
			console.log(URL)
		}
	}
}

Comparing these forms to the class forms, there are more similarities than differences

The class form stores objects and data on an object instance, which must be accessed with the this. prefix. With modules the methods and data are accessed as identifier variables in scope, without any this. prefix

with class the “Api” of an instance is implicit in the class definition — also all methods and data are public. With the module factory function, you explicitly create and return an object with any publicly exposed methods, and any data or other unreferenced methods remain private inside the factory function.

There are other variants to this factory function form that are quite common across JS. you may run across these forms in different JS programs: AMD (Asynchronous module definition), UMD (Universal module definition), CommonJS (Classic Node.js style modules). However all of these forms rely on the same basic principle

var YDKJS = Book({
    title: "You Don't Know JS",
    author: "Kyle Simpson",
    publishedOn: "June 2014",
    publisher: "O'Reilly",
    ISBN: "123456-789"
});

YDKJS.print();
// Title: You Don't Know JS
// By: Kyle Simpson
// June 2014
// Publisher: O'Reilly
// ISBN: 123456-789

var forAgainstLet = BlogPost(
    "For and against let",
    "Kyle Simpson",
    "October 27, 2014",
    "https://davidwalsh.name/for-and-against-let"
);

forAgainstLet.print();
// Title: For and against let
// By: Kyle Simpson
// October 27, 2014
// https://davidwalsh.name/for-and-against-let

The only observable difference here is the lack of using new, calling the module factories as normal functions.

ES Modules

In ES Modules the wrapping context is a file. ESM’s are file based; one file, one module

Secondly use export to add a variable or method to it’s public API definition. If something is defined in a module and not exported then it stays hidden

Thirdly no instantiation of Es Module, you just import it to use its single instance. ESMs are, in effect, “singletons,” in that there’s only one instance ever created, at first import in your program, and all other imports just receive a reference to that same single instance. If your module needs to support multiple instantiations, you have to provide a classic module-style factory function on your ESM definition for that purpose

In our running example, we do assume multiple-instantiation, so these following snippets will mix both ESM and classic modules.

consider publication.js

function printDetails(title,author,pubDate) {
    console.log(`
        Title: ${ title }
        By: ${ author }
        ${ pubDate }
    `);
}

export function create(title,author,pubDate) {
    var publicAPI = {
        print() {
            printDetails(title,author,pubDate);
        }
    };

    return publicAPI;
}

To import and use this module, from another ES module like blogpost.js:

import { create as createPub } from "publication.js";

function printDetails(pub,URL) {
    pub.print();
    console.log(URL);
}

export function create(title,author,pubDate,URL) {
    var pub = createPub(title,author,pubDate);

    var publicAPI = {
        print() {
            printDetails(pub,URL);
        }
    };

    return publicAPI;
}

And finally, to use this module, we import into another ES module like main.js:

import { create as newBlogPost } from "blogpost.js";

var forAgainstLet = newBlogPost(
    "For and against let",
    "Kyle Simpson",
    "October 27, 2014",
    "https://davidwalsh.name/for-and-against-let"
);

forAgainstLet.print();
// Title: For and against let
// By: Kyle Simpson
// October 27, 2014
// https://davidwalsh.name/for-and-against-let

As shown, ES modules can use* classic modules* internally if they need to support multiple-instantiation. Alternatively, we could have exposed a class from our module instead of a create(..) factory function, with generally the same outcome. However, since you’re already using ESM at that point, I’d recommend sticking with classic modules instead of class.

If your module only needs a single instance, you can skip the extra layers of complexity: export its public methods directly.


tags: javascript