Tips and Tricks

in Web App Development

ECMAScript 262-3

Global Variable

// Assuming following codes run in global context

var a = 0;     // "a" is a global variable here

function test() {
  b = 10;      // QUESTION: Is "b" a "global variable"?

  for (var i = 0; i < 10; i++) {
    a++;       // global variable is visible in inner scopes
  }

  return i;    // == 10 because "i" is scoped under "test()", not "for"
}
 Variable is defined via and only via var keyword, therefore, "b" is even not a variable, it's a property of global object ...
var a = 10;    // variable a = 10
b = 10;        // this === window here, so window.b = 10;

delete a;      // return false, a = 10
delete b;      // return true, window.b is undefined
 An object property can be deleted, while a variable cannot ...

Implicit Type Conversion

String Number Boolean Object
undefined "undefined" NaN false Error
null "null" 0 false Error
Nonempty string Numeric value or Nan true String 
Empty string 0 false String
0 "0" false Number
NaN "NaN" false Number
Infinity "Infinity" true Number
Negative infinity "-Infinity true Number
number string value true Number
true "true" 1 Boolean
false "false" 0 Boolean

Number Conversion

// The quickest way to put variable into number context

+"" === +false === +null === 0
+"a" === +undefined === NaN

+"1" === +"0b1" === +true === 1
+"-1" === -1
+"1.1" === 1.1

The most straightforward approach is via implicit type conversion:

Some other alternatives could be:

// Convert using Number object constructor
Number("") === 0
Number("1.1") === 1.1
Number(true) === 1

// Convert using "parseInt" and "parseFloat" functions
parseInt("1.1") === 1
parseFloat("1.1") === 1.1

parseInt("") === parseFloat("") === NaN
parseInt("a") === parseFloat("a") === NaN

parseInt

parseInt('10');           // 1
parseInt('0.10');         // 0
parseInt('-10');          // -1

parseInt(str, radix): returns the parsed number, or NaN if "str" does NOT begin with a valid integer. 

 parses and returns the first number (with an optional leading minus sign) that occurs in “str”. Parsing stops, and the value is returned, when parseInt encounters an invalid digit for the specified radix.

 

 

 When no radix is specified, ECMAScript v3 allows an implementation to parse a string that begins with 0 (but not 0x or 0X) as an octal or as a decimal number.  

parseInt('bug1');         // NaN
parseInt('bug1', 16);     // 11
parseInt('0x10');         // 16
parseInt('010');          // 10???

Big Integer

const bigint = '78099864177253771992779766288266836166272662';

const result = parseInt(bigint);
// output: 7.809986417725377e+43

result.toString(10);
// "7.809986417725377e+43"

The Number.MAX_SAFE_INTEGER constant represents the maximum safe integer in JavaScript (2 ^ 53 - 1 = 9007199254740991).

const bigint = '78099864177253771992779766288266836166272662';

let result = BigInt(bigint);
// output: 78099864177253771992779766288266836166272662n

result.toString(10);
// "78099864177253771992779766288266836166272662"

BigInt is now a native JavaScript language feature. It's at Stage 3 in the TC39 process and it's shipping in V8 v6.7 and Chrome 67.

May simply turn to npm to find your favorite polyfill lib to deal with.

String Concatenation

Use "+" operator for string concat was a hazard to be aware of ...

... creates performance issues in IE when used within an iteration. This is because, like Java and C#, JavaScript uses unmutable strings.

 

Actually, we have many other alternatives to join two or several strings:

var foo = "three";

// String.prototype.join
var bar = ["first", 2, foo].join('');

// String.prototype.concat
var baz = "first".concat(2).concat(foo);

// ES6 string template
var boo = `first2${foo}`; 

String Replacement

How to convert "uissee compassss" to "uisee compass"?

"uissee compassss".replace('ss', 's');    // uisee compassss

 If the first argument to String.prototype.replace( regex, replacement) is a string rather than a regular expression, the method searches for that string literally rather than converting it to a regular expression with the RegExp constructor.

"uissee compassss".replace(/ss/, 's');    // uisee compassss

 If regex has the global "g" attribute specified, replace( ) replaces all matching substrings. Otherwise, it replaces only the first matching substring.

"uissee compassss".replace(/ss/g, 's');    // uisee compass

Empty the Array

There are many approaches to empty the array, some look naive:

var arr1 = [1, 2, 3, 4, 5, 6];
var arr2 = arr;

// Method 1: assign a new empty array
arr1 = [];                            // arr2: [1, 2, 3, 4, 5, 6]


// Method 2: remove all items one by one
while(arr1.length > 0) arr1.pop();    // === arr1.shift(), arr2: []

