Types and Coercion in JavaScript
Primitive Types
- The primitive types are:
- undefined
- string
- number
- boolean
- object
- array: use array.isArray() to test if an array is an array.
- function
- null: A historical bug... important to know that if you're testing for null, you may be getting an object type that is actually null.
- symbol
- bigint (future)
- In JavaScript variables don't have types, their values have types... because JS is a dynamically typed language.
- typeof returns strings! Great for checking to see if a value is what you expect.
- undefined is different to undeclared... undeclared means it has never been created in any scope whereas undefined means that the variable has no value.
Special values
- NaN:
- it has a type of number!!!
- represents an invalid number.
- Can occur when trying to convert a string that can't be converted to number to a number.
- Or subtracting a string from a number.
- NaN don't have the identity property, NaN is the only value that is never equal to itself.
- isNaN() will coerce whatever is in the brackets to a number then tell you if its a NaN, this results it returning true for strings which is wrong.
- Number.isNaN() does the same as above but without coercion.
- Negative Zero:
- Only way to test if you have one is with
Object.is(value, -0)
. - Useful for when you are using signs to distinguish direction.
- Only way to test if you have one is with
Fundamental Objects
- Use the new keyword when creating the following objects:
- Object()
- Array()
- Function()
- Date()
- RegExp()
- Error()
- Don't use the following with new, they should be used as functions for coercion:
- String()
- Number()
- Boolean()
Abstract Operations
- A key thing to keep in mind with these operations is to check the spec and see what other coercion is going on.
- ToPrimitive(hint): hint tells the operation what primitive you'd like (string, number, or no input... function does the best it can)
- hint = 'number' -> valueOf() then it will try toString(), if neither work we will get an error.
- hint = 'string' -> toString() then it will try valueOf(), if neither work we will get an error.
- ToString(): takes any value and gives us the representation of that value in string form.
- avoid using this on Arrays due to the amount of edge cases
- null and undefined are just ignored in arrays
- for objects it will just return "[object Object]" Unless you overwrite it with:
{toString(){return "My new object string tag";}}
- ToNumber(): converts a string to number.
- "" = 0
- "009" = 9
- "0." = 0
- ".0" = 0
- "." = NaN
- "0xaf" = 175 (octal)
- ToNumber(Array):
- [""] 0
- ["0"] 0
- ["-0"] -0
- [null] 0
- [undefined] 0
- [1,2,3] NaN
- [[[[]]]] 0
- ToNumber(Object): - We can invoke the ToNumber operation with
+
-{}
NaN{valueOf{return 3;}}
3 -ToBoolean: Is the value falsy or not? - If arg is boolean, return it - If arg is null, undefined, NaN, 0, -0, return false - Else return true
Coercion
-
- Overloading
- If either one of the operands to the + operator is a string, the + will then perform string concatenation.
- Needs 2 numbers for + to do addition
- Overloading
-
Coercion happens implicitly everywhere
`There are ${number} chickens here!` // Converts the number, which is of type number, to a string.
-
The minus operator isn't overloaded like + and this leads to it doing coercion when passed other types... it implicitly calls ToNumber()
-
Boxing: Allows accessing properties of primitive values. It essentially gets primitive values and implicitly coerces it to an object so you can access things like "length"
var myString = "Hey mate"; myString.length;
-
Coercion Corner Cases:
Number( "" ); // 0 Number(" \t\n"); // 0 Number( null ); // 0 Number( undefined ); // NaN Number( [] ); // 0 Number( [1,2,3] ); // NaN Number( [null] ); // 0 Number( [undefined] ); // 0 Number( {} ); // NaN String( -0 ); // "0" String( null ); // "null" String( undefined ); // "undefined" String( [null] ); // "" String( [undefined] ); // "" Boolean( new Boolean(false) ); // true
- It is not wise to implicitly coerce booleans to numbers. Look at the following example:
1 < 2 < 3 (1 < 2) < 3 true < 3 1 < 3 // Accidentally true, not behaving as expected at face value. // Look at this one 3 > 2 > 1 (3 > 2) > 1 true > 1 1 > 1 // This is now false... OOPS
Equality
- ==: Abstract Equality Comparison
- If types are the same: ===
- If null or undefined: equal
- If non-primitive: convert with ToPrimitive
- Prefers to use ToNumber!
- Specification
- AVOID:
- using with 0, "" or " "
- using it with non-primitives
- use it with true or false (let ToBoolean happen implicitly or just use ===)
- using it at all if you don't know the types.
- The double equals is preferred over the triple equals IF APPLICABLE.
- ===: Strict Equality Comparison
- If type of x is different to type of y return false
- If x is a number then:
- If x is NaN return false
- If y is NaN return false
- If x is the same number value as y return true
- If x is +0 and y is -0 return true
- If x is -0 and y is +0 return true
- return false
- Return the SameValueNonNumber(x,y)
- If you know the types will be different, === would be broken... it'll always fail The Key here is that == will allow coercion when types don't match. Whereas === will not allow coercion. If you know the types, == is the more sensible choice If possible, you should always try to structure your code so you know the types
Key Takeaways
- Useful: when the reader is focused on what's important
- Dangerous: when the reader can't tell what will happens
- Better: when the reader understands the code