How JavaScript works?
JavaScript is a single-threaded language that can be non-blocking.
JavaScript Engine
For the code below:
const f()=>{
const ff()=>{
console.log('a');
}
}
f();
f()
, ff()
and console.log('a')
will be push in the call stack one by one and be popped out in the reverse order (first in last out).
- Single threaded means the JavaScript Engine has only one call stack. (simple, multithreaded environment is complicated and has many issues like 'deadlock' to deal with.)
-
Synchronous programming means the program gets executed one line after another in order. If we have one function that takes a lot of time, the whole line will be held up.
When we need to do things like image processing or making requests over the network like API calls, we should do asynchronous programming.
JavaScript Run-time environment
We can do asynchronous programming with setTimeout()
, for example:
console.log('1');
setTimeout(() => {
console.log('2');
}, 2000);
console.log('3');
//>> 1, 3, 2
-
console.log('1')
is pushed in the call stack and executed, then popped out; -
setTimeout()
was pushed in the call stack. Bc it is not part of JavaScript but part of Web APIs, it triggers the Web APIs, then is popped out of the call stack. And the Web APIs starts a timer of 2 seconds. - During the 2 seconds, as the call stack is empty,
console.log('3')
is pushed in, executed and pushed out. - After 2 seconds, the Web APIs adds a callback of
setTimeout
to the callback queue, ready to rune the content inside ofsetTimeout()
. - The event loop keeps checking the callback stack and only if the call stack is empty, it will push the first thing in the queue to the stack and do some work. So the callback is removed from the callback queue and added in the call stack, and by running it we add
console.log('2')
in the call stack, when it is done, the callback is popped out. Now everything is empty.
⚠️ if the time in setTimeout()
is set to 0, the output order is still the same, bc only if the call stack is empty, it will push the first thing in the queue to the stack and do some work.
⚠️ JavaScript is synchronous in the sense that it is single-threaded but can have asynchronous code by using the event queue on things like AJAX requests.
S
Data structures
Data types
The latest ECMAScript standard defines seven data types (built-in types):
-
Six primitive data types:
- Boolean
- Null
- Undefine
- Number
- String
- Symbol (new in ECMAScript6)
- Object
Array and Function are two specialized version of the Object type. Use typeof
operator to figure out the type of the value that held by a variable (only values have types, variables are only containers):
var a;
typeof a; // >>"undefined"
var b = null;
typeof b; // >>"object"
var c = undefined;
typeof c;// >>"undefined"
typeof function() {} === 'function'; // >>"True"
var arr = [1, 2, 3];
typeof arr; // >>"object"
// use Array.isArray or Object.prototype.toString.call
// to differentiate regular objects from arrays
⚠️ typeof null
returns Object
instead of null
is a long-standing bug in JS, but one that is likely never going to be fixed. Too much code on the Web relies on the bug and thus fixing it would cause a lot more bugs!
⚠️ The typeof
of arrays is object.
⚠️ A variable value can be undefined
by 1. explicitly assignment, 2. declared with no assignment, 3. function return with no value, 4. usage of void
operator
Coercion
Truthy & Falsy
A non-boolean value can always be coerced to a boolean value. The specific list of "falsy" values are as follows:
- empty string: ""
- 0, -0
- invalid number: NaN
- null, undefined
- false
Equality & Inequality
There are four equality operators: ==, ===, !=, and !==.
Overall, the difference between == and === is that == checks the equality with coercion allowed, and === checks the equality without coercion allowd ("strictly equality").
The Strict Equality Comparison Algorithm (x === y)
check Ecma-262 Edition 5.1 Language Specification here
- if: typeof x differs from typeof y, return
false
- elif: typeof x is 'undefine' or 'null', return
true
, else: -
elif: typeof x is 'number'
- if: x or y is NaN, return
false
(⚠️NaN === nothing) - elif: x is the same number value as y, return
true
- elif: x is +0, y is -0 and vice versa, return
true
- else: return
false
- if: x or y is NaN, return
-
elif: typeof x is 'string'
- if: x and y are exactly the same sequence of characters (same length and same characters in corresponding positions), return
true
- else: return
false
- if: x and y are exactly the same sequence of characters (same length and same characters in corresponding positions), return
-
elif: typeof x is 'boolean'
- if: x and y are both 'true' or 'false', return
true
- else: return
false
- if: x and y are both 'true' or 'false', return
-
elif: typeof x is 'object'
- if: x and y both refer to the same object, return
true
- else: retrun
false
- if: x and y both refer to the same object, return
- else: pass
The Abstract Equality Comparison Algorithm (x == y)
- if: typeof x is the same as typeof y, return the same result as x === y
- elif: typeof x is undefined and typeof y is null, and vice versa, return
true
- elif: typeof x is 'string' and typeof y is 'number', return the result of
ToNumber(x) == y
, and vice versa - elif: typeof x is 'boolean', return the result of
ToNumber(x) == y
, and vice versa ⁉️ - elif: typeof x is 'object', typeof y is 'string' / 'number', return the result of
ToPremitive(x) == y
, and vice versa ⁉️ - else: return
false
Sameness comparison of == and ===
Relational Comparison ??
Relational Comparison operators incluede <, >, <=, and >=.
var a = 32;
var b = "31";
var c = "c";
var d = "111";
a > b; // >>true
c > b; // >>true
d > b; // >>false
a > c; // >>false
a < c; // >>false
a == c; // >>false
As for x > y, concluded from the cases above:
- if: both of the operands are 'string's, the comparison is made lexicographically (aka alphabetically like a dictionary)
-
if: one of the operands is 'number' and the other one is 'string':
- if: the 'string' can be coerced to be 'number', a typical numeric comparison occurs
- else: the 'string' is coerced to NaN, none of the relations (>, <, >=, <=, ==, ===) are valid.
Arrays
The arrays in JS is similar with those in python with use square brackets []. Some predefined methods are listed below:
var li = [1, 2, 3, 4, 5];
var empty_li = [];
//shift()
var first_of_li = li.shift(); //removes the first element and returns that element
console.log(li) //>> [2, 3, 4, 5]
//pop()
var last_of_li = li.pop(); //removes the last element and returns that element
console.log(li) //>> [2, 3, 4]
//push()
var new_length = li.push(6, 7, 8); //adds items to the end and returns the new lenght of that array
console.log(li) //>> [2, 3, 4, 6, 7, 8]
//concat()
var merged_li = li.concat([9, 10]);//concatenate two arrays without changging them
//sort()
//sorts the elements of an array in place and returns the array. The default sort order is according to string Unicode code points.
var months = ['March', 'Jan', 'Feb', 'Dec'];
months.sort();
console.log(months) //>> ["Dec", "Feb", "Jan", "March"]
var array1 = [1, 30, 4, 21];
array1.sort();
console.log(array1) //>> [1, 21, 30, 4]
//trim()
//removes whitespace from both ends of a string. Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.).
var greeting = ' Hello world! ';
console.log(greeting.trim()); //>> "Hello world!";
//join(separator)
//creates and returns a new string by concatenating all of the elements in an array (or an array-like object), separated by commas or a specified separator string.
//The separator is converted to a string if necessary. If omitted, the array elements are separated with a comma (","). If the separator is an empty string, all elements are joined without any characters in between them.
//If an element is undefined or null, it is converted to the empty string.
var a = ['Wind', 'Rain', 'Fire'];
a.join(); // 'Wind,Rain,Fire'
a.join(', '); // 'Wind, Rain, Fire'
a.join(' + '); // 'Wind + Rain + Fire'
a.join(''); // 'WindRainFire'
//The following example joins array-like object (arguments), by calling Function.prototype.call on Array.prototype.join.
function f(a, b, c) {
var s = Array.prototype.join.call(arguments);
console.log(s);
}
f(1, 'a', true); //>> "1,a,true"
As for looping, instead of using forEach
which simply loop an array but does nothing to its elements, map
, filter
and reduce
are way more convenient.
map
It iterates elements and applying the operations to each of them, in the expectation that the operations return elements. e.g.:
/* double the elements in array */
var array = [5, 2, 10, 26];
var mapArray = array.map(ele => ele * 2);//shortform of 'array.map(ele => {return ele*2;})' coz there is only a single line
//using forEach
var double = [];
array.forEach(ele => {double.push(ele * 2);});
ATTENTION: usaully 'map()' function will not change the original array bc the first argument stores the value of the current element; however, when the elements are objects, the first argument stores its address, so the original array will be changed. e.g: in following codes, array
's value will changed into map_Array
after using the map() function.
const array = [{
username: "john",
team: "red",
score: 5,
items: ["ball", "book", "pen"]
},
{
username: "becky",
team: "blue",
score: 10,
items: ["tape", "backpack", "pen"]
},
{
username: "susy",
team: "red",
score: 55,
items: ["ball", "eraser", "pen"]
},
{
username: "tyson",
team: "green",
score: 1,
items: ["book", "pen"]
},
];
//create a new list with all user information, but add "!" to the end of each items they own.
var mapArray = array.map(t => {t.items = t.items.map(itt => itt+"!"); return t;})
filter
As its name suggests, it filters elements with conditions. As with map
, it has to return something.
/* find the elements > 5 in an array */
var array = [5, 2, 10, 26];
var filterArray = array.filter(ele => ele > 5);//shortform of 'array.filter(ele => {return ele > 5;})'
reduce
It is another powerful function which you can even use to do 'map' and 'filter'. It use an 'accumulator' to store the results caused in the body of the function.
/* sum all the elements of an array */
var array = [5, 2, 10, 26];
var reduceArray = array.reduce((accumulator, ele) => ele + accumulator, 0);//shortform for 'array.reduce((accumulator, ele) => {return ele + accumulator;}, 0)
Objects
The objects in JS are declared similarly with dicts in Python, and other than using '[]' to query a property, '.' is also applicable. However, when using '[]', the expressing within should be a string. e.g.:
var empty_user = {};
var user = {
name: "John",//property name can be unquoted if there is no space between words
age: 34,
isMarried: False
spells: ["blabla", "blablabla"],
//a function inside an object is a method
shout: function(){
console.log("aaaaaaa")//no comma
};
var user_age = user["age"];
var user_name = user.name;
-
Add a property:
Adding a property is just like query a propety using '[]' or '.':user.sex = "male"; user["hobby"] = "soccer";
- Empty object and null object:
We can add properties to an empty object but we cannot set property name of a null object.
Reference type
Different from the primitive types defined by the programming language, objects are of reference type. Arrays are also objects.
Context
'Context' is an object-based concept whereas 'scope' is function based.
'Context' refers to an object. Within an object, the keyword “this” refers to that object (i.e. “self”), and provides an interface to the properties and methods that are members of that object. When a function is executed, the keyword “this” refers to the object that the function is executed in.
'Scope' pertains to the variable access of a function when it is invoked and is unique to each invocation
An example:
From the example above, we know that 'this' in the JavaScript refers to the window, so that console.log()
and a()
are both its methods and can be called with 'window.' or 'this.'.
obj.a()
refers to obj
.
Instantiation
The instantiation of an object is the creation of a real instance of an object and reuse of codes. Before the instantiation, we shall create a class with a constructor which defines the template. As the code shows below, the constructor allow us to customize proprieties 'name' and 'type' every time we create a Player object like what a consturctor does in C++.
class Player{
constructor(name, type){
this.name = name;
this.type = type;
}
introduce(){
console.log(`Hi, I'm ${this.name}, I'm a ${this.type}`) //syntax template string with ``
}
}
To create a Wizard class which includes all the proprietyies and methods that Player has (a child of Player):
class Wizard extends Player{
constructor(name, type){
super(name, type); //call functions on the parent
}
play(){console.log(`I'm a wizard of ${this.type}`);}
}
Now we can do instantiation with the key word new.
To get a better interpretation of context, lets run the code below:
class Player{
constructor(name, type){
console.log('player',this);
this.name = name;
this.type = type;
console.log('player',this);
}
introduce(){
console.log(`Hi, I'm ${this.name}, I'm a ${this.type}`) //syntax template string with ``
}
}
class Wizard extends Player{
constructor(name, type){
super(name, type); //When used in a constructor, the super keyword appears alone and must be used before the this keyword is used
console.log('wizard', this);
}
play(){console.log(`I'm a wizard of ${this.type}`);}
}
var player = new Player("Dali", "painter");
var wizard = new Wizard("Elio", "blackMagic");
Identifier names
In JavaScript, variable names (including function names) must be valid identifiers.
- Unicode: The strict and complete rules for valid characters in identifiers are a little complex (omitted)
- ASCII: An identifier must start with a- z, A - Z, $, or _. It can then contain any of those characters plus the numerals 0 - 9.
certain words cannot be used as variables, but are OK as property names. These words are called "reserved words," and include the JS keywords ( for, in, if, etc.) as well as null, true, and false.
Modules
As JavaScript does not have a module system, we have lots of ways to import and export modules:
Inline scripts -> Script tags -> IFFE -> ES6 + Webpack2
-
Inline scripts:
- lack of reusability
- pollution of the global namespace
(the global namespace is the window object with all the names. When working with members with pieces of code, it is easy to go off the same names.)
-
Script tags:
- lack of reusability
- lack of dependency resolutions
(the scripts should be added in the proper order) - pollution of the global namespace
(all the functions and variables declared in each of the files will be on the window object)
-
IIFE (Immediately-Invoked Function Expressions):
- lack of reusability
(still, we use script tags) - lack of dependency resolutions
- pollution of the global namespace
(still, we expose one object to the global name scope)
- lack of reusability
- Browserify (+ common JS)
- ES6 + Webpack2
IIFE
Pronounced as "Iffy", the key to IIFE pattern is taking a function and turning it into an expression and executing it immediately.
- The simple IIFE style (with no return):
!function() { alert("Hello from IIFE!"); }();// there is () // ! can be replaced by basically any unary operators such as +, -, ~ // ! can be replaced by "void" // those unary operators and "void" forces JavaScript to treat whatever coming after ! as expression
-
The classical IIFE style:
// Variation 1 (recommanded) (function() { alert("I am an IIFE!"); }()); // Variation 2 (function() { alert("I am an IIFE, too!"); })(); // These tow stylistic variations differ slightly on how they work. // IIFE with parameters (function IIFE(msg, times) { for (var i = 1; i <= times; i++) { console.log(msg); } }("Hello!", 5)); // IIFE with returns var result = (function() { return "From IIFE"; }()); alert(result); // alerts "From IIFE"
-
Cases where the parentheses can be omitted (not recommended)
// Parentheses around the function expression basically force the function to become an expression instead of a statement. // But when it’s obvious to the JavaScript engine that it’s a function expression, we don’t technically need those surrounding parentheses as shown below. var result = function() { return "From IIFE!"; }(); // In the above example, function keyword isn’t the first word in the statement. So JavaScript doesn’t treat this as a function statement/definition.
Any variables and functions declared inside the IIFE are not visible to the outside world. e.g.:
(function IIFE_initGame() {
// Private variables that no one has access to outside this IIFE
var lives;
var weapons;
init();
// Private function that no one has access to outside this IIFE
function init() {
lives = 5;
weapons = 10;
}
}());
In this way, we can add variables and functions to a global object ((the only one exposed to the global scope) inside an IIFE:
var myApp = {};
(function(){
myApp.add = function(a, b) {
return a + b;
}
})();
So the advantages of using IIFE includes:
- Wrapping the variables and functions in an IIFE as private can avoid polluting the global environment, and prevent them from being changed by others.
-
Reduce the look-up scope. Without using IIFE, global objects such as
window
,document
andjQuery
in the scope defined by the function have to look up their properties from inside to outside. IIFE can pass them inside of the function directly.(function($, global, document) { // use $ for jQuery, global for window }(jQuery, window, document));
- Minification optimization, avoid name conflict. Since we can paas values to the arguments in IIFE, we areable to reduce the name of each global object to a one letter word. Known that "$" stands for identifier jQuery in jQuery, but used in other ways in other JavaScript libraries, so confict might happens when using jQuery and other JS libraries. This method is especially useful for writing jQuery third-party library plugins.
- Manage Browser Memory. If the functions are such that they require single execution, we need not to add those function and variable to the gobal scope. Adding them to global scope would consume space on the Browser Memory. When the IIFE is executed, the required functions and variables are created, used and once IIFE has finished execution, they are available for Garbage Collection, Hence freeing the Browser Memory.
An Example of module pattern using IIFE and closure:
var Sequence = (function sequenceIIFE() {
// Private variable to store current counter value.
var current = 0;
// Object that's returned from the IIFE.
return {
getCurrentValue: function() {
return current;
},
getNextValue: function() {
current = current + 1;
return current;
}
};
}());
console.log(Sequence.getNextValue()); // 1
console.log(Sequence.getNextValue()); // 2
console.log(Sequence.getCurrentValue()); // 2
Where IIFE makes the variable 'current' private and the closure makes it accessble.
Browserify + Common JS
// js1 "./add"
module.exports = function add(a, b){
return a+b;
}
// js2
var add = require("./add");
Browserify is a module bundler that runs before putting the website online, it bundles all JS files into one massive file. The common JS syntax instructs it to do it automatically.
ES6 + Webpack2
// js1 "./add"
export const add = (a, b) => a + b;
// or (set the function as default function)
export default function add(a, b){
return a + b;
}
// js2
import {add} from './add'; // destructuring
// or (no need to destructuring since we import hte default function)
import add from './add';
Webpack is a bundler like Browserify which allows us using ES6 and bundle the files into one or multiple filesbased on your needs.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。