And some will be tricky:

// Method 3: remove all and add nothing
arr1.splice(0, arr1.length);          // return all deleted, arr2: []

// May be the most tricky approach
arr1.length = 0;                      // arr2: []

Array Sort

Q1. How to get numeric order of 33, 4, 1111, 222?

[33, 4, 1111, 222].sort();    // [1111, 222, 33, 4]

If Array.prototype.sort(sortfunc) is called with no arguments, the elements of the array are arranged in alphabetical order (more precisely, the order determined by the character encoding)..

function numorder(a, b) { return a - b; }
[33, 4, 1111, 222].sort(numorder);        

Q2. How to get pinyin order of 一、二、三、四?

['一', '二', '三', '四'].sort();           // ["一", "三", "二", "四"]
function locale(a, b) { return a.localeCompare(b); }
['一', '二', '三', '四'].sort(locale);     // ["一", "三", "二", "四"]
function locale(a, b) { return a.localeCompare(b, 'zh'); }
['一', '二', '三', '四'].sort(locale);     // ["二", "三", "四", "一"]

Array Sort (cont.)

The sort() method sorts the elements of an array in place and returns the array. The sort is not necessarily stable

var items = [
  { name: 'a', value: 6 },
  { name: 'b', value: 6 },
  { name: 'c', value: 6 },
  { name: 'd', value: 3 },
  { name: 'e', value: 2 },
  { name: 'f', value: 1 },
];

// array is mutated in-place
items.sort(function (a, b) {
  return a.value - b.value;
});

//items = [
//  { name: 'd', value: 1 },
//  { name: 'e', value: 2 },
//  { name: 'f', value: 3 },
//  { name: 'a', value: 6 },
//  { name: 'b', value: 6 },
//  { name: 'c', value: 6 },
//];   
var items = [
  { name: 'a', value: 6 },
  { name: 'b', value: 6 },
  { name: 'c', value: 6 },
  { name: 'd', value: 3 },
  { name: 'e', value: 2 },
  { name: 'f', value: 1 },
];
var dup = items.concat(items);

// same items may not keep same seqenuce
dup.sort(function (a, b) {
  return a.value - b.value;
});

//dup = [
//  ...,
//  { name: 'b', value: 6 },
//  { name: 'b', value: 6 },
//  { name: 'c', value: 6 },
//  { name: 'c', value: 6 },
//  { name: 'a', value: 6 },
//  { name: 'a', value: 6 },
//];   

Timers

Timers (cont.)

  • JavaScript engines only have a single thread, forcing asynchronous events to queue waiting for execution. 

  • One useful trick with setTimeout( ) is to register a function to be invoked after a delay of 0 milliseconds.
    • The code isn't invoked right away but is run "as soon as possible." In practice, setTimeout( ) tells the browser to invoke the function when it has finished running the event handlers for any currently pending events and has finished updating the current state of the document.
    • Event handlers that query or modify document content must sometimes use this trick to defer execution of their code until the document is in a stable state.

ECMAScript 5

Strict Mode

Element Restriction Example
Variable Using a variable without declaring it. testvar = 4;
Octals Assigning an octal value to a numeric literal, or attempting to use an escape on an octal value. var testoctal = 010;
var testescape = \010;
this The value of this is not converted to the global object when it is null or undefined. function testFunc() {
  return this;
}
var testvar = testFunc();


In non-strict mode, the value of testvaris the global object, but in strict mode the value is undefined.
arguments in a function You cannot change the values of members of the local arguments object. function testArgs(oneArg) {
  arguments[0] = 20;
}


In non-strict mode, you can change the value of the oneArg parameter by changing the value of arguments[0], so that the value of both oneArg and arguments[0] is 20.  

Bound Function

 A common mistake for new JavaScript programmers is to extract a method from an object, then to later call that function and expect it to use the original object as its this (e.g. by using that method in callback-based code). 

this.x = 9;    // this refers to global "window" object here in the browser
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var retrieveX = module.getX;
retrieveX();   
// returns 9 - The function gets invoked at the global scope

// Create a new function with 'this' bound to module
// New programmers might confuse the global var x with module's property x
var boundGetX = retrieveX.bind(module);
boundGetX();  // 81

The bind() method creates a new function that, when called, has its this keyword set to the provided value, following with a given sequence of arguments ...

ECMAScript 6

"let" and "const"

 Block-scoped binding constructs. let is the new var. const is single-assignment. Static restrictions prevent use before assignment. 

function f() {
  {
    let x;
    {
      // okay, block scoped name
      const x = "sneaky";
      // error, const
      x = "foo";
    }
    // error, already declared in block
    let x = "inner";
  }
}

