26
loading...
This website collects cookies to deliver better user experience
// expected
typeof 135.791113 // "number"
typeof "foo" // "string"
typeof {} // "object"
typeof Symbol('foo') // "symbol"
typeof 1357911n // "bigint"
// somewhat unexpected for beginners
typeof [] // "object", expected something like "array"
typeof async () => {} // "function", expected "async function"
// totally not as expected
typeof NaN // "number", what!? Not a number is a number!?
typeof null // "object", how can nothing be an object!?
typeof
can therefore not be considered as safe, at least not without detailed additional checks. Relying on it in sensitive contexts can have severe consequences.n
is of type number
and is not equal NaN
// Proxy simply comes from another dimension....
new Proxy({}, {}) instanceof Proxy // TypeError: 'prototype' property of Proxy is not an object
// descendants of Object are still Objects
(() => {}) instanceof Object // true
// primitives disguising as Object
new String('foo') instanceof Object // true
new Number(1.357911) instanceof Object // true
// Object disguising as non-Object
Object.create(null) instanceof Object // false
const obj = {}
obj.__proto__ = null
obj instanceof Object // false
class Food {
constructor (expires) {
this.expires = expires
this.days = 0
}
addDay () {
this.days++
}
hasExpired () {
return this.days >= this.expires
}
}
class Apple extends Food {
constructor () {
super(3) // 3 days
this.shape = 'sphere'
}
}
Apple
instance and see which of the properties are available:const apple = new Apple()
// let's add this method just to this one apple instance
apple.isFresh = () => apple.days < apple.expires
'expires' in apple // true
'shape' in apple // true
'addDay' in apple // true
'hasExpired' in apple // true
'isFresh' in apple // true
true
for every in
check, becauseThe in operator returns true if the specified property is in the specified object or its prototype chain.
MDN
in
operator with the for..in
statement. It gives you a totally different result:for (const prop in apple) {
console.log(prop)
}
// output
"expires"
"days"
"shape"
"isFresh"
for..in
loops only through the enumerable properties and omits all the methods, which are assigned to the prototype but it still lists the directly assigned properties.for..in
? Let's take a look at a slightly different approach to our food-chain:const Food = {}
Food.expires = 3 // assigned, right!?
const apple = Object.create(Food)
apple.shape = 'sphere' // also assigned
'expires' in apple // true
apple.hasOwnProperty('expires') // false
'shape' in apple // true
apple.hasOwnProperty('shape') // true
for (const prop in apple) {
console.log(prop)
}
// output
"expires"
"shape"
apple
is now created with Food
as it's prototype, which itself has Object
as it's prototype.expires
property hasn't been passed down the prototype chain as it happened with the ES6 classes example above. However, the property is considered as "enumerable", which is why it's listed in the for..in
statement's output.toString
method is a builtin that descends from Object
and returns a String-representation of it. Descendants can override it to create a custom output that suits it's internal structure.toString
method to fast-compare two Arrays:[1, 2, 3].toString() === ["1",2,3].toString() // true, should be false
0.0.toString() === "0.0" // false, should be true
Array.prototype.toString = function () {
return '[I, am,compliant, to, your, checks]'
}
[1, 2, 3].toString() // "[I, am,compliant, to, your, checks]"
toString
spoofing / overriding can break these checks and is considered a vulnerabilityJSON.stringify
+ sorting on arraysJSON.stringify
alone isn't enough, you may need to write a custom replacer function
toLocaleString()
or toISOString()
on Date objects but note they are also easily overriddenNumber.parseInt
which allows to parse a (decimal) Number to an integer (still Number).radix
parameter:// expected
Number.parseInt(1.357911) // 1
Number.parseInt('1.357911') // 1
Number.parseInt(0x14b857) // 1357911
Number.parseInt(0b101001011100001010111) // 1357911
// boom
const hexStr = 1357911.toString(16) // "14b857"
Number.parseInt(hexStr) // 14
const binStr = 1357911.toString(2) // "101001011100001010111"
Number.parseInt(binStr) // 101001011100001010111
// fixes
Number.parseInt(hexStr, 16) // 1357911
Number.parseInt(binStr, 2) // 1357911
radix
parameter0x14b857
and 0b101001011100001010111
are of type number
and due to the 0x
and the 0b
prefixes the parseInt
method will automatically detect their radix (but not for other systems like octal or other bases)Type coercion is the automatic or implicit conversion of values from one data type to another (such as strings to numbers). Type conversion is similar to type coercion because they both convert values from one data type to another with one key difference — type coercion is implicit whereas type conversion can be either implicit or explicit.
const add = (a, b) => a + b
add('1', 0) // '10'
add(0, '1') // '01'
add(0) // NaN, because Number + undefined = NaN
add(1, null) // 1, just don't think about why...
add(1, []) // "1", just don't think about why...
add(1, []) // "1", just don't think about why...
add(1, () => {}) // "1() => {}", I'll stop here
const isNumber = x => typeof x === 'number' && !Number.isNaN(x) // unfortunately NaN is of type number
const add = (a, b) => {
if (!isNumber(a) || !isNumber(b)) {
throw new Error('expected a and b to be a Number')
}
return a + b
}
add('1', 0) // throws
add('0', 1) // throws
add(0) // throws
add(1, null) // throws
add(1, []) // throws
add(1, []) // throws
add(1, () => {}) // throws
add(1, 2) // 3, yeay!
// preventing NaN by using parameter defaults
const add = (a = 0, b = 0) => {
let a1 = Number.parseFloat(a, 10)
let b1 = Number.parseFloat(b, 10)
// a1, b1 could be NaN so check them
if (!isNumber(a1) || !isNumber(b1)) {
throw new Error('Expected input to be number-alike')
}
return a1 + b1
}
add('1', 0) // 1
add('0', 1) // 1
add(0) // 0
add(1) // 1
add(1, null) // throws
add(1, []) // throws
add(1, []) // throws
add(1, () => {}) // throws
add(1, 2) // 3, yeay!
const add = function (a:number, b:number) {
return a + b
}
add(1, NaN) // NaN
const isDefined = x => !!x
isDefined('') // false, should be true
isDefined(0) // false, should be true
const isDefined = x => typeof x !== 'undefined'
isDefined('') // true
isDefined(0) // true
isDefined(null) // true <-- uh oh
const isDefined = x => typeof x !== 'undefined' && x !== null
isDefined('') // true
isDefined(0) // true
isDefined(null) // false
typeof
check here, you can alternatively use x !== (void 0)
.__proto__
or prototype
and thus potentially affecting all Objects in the current scope.const user = { id: 'foo', profile: { name: 'Jane Doe', age: 42 }, roles: { manager: true } }
function updateUser(category, key, value) {
if (category in user) {
user[category][key] = value
}
}
// good use
updateUser('profile', 'locale', 'de-DE')
// bad use
updateUser('__proto__', 'exploit', 'All your base are belong to us')
// consequence of this
const newObject = {}
newObject.exploit // "All your base are belong to us"
function updateUserProfile(category, key, value) {
if (key === 'name') user.profile.name = value
if (key === 'age') user.profile.age = value
}
Object.prototype.hasOwnProperty
to check
function updateUser(category, key, value) {
if (Object.prototype.hasOwnProperty.call(user, category)) {
user[category][key] = value
}
}
updateUser('__proto__', 'exploit', 'All your base are belong to us')
const newObject = {}
newObject.exploit // undefined
Proxy
Object
const forbidden = ['__proto__', 'prototype', 'constructor']
const user = new Proxy({ id: 'foo', profile: { name: 'Jane Doe', age: 42 }, roles: { manager: true } }, {
get: function (target, prop, receiver) {
if (forbidden.includes(prop)) {
// log this incident
return
}
// ... otherwise do processing
}
})
function updateUser(category, key, value) {
user[category][key] = value
}
updateUser('profile', 'locale', 'de-DE')
updateUser('__proto__', 'exploit', 'All your base are belong to us') // error
'number'
types in previous sections:const isNumber = n => typeof n === 'number'
isNumber(NaN) // true
isNumber(Number.MAX_VALUE * 2) // true
isNumber(Number.MIN_VALUE / 2) // true
const isValidNumber = num => (typeof num === 'number') && !Number.isNaN(num)
const num = Number.parseFloat({}) // => NaN
isNumber(num) // false, as expected
export const isValidInteger = num => isValidNumber(num) && Number.isSafeInteger(num)
isValidInteger({}) // false
isValidInteger(Number.parseFloat({})) // false
isValidInteger(1.357911) // false
isValidInteger(1.0) // true
isValidInteger(1) // true
1.0
which is internally in JS treated as integer:let n = 1
n.toString(2) // "1"
const isInFloatBounds = num => isValidNumber(num) && num >= Number.MIN_VALUE && num <= Number.MAX_VALUE
isInFloatBounds(Infinity) // false
isInFloatBounds(-Infinity) // false
// check for MAX_VALUE
isInFloatBounds(100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) // true
isInFloatBounds(1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000) // false
// check for MIN_VALUE
isInFloatBounds(0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) // true
isInFloatBounds(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001) // false
BigInt
or a specialized library for large Numbers.const almostZero = 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
isInFloatBounds(almostZero) // true
almostZero // 1e-323
const zero = 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001
isInFloatBounds(zero) // false
zero // 0
export const isValidFloat = num => {
if (!isValidNumber(num)) return false
if (num === 0) return true // this is debatable
return isInFloatBounds(num < 0 ? -num : num)
}
Number
in JavaScript!The JavaScript Number type is a double-precision 64-bit binary format IEEE 754 value, like double in Java or C#. This means it can represent fractional values, but there are some limits to what it can store. A Number only keeps about 17 decimal places of precision; arithmetic is subject to rounding. The largest value a Number can hold is about 1.8E308. Numbers beyond that are replaced with the special Number constant Infinity.
const n = 0.1 + 0.2 // 0.30000000000000004
n === 0.3 // false
const num = 1.357911
const hex = num.toString(16) // 1.5ba00e27e0efa
const bin = num.toString(2) // 1.010110111010000000001110001001111110000011101111101
Number.parseFloat(hex, 16) // 1.5
Number.parseFloat(bin, 2) // 1.01011011101
// integers
const num = Number.MAX_SAFE_INTEGER
num // 9007199254740991
num + 100 // 9007199254741092, should be 9007199254741091
// floats
const max = Number.MAX_VALUE
max // 1.7976931348623157e+308
max * 1.00001 // Infinity
Math.fround
const isFilled = arr => arr && arr.length > 0
isFilled([ ]) // false
isFilled([1]) // true
isFilled() // undefined
true
or false
but never undefined
.const isFilled = arr => arr ? arr.length > 0 : false
isFilled([ ]) // false
isFilled([1]) // true
isFilled() // false
const isFilled = arr => {
if (!Array.isArray(arr)) {
throw new TypeError('expected arr to be an Array')
}
return arr.length > 0
}
isFilled([ ]) // false
isFilled([1]) // true
isFilled() // throws Uncaught TypeError
const myObj = {
_count: 0,
count: function () {
return count++
}
}
const createCounter = () => {
let count = 0
return {
count: () => count++
}
}
Proxy
Object to have fine grained control about any member accessclass-privacy
if you can't support private members yeteval
without exactly knowing what you're doingsetTimeout
(or setInterval
)