Here's some random memos about JavaScript.
-
NaN
is a number (?); operators onNaN
yields intoNaN
except in the + case with a string (as below); -
IMPORTANT: if we write
(x + y)
withx
as a number andy
as a string, it yields into a string (because+
is mainly a string concatenation operator); but if+
is another math operator (eg:*
), it yields into a number; another important case of+
:1 + true
yields into2
becausetrue
(otherwisefalse
ornull
) is converted into1
(0
respectively);1 + undefined
yield intoNaN
;"1" + null
yields into"1null"
; -
beware of precision issues with floating-point numbers (because JS uses binary form to represent decimal numbers internally); try to remove the floating point (multiply by
10eX
whereX
is a positive integer) then calculate, finally then divide by the same multiplier; -
remember the ternary operator:
(condition) ? yes : no
; -
IMPORTANT (of for arrays, strings, in for objects):
for(let fruit of fruits)
should be used to iterate over an array where the index order is important; thefor(let i in fruits) /* use fruits[i] */
is optimized for iterating over object's properties;
Backquotes define template literals; you can place variables or expressions inside the string by using ${...}
:
let a = 10;
let b = 20;
let res = `The sum of ${a} and ${b} is ${a+b}.`;
arrow functions don't have its own this
:
function Person(nickname, country) {
this.nickname = nickname;
this.country = country;
this.getInfo = function() {
return () => {
alert(this.constructor.name); // Person (Window if you use a function)
alert(`Hi, I'm ${this.nickname} from ${this.country}`); // works as expected (undefined if you use a function)
};
};
}
class
example:
class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
getPerimeter() {
return 2 * (this.length + this.width);
}
}
An example:
- in
main.js
, you declare some variables and functions to export:
let greet = "Hello!";
function multiplyNumbers(a, b) {
return a * b;
}
export { greet, multiplyNumbers };
- in
app.js
, you import some variables and functions from a file:
import { greet, multiplyNumbers } from './main.js';
alert(greet);
alert(multiplyNumbers(3,5));
- in an HTML file using HTTP protocol:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><title>ES6 Module Demo</title></head>
<body><script type="module" src="{...}
A rest parameter can only be the last one (like a Common Lisp &rest
); let's see some examples:
// rest makes an array from the rest (or all parameters here)
function sortNames(...names) {
return names.sort();
}
// spread makes a list of arguments from an array
function addNumbers(a, b, c) {
return a + b + c;
}
let numbers = [5, 12, 8];
alert(addNumbers(...numbers));
let [a, b] = ["Apple", "Banana"];
let [a, ...arr] = ["Apple", "Banana", "Mango"]; //arr is assigned from the rest (with the rest operator)
let {name, age} = person; // from the person.name & person.age properties
push()
&pop()
runs faster thanunshift()
&shift()
;- unlike
slice()
&concat()
,splice()
modifies the array on which it is called on; sort()
&reverse()
modify the original array and return a reference to the same array; BEWARE:sort()
sorts as strings so numeric array elements are converted; to avoid to mess up:
numbers.sort(function(a,b) {
return a - b;
});
- `apply() is a good way to convert an array to a list of arguments to pass to a function:
const numbers = [5, 6, 2, 3, 7];
const max = Math.max.apply(null, numbers);
const min = Math.min(...numbers); // ES6 spread operator can do this too (read above)
it's the same as having written this: Math.max(5, 6, 2, 3, 7)
;
- sorting example of an array of objects:
var persons = [
{ name: "Harry", age: 14 },
{ name: "Ethan", age: 30 }
];
persons.sort(function(a,b) {
var x = a.name.toLowerCase();
var y = b.name.toLowerCase();
if (x < y) {
return -1;
}
if (x > y) {
return 1;
}
return 0;
});
let
scope:
var funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function(){
console.log("My value: " + i);
};
}
funcs[1]()
yields into a My value: 1
output. IMPORTANT: if you declare i
with var
, the global scope of i
(in the state 3
in the last i++
call) makes funcs[1]()
to return My value: 3
because of the state of i
before the invokation since the anonymous functions were bound to the same variable;
- NOTE: you can use
name = 'Guest'
as a default parameter in ES6; previously you'd need such a construct:var name = name || 'Guest';
; - REMINDER: no need to put a semicolon after the closing curly bracket in a function declaration but function expressions like
var getSum=function(a,b){return a+b;};
(which must be declared before the invokation) should always end with a semicolon; - the square bracket notation (to access object's properties) offers much more flexibility than the dot notation: it allows you to specify property names as variables instead of just string literals;
- IMPORTANT: JavaScript objects are reference types (like in Java) but the primitive values like strings and numbers are assigned or copied as a whole value;
document.querySelectorAll("ul li.tick")
: this method supports CSS pseudo-classes (like:first-child
) but not the CSS pseudo-elements (such as::before
or::first-line
; in that case, it returns an empty list);- REMINDER: a CSS property like
font-size
orborder-left-style
becomes a DOM property likefontSize
orborderLeftStyle
; window.getComputedStyle()
is useful:
var elem = document.getElementById("intro");
var styles = window.getComputedStyle(elem);
alert(styles.getPropertyValue("font-size"));
elem.classList.add("newClass")
is very useful; you can callremove()
,toggle()
(switch betweenadd()
andremove()
) orcontains()
(the last one returns a boolean);- example of element creation:
var newDiv = document.createElement("div");
var newContent = document.createTextNode("Hi, how are you doing?");
newDiv.appendChild(newContent);
var existingDiv = document.getElementById("main");
document.body.insertBefore(newDiv, existingDiv); //at the beginning
document.body.appendChild(newDiv, existingDiv); //at the end
insertAdjacentHTML()
inserts HTML into the document without replacing the existing contents (innerHTML
replaces all existing content); it uses"beforebegin"
,"afterbegin"
,"beforeend"
or"afterend"
as first argument;- REMINDER: remember these snippets:
childElem.parentElement.removeChild(childElem);
& `childElem.parentNode.replaceChild(newChildElem, childElem)``; nodeName
as indocument.getElementById("main").firstElementChild.nodeName
is read-only;if (main.hasChildNodes())
then usechildren
instead ofchildNodes
(like you usefirstElementChild
,previousElementSibling
orparentElement
instead offirstChild
,previousSibling
orparentNode
) to get a collection of elements (instead of all the nodes);- use the
nodeType
property.
window.screen
;window.location.href
;window.addEventListener("online", function(){alert("Welcome back!");})
;navigator.cookieEnabled
.
- use
(typeof something)
; Number(str)
,String(null)
,Boolean(NaN)
;Boolean("0")
yields intotrue
; BEWARE: in PHP, string"0"
isfalse
but a non-empty string in JS is always true.
- Bubbling from the lowest level to the higher parent is the way to go in modern browsers (Capturing is rarely used, often unsupported and only works with event handlers registered with the
addeventlistener()
method with the third argument is set totrue
); event.target
is the target element that generates the event;this
represents the current element so the element tht has a currently running handler attached to it (thus, not the same asevent.target
);- you can stop the propagation (or prevent the default one to happen with
event.preventDefault()
):
function showAlert(event){
alert("You clicked: " + this.tagName);
event.stopPropagation(); // or event.stopImmediatePropagation() to enforce the stop
}
var elems = document.querySelectorAll("div, p, a");
for (let elem of elems) {
elem.addEventListener("click", showAlert);
}
You can use call()
to borrow a method from the caller (IMPORTANT: apply(thisObj. [argsArray])
is like call(thisObj, arg1, arg2, ...)
but with an array of arguments as a second parameter):
var A = {
name: "object A";
say: function(greet) {
alert(greet + ", " + this.name);
}
}
var B = {
name: "object B";
}
A.say.call(B, "Hello");
Array.reduce()
is very useful; it's a functional trick; the first argument will be the accumulator and the second argument is the current value in the array;
var numbers = [2, 5, 6, 4, 3, 7];
var max = numbers.reduce(function(acc, i) {
return Math.max(acc, i);
});
Like in Lisp, it's an inner function that has access to the parent function's scope, even after the parent function has finished executing.
function makeCounter() {
var counter = 0;
function make() {
counter += 1;
return counter;
}
return make;
}
var myCounter = makeCounter();
console.log(myCounter()); // the makeCounter() has already finished executing,
// but the closure internally stores references to
// their outer variables, and can access and update
// their values
/** as an anonymous function expression */
var myCounter = (function() {
var counter = 0;
return function() {
counter += 1;
return counter;
}
})(); // don't forget the last () to invoke the outer lambda
/** getter/setter examples */
var getValue, setValue;
(function() {
var secret = 0;
getValue = function() {
return secret;
};
setValue = function(x) {
if (typeof(x) === "number") {
secret = x;
}
};
}());
Enable strict mode: "use strict";
restrictions:
- undeclared variables;
- deleting a variable;
- duplicating a parameter name;
- writing a read-only property as in:
Object.defineProperty(aPersonObject, "gender", {value: "male", writable: false});
will throw an error; - adding a new property to a non-extensible object set with
Object.freeze(myObject)
(REMINDER: check the extensibility withObject.isExtensible(myObject)
); eval
cannot alter scope (neither declaration/modification of variables, nor definition of function);eval
&arguments
are treated like keywords;await
,implements
,interface
,package
,private
,protected
,public
andstatic
are reserved for future (post ES6) and aren't allowed in strict mode;- the unrecommended
with
statement is forbidden; - octal numbers.
- syntax rules:
{}
for object,[]
for array,number
,string
(must be enclosed in"
),true
,false
&null
; - example:
var json = `{
"book": {
"name": "Harry Potter and the Goblet of Fire",
"author": "J. K. Rowling",
"year": 2000,
"characters": ["Harry", "Hermione", "Ron" ],
"price": {
"paperback": "$10.40",
"hardcover": "$20.32",
"kindle": "$4.11"
}
}
}`;
var obj = JSON.parse(json);
function printValues(obj) {
for (var k in obj) {
if ((obj[k] instanceof Object)) {
printValues(obj[k]);
} else {
document.write(obj[k] + "<br>");
};
}
};
var sameJson = JSON.stringify(obj);
var num = prompt("Enter a positive integer between 0 & 100");
var start = Date.now();
try {
if (num > 0 && num <= 100) {
alert(Math.pow(num,num));
} else {
throw new Error("An invalid value is entered);
}
} catch(e) {
alert(e.message);
} finally {
alert("Execution took: " + (Date.now() - start) + "ms");
}
- syntax:
var literalRegex = /^Mr\./;
var regex = new RegExp("^Mr\\."); // BEWARE: you need to double-escape
// in the constructor syntax
test()
(andexec()
) is a RegExp method;search()
,replace()
,match()
&split()
are String methods.
- creation:
document.cookie = "name=" + encodeURIComponent("Christopher Columbus") + "; max-age=" + 30*24*60*60 + "; path=/; domain=hondana.net; secure";
- reading:
function getCookie(name) {
var cookieArr = document.cookie.split(";");
for (var cookie of cookieArr) {
var cookiePair = cookie.split("=");
if (name == cookiePair[0].trim()) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
}
Published by Martial BONIOU (2021-03-16)