Special notice goes to const object which is open for any property manipulation: 

const obj = { a: 1 };            // DO NOT use "let" here 

obj.b = 2;                       // { a: 1, b: 2 }
delete obj.b;                    // { a: 1 }
Object.assign(obj, { b: 2 });    // { a: 1, b: 2 }

Enhanced Object Literals

const obj = {
  // __proto__
  __proto__: theProtoObj,
  
  // Shorthand for ‘handler: handler’
  handler,

  // Getter and setter                                
  _id: 1,                                           // obj._id === 1
  get id() { return "id" + this._id; }              // obj.id === "id1"
  set id(v) { this._id = v + 1; }                   // obj.id = 1; (_id: 3)
  
  // Prototype methods
  toString() { return "d " + super.toString(); },   // call "super"
  foo() { return this.id + this.toString(); },      // call "this" 
    
  // Computed (dynamic) property names
  [ 'prop_' + (() => 42)() ]: 42
};

The most tricky part is always the "this" reference:

const obj = {
  baz: function() { return this.id; }               // same as baz() {}
  bar: () => this.id;   // "bar" is NOT proto method, "this" is global object 
};

Symbols

var obj = {};
var sym = Symbol('a');

obj[sym] = 'a';                           // obj[Symbol('a')] === undefined
obj[sym] = 'b';                           // obj[sym] === 'b'
obj[Symbol.for('b')] = 'b';

obj['c'] = 'c';                           // object['c'] === 'c'
obj.d = 'd';

for (var i in obj) { console.log(i); }    // logs "c" and "d
  • Symbols are a new primitive type
  • Symbols allow properties to be keyed by either string or symbol.
  • Symbols are very useful to hold private property for object literal.
Symbol('foo') === Symbol('foo');              // false

JSON.stringify({[Symbol('foo')]: 'foo'});     // '{}'

typeof Symbol() === 'symbol'
typeof Symbol('foo') === 'symbol'
typeof Symbol.iterator === 'symbol'
typeof Object(Symbol('foo')) === 'object'

Deconstructing

// list matching
const [a, , c] = [1, 2, 3];                      // a = 1, c = 3

// object matching
const node = { op: 1, lhs: { op: 2 }, rhs: 3 };

const { op, lhs, rhs } = node;                   // op = 1, lhs = { op: 2 }, rhs = 3  

// object matching with new variable assigned
const { op: a, lhs: { op: b }, rhs: c } = node;  // a = 1, b = 2, c = 3
                                                 // op === lhs === rhs === undefined 

// Can be used in parameter position
const g = ({ name }) => console.log(name);       // expected: 5
g({ name: 5 });                                  

// Fail-soft destructuring
const [a] = [];                                  // a === undefined;

// Fail-soft destructuring with defaults
let [a = 1] = [];                                // a === 1;
let { a = 1, b = 2 } = {};                       // a === 1, b === 2
// Fail defaults ONLY effect for "undefined" item
const [a = 1] = [null];                          // a === null
const { a = 1, b = 2 } = { a: null };            // a === null, b === 2

Rest and Spread

// Rest of array: r = [2, 3]
const [a, ...r] = [1, 2, 3]    







// Rest of object: r = { b: 2, c: 3 }
const { a, ...r } = { a: 1, b: 2, c: 3}





// Rest of function arguments
function f(x, ...y) {
  // y is an array: ["hello", true]
  return x * y.length;  
}

f(3, "hello", true) == 6
// Spread into array: arr = [1, 2, 3]
const arr = [1, ...r];    

// ... effects as copying an array 
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
arr2.push(4);        // arr2: [1, 2, 3, 4], 
                     // arr: [1, 2, 3]

// Spread into object: { a: 1, b: 2, c: 3 }
// (Object spread comes in ES2016)
const obj = { a: 1, ...r };

// ... will overwrite the previous prop
const obj = { a: 1, b: -1, ...r };

// Spead an array as the argument
function f(x, y, z) {
  // as [x, y, z] = [1, 2, 3]
  return x + y + z;
}

f(...[1, 2, 3]) == 6

Modules

// file: lib/math.js
// export named variable
export const pi = 3.141593;
export const subtract = (x, y) => x - y;

// export named function declaration
export function sum(x, y) { return x + y; };

// exports a function declared earlier
const divide = (x, y) => x / y;
const multiply = (x, y) => x * y;

export { divide, multiply }; 

// using default export
export default function cube(x) { return x * x * x; };


