You don't know JS

-  Coercion & Grammar

Mia Yang

Converting a value from one type to another

Coercion

Casting v.s Coercion

Implicit corecion v.s Explicit corecion

var num = 42

var implicit = num + ""

var explicit = String(num)

Abstract Value Operations

  • ToString
  • ToNumber
  • ToBoolean
  • ToPrimitive

ToString

When any non-string value is coerced to a string 

representation, it can either be called explicitly, or it will automatically be called if a non-string is used in a string context.

Built-in primitive values have natural stringification,

and for regular object, unless you specify your own, the default will return [object Object]

const a = null // "null"

const b = true // "true"

const c = undefined // "undefined"

const d = 42 // "42"

const e = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000  // "1.07e21"

const f = { name: "Mia", age: 21 } // [object Object]

As shown earlier, if an object has its own toString()

method on it, and you use that object in a string-like

way, its toString() will automatically be called, and the string result of that call will be used instead.

let obj = {
  name: 'mia'
}

console.log(obj.toString()) // [object Object]

obj.toString = () => {
  return 'over'
}

const implicit = obj + 'ride!' // "override!"

const explicit = obj.toString() + 'ride!' // "override!"
const arr = [1, 2, 3]

arr.toString() // "1, 2, 3"

JSON Stringification

Any JSON-safe value can be stringified by JSON.stringify()

JSON.stringify( 42 );    // "42"

JSON.stringify( "42" );    // ""42""

JSON.stringify( null );    // "null"

JSON.stringify( true );    // "true"

What is JSON-safe?

It may be easier to consider values that are not JSON-safe

JSON.stringify( undefined );                    // undefined
JSON.stringify( function(){} );                    // undefined

JSON.stringify( [1,undefined,function(){},4] );    // "[1,null,null,4]"
JSON.stringify( { a:2, b:function(){} } );        // "{"a":2}"


var o = {};

var a = {
  b: 42,
  c: o
};

o.a = a;

JSON.stringify(o); // Converting circular structure to JSON

JSON stringification has the special behavior that if an object value has a toJSON() method defined, this method will be called first to get a value to use for serialization

var obj = {
  value: 42
};

obj.toJSON = function() {
  return { value: 1 };
};

console.log(JSON.stringify(obj)); // { "value": 1 }

It's a very common misconception that toJSON() should return a JSON stringification representation

var a = {
  val: [1, 2, 3],
  // probably correct!
  toJSON: function() {
    return this.val.slice(1);
  }
};

var b = {
  val: [1, 2, 3],
  // probably incorrect!
  toJSON: function() {
    return "[" + this.val.slice(1).join() + "]";
  }
};

console.log(JSON.stringify(a)) // "[2,3]"

console.log(JSON.stringify(b)) // ""[2,3]""
var a = {
  b: 42,
  c: " 42 ",
  d: [1, 2, 3]
};

JSON.stringify(a, ["b", "c"]) // "{"b":42,"c":"42"}"

JSON.stringify(a, function(key, value) {
	if (key !== "c") return value;
})
// "{"b":42,"d":[1,2,3]}"
console.log(JSON.stringify(a, null ,4))
/*
{
    "b": 42,
    "c": " 42 ",
    "d": [
    	1,
        2,
        3
    ]
}
*/

console.log(JSON.stringify(a, null, '--'))
/*
{
--"b": 42,
--"c": " 42 ",
--"d": [
----1,
----2,
----3
--]
}
*/

Second & Third parameters

ToNumber

console.log(Number(true)); // 1
console.log(Number(false)); // 0

console.log(Number(null)); // 0
console.log(Number(undefined)); // NaN

console.log(Number("")) // 0
console.log(Number("42")) // 42
console.log(Number("number")) // NaN
var a = {
  valueOf: function() {
    return " 42 ";
  }
};

var b = {
  toString: function() {
    return " 42 ";
  }
};

var c = [4, 2];

console.log(Number(a)); // 42
console.log(Number(b)); // 42
console.log(Number(c)); // 42
console.log(Number([])) // 0
console.log(Number(["abc"])) // NaN
console.log(Number({ a: 10})) // NaN

ToBoolean

It's a common misconception that the values 1 and 0 are identical to true/false. While that may be true in other languages, in JS the numbers are numbers and the booleans are booleans

Falsy Values

The following as the so-called falsy list, and everything else will be Truthy

  • undefined
  • null
  • false
  • +0, -0, NaN
  • ""

Truthy Values

A value is truthy if it's not on the falsy list.

In other words, the truthy list is infinitely long. It's impossible to make such a list. You can only make a finite falsy list and consult it.

Explicitly: Strings <--> Numbers

var a = 42;
var b = a.toString();

var c = "3.14";
var d = +c;

b; // "42"
d; // 3.14
const a = +new Date();

const b = new Date().getTime()

const c = Date.now()

~ operator (aka "bitwise NOT")

+ 2^(n-1) - 1 0111 …. 1111
.
+ 2 0000 …. 0010
+ 1 0000 …. 0001
0 0000 …. 0000
– 1 1111 …. 1111
– 2 1111 …. 1110
– 3 1111 …. 1101
.
– 2^(n-1) 1000 …. 0000

~ x  ==>  - (x + 1)

~42;    // -(42+1) ==> -43

In JavaScript, -1 is commonly called a "sentinel value"

var a = " Hello World ";

if (a.indexOf("lo") >= 0) {
  console.log("found it");
}
if (a.indexOf("lo") != -1) {
  console.log("found it");
}

if (a.indexOf("ol") < 0) {
  console.log("Not found");
}
if (a.indexOf("ol") == -1) {
  console.log("Not found");
}
var a = "Hello World";

if (~a.indexOf("lo")) {
  console.log("found it");
}

