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
- primitive - JS Primitives
- 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
- For
NaNcomparisons, use theNumber.isNan(...) - For
-0useObject.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