// file: lib/mathex.js
// module redirects
export { default, sum as add, divide } from './math';
export * from './min-max';    
// file: foo.js
import {
  pi,
  subtract,


  sum,


  divide,
  multiply,



},
AnyName from 'lib/math';



import AnyName, {
  add, divide, min, max
} from 'lib/mathex';
export { cube: x => x * x * x };     // should export as variable or default
export sum from 'lib/math';          // still an ECMAScript proposal
export * as math from 'lib/math';    // still an ECMAScript proposal

Promises

  • Callbacks will never be called before the completion of the current run of the JavaScript event loop.
  • Callbacks added with .then even after the success or failure of the asynchronous operation will be called, as above.
  • Multiple callbacks may be added by calling .then several times, to be executed independently in insertion order.
  • But the most immediate benefit of promises is chaining.

Promises (cont.)

  • macro-task (宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task (微任务):process.nextTick, promises

Odds and Ends

0b111110111 === 503            // binary literal
0o767 === 503                  // octal literal

Number.EPSILON
Number.isInteger(Infinity)     // false
Number.isNaN("NaN")            // false

Math.acosh(3)                  // 1.762747174039086
Math.hypot(3, 4)               // 3 ^ 2 + 4 ^ 2 = 5

"abcde".includes("cd")         // true
"abcde".startsWith("abc")      // true
"abcde".endsWith("cde")        // false

Array.from(document.querySelectorAll('*'))     // Returns a real Array
Array.of(1, 2, 3) // Similar to new Array(...), without special one-arg behavior

[0, 0, 0].fill(7, 1)                           // [0, 7, 7]
[1, 2, 3].find(x => x === 3)                   // 3
[1, 2, 3].findIndex(x => x === 2)              // 1
[1, 2, 3, 4, 5].copyWithin(3, 0)               // "memmove" alike [1, 2, 3, 1, 2]

["a", "b", "c"].entries()                      // iterator [0, "a"], [1,"b"], [2,"c"]
["a", "b", "c"].keys()                         // iterator 0, 1, 2
["a", "b", "c"].values()                       // iterator "a", "b", "c"

Object.assign({ a: 1 }, { a: -1, b: 2 })       // { a: -1, b: 2 }

ECMAScript 2016+

Lodash

Array/Object Transform

Usually, we use _.map function to convert a collection of items:

// transform a plain array
map([1, 2, 3], v => v + 1);                         // [2, 3, 4] 

// transform a collection of object
map([{ id: 1 }, { id: 2 }], v => v.id);             // [1, 2]
map([{ id: 1 }, { id: 2 }], v => ({ id: v.id });    // [{ id: 1 }, { id: 2 }]

// transform a collection of object via `_.property` iteratee shorthand
map([{ id: 1 }, { id: 2 }], 'id');                  // [{ id: 1 }, { id: 2 }]
// transform a plain object
map({ id: 1, name: 'foo' }, v => v + 1);            // [2, 'foo1']

Though map function takes in object, but it always returns an array: 

_.mapValues or _.transform come to the rescue if object required:

// transform the value of a plain object 
mapValues({ id: 1, name: 'foo' }, v => v + 1);      // { id: 2, name: 'foo1'}

// transform an object to another object
transform({ id: 1, name: 'foo' }, (result, v, k) => result[k] = v + 1 }, {});

Property Accessors

_.get and _.set are really useful functions for handling object property:

// get and set root level property, given const foo = { id: 1 }
get(foo, 'id');                                  // 1
set(foo, 'id', 2);                               // foo = { id: 2 }

// try to get a certain level of property, backed up with a default value
get({ a: [{ b: 1}] }, 'a[0].b');                 // 1
get({ a: [{ b: 1}] }, 'a[0].b.c');               // undefined
get({ a: [{ b: 1}] }, 'a[0].b.c', false);        // false
get({ a: [{ b: 1}] }, ['a', 0, 'b']);            // 1, via property array 

// try to set the value of a property, with full property path created 
set({ a: [{ b: 1}] }, 'a[0].b', 2);              // { a: [{ b: 2}] }
set({ a: [{ b: 1}] }, 'a[0].c[1]', 2);           // { a: [{ b: 1, c: [,2] }] }
set({ a: [{ b: 1}] }, ['a', 0, 'c', 1], 2);      // via property array

// use _.update to produce value based on its original data
update({ a: [{ b: 1}] }, 'a[0].b', n => n * 6);  // { a: [{ b: 6}] }

And _.invoke is a nice helper to ensure a object method is called safely:

const object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
 
_.invoke(object, 'a[0].b.c.slice', 1, 3);        //  [2, 3]

React

Tips and Tricks in Web App Development

By Haili Zhang

Tips and Tricks in Web App Development

  • 966