if (!~a.indexOf("ol")) {
  console.log("Not found");
}

Explicity: Parsing Numeric Strings

Parsing a numeric value out of a string is tolerant of non-numeric characters, It just stops parsing left-to-right when encountered ,whereas coercion is not tolerant and fails resulting in the NaN value.

var a = "42";
var b = "42px";

console.log(Number(a)); // 42
console.log(parseInt(a)); // 42

console.log(Number(b)); // NaN
console.log(parseInt(b)); // 42

If you pass a non-string, the value you pass will automatically be coerced to a string first, so never use parseInt(..) with a non-string value.

parseInt ( 1 / 0 , 19 ); // 18
parseInt ( 0.000008 );		 // 0 ("0" from "0.000008") 
parseInt ( 0.0000008 );		 // 8 ("8" from "8e-7") 
parseInt ( false , 16 );		 // 250 ("fa" from "false") 
parseInt ( parseInt, 16 );	 // 15 ("f" from "function..")
parseInt ( "0x10" );			 // 16 
parseInt ( "103" , 2 );		 // 2

parseInt(..) would look at the beginning character(s) to make a guess.

Explicitly: * --> Boolean

var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g;

console.log(Boolean(a)) // true
console.log(Boolean(b)) // true
console.log(Boolean(c)) // true

console.log(Boolean(d)) // false
console.log(Boolean(e)) // false
console.log(Boolean(f)) // false
console.log(Boolean(g)) // false
var a = "0";
var b = [];
var c = {};

var d = "";
var e = 0;
var f = null;
var g;

console.log(!!a) // true
console.log(!!b) // true
console.log(!!c) // true

console.log(!!d) // false
console.log(!!e) // false
console.log(!!f) // false
console.log(!!g) // false

Implicitly: Strings <--> Numbers

var a = 42
var b = a + ""

b // "42"
var a = [ 1 , 2 ];
var b = [ 3 , 4 ];

a + b; // "1,23,4"

 But if you're using an object instead of a regular primitive number value, you may not necessarily get the same string value!

var a = {
  valueOf: function () { return 42; },
  toString: function () { return 4; }
};

console.log(a + "");      // "42"

console.log(String(a))    // "4"

Implicitly: * --> Boolean

  1. The test expression in an if (..) statement.
  2. The test expression (second clause) in a for ( .. ; .. ; .. ) header.
  3. The test expression in while (..) and do..while(..) loops.
  4. The test expression (first clause) in ? : ternary expressions.
  5. The left-hand operand (which serves as a test expression -- see below!) to the || ("logical or") and && ("logical and") operators.

Operators || and &&

The value produced by a && or || operator is not necessarily of type Boolean. The value produced will always be the value of one of the two operand expressions.

var a = 42;
var b = "abc";
var c = null;

console.log(a || b) // 42 
console.log(a && b) // "abc"

console.log(b || c) // "abc" 
console.log(b && c)	// null

Guard Operator

function foo () {
  console.log(a);
}

var a = 42;

a && foo(); // 42

Loose Equals ==  v.s  Strict Equals ===

A very common misconception about these two operators is: "== checks values for equality and === checks both values and types for equality."

The correct description is: "== allows coercion in the equality comparison and === disallows coercion."

What's difference between those two explanation ?

Abstract Equality

string to number

  • If Type(x) is Number and Type(y) is String, return the result of the comparison x == ToNumber(y).
  • If Type(x) is String and Type(y) is Number, return the result of the comparison ToNumber(x) == y.
var a = 42;
var b = "42";

a === b;    // false
a == b;     // true

anything to boolean

  • Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.
  • If Type(y) is Boolean, return the result of the comparison x == ToNumber(y).
var a = "42";
var b = true;
var c = false

a == b;  // false
a == c;  // false

null to undefined

  • If x is null and y is undefined, return true.
  • If x is undefined and y is null, return true.
var a = null;
var b;

a == b;        // true
a == null;    // true
b == null;    // true

a == false;    // false
b == false;    // false
a == "";    // false
b == "";    // false
a == 0;        // false
b == 0;        // false

object to non-object

  • If Type(x) is either String or Number and Type(y) is Object, return the result of the comparison x == ToPrimitive(y).
  • If Type(x) is Object and Type(y) is either String or Number, return the result of the comparison ToPrimitive(x) == y.
var a = 42;
var b = [42];
var c = Object("42")

a == b; // true
a == c; // true
var i = 1;

Number.prototype.valueOf = function () {
  return ++i;
};

var a = new Number(42);

if (a == 2 && a == 3) {
  console.log(" Yep, this happened. "); // Yep, this happened.
}

Edge Cases

"0" == null;        // false 
"0" == undefined;   // false 
"0" == false;       // true --噢!
"0" == NaN;         // false 
"0" == 0;           // true 
"0" == "";          // false

false == null;      // false 
false == undefined; // false 
false == NaN;       // false 
false == 0;         // true --噢!
false == "";        // true --噢!
false == [];        // true --噢!
false == {};        // false

"" == null;         // false 
"" == undefined;    // false 
"" == NaN;          // false 
"" == 0;            // true --噢!
"" == [];           // true --噢!
"" == {};           // false

0 == null;          // false 
0 == undefined;     // false 
0 == NaN;           // false 
0 == [];            // true --噢!
0 == {};            // false

[] == ![]           // true --噢!

Abstract Relational Comparison

The algorithm is only defined for a > b
So, a > b is handled as b < a

var a = [42];
var b = ["43"];

a < b // true

var c = [4, 2]
var d = [4, 3]

c < d // true
var a = { x: 10 }
var b = { x: 11 }

a < b  // false
a == b // false
a > b  // false

a <= b // true
b <= a // true

a <= b  ===> ! (a > b)

Made with Slides.com