Last active
November 18, 2017 20:33
-
-
Save qntm/d899c00aa1ac2c663ac6db23bcffcaba to your computer and use it in GitHub Desktop.
An implementation of the JavaScript `==` operator without using `==` itself.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict' | |
// <http://www.ecma-international.org/ecma-262/5.1/#sec-8> Type | |
var type = function (x) { | |
// The `typeof` operator gives us almost what we want | |
return ( | |
typeof x === 'function' ? 'object' | |
: x === null ? 'null' | |
: typeof x | |
) | |
} | |
// <http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.8> DefaultValue | |
// <http://www.ecma-international.org/ecma-262/5.1/#sec-4.3.2> IsPrimitive | |
// Always returns a primitive, although not necessarily a | |
// number or a string. Booleans, `undefined` and `null` are | |
// also primitives. | |
var defaultValue = function (o) { | |
var methodNames = o instanceof Date ? ['toString', 'valueOf'] : ['valueOf', 'toString'] | |
for (var i = 0; i < methodNames.length; i++) { | |
var methodName = methodNames[i] | |
var method = o[methodName] | |
if (method instanceof Function) { | |
var result = method.bind(o).call() | |
if (type(result) !== 'object') { | |
return result | |
} | |
} | |
} | |
throw new TypeError('Cannot convert object to primitive value') | |
} | |
// <http://www.ecma-international.org/ecma-262/5.1/#sec-9.1> ToPrimitive | |
// <http://www.ecma-international.org/ecma-262/5.1/#sec-9.3> ToNumber | |
// <http://www.ecma-international.org/ecma-262/5.1/#sec-11.9.3> The Abstract Equality Comparison Algorithm | |
var doubleEquals = function (x, y) { | |
var typeX = type(x) // "undefined", "null", "boolean", "number", "string" or "object" | |
var typeY = type(y) // "undefined", "null", "boolean", "number", "string" or "object" | |
if (typeX === 'object' && typeY === 'object') { | |
return x === y | |
} | |
if (typeX === 'object' && (typeY === 'boolean' || typeY === 'number' || typeY === 'string')) { | |
x = defaultValue(x) | |
typeX = type(x) // "undefined", "null", "boolean", "number" or "string" | |
// And drop through | |
} | |
if ((typeX === 'boolean' || typeX === 'number' || typeX === 'string') && typeY === 'object') { | |
y = defaultValue(y) | |
typeY = type(y) // "undefined", "null", "boolean", "number" or "string" | |
// And drop through | |
} | |
var nuX = typeX === 'undefined' || typeX === 'null' | |
var nuY = typeY === 'undefined' || typeY === 'null' | |
if (nuX && nuY) { | |
return true | |
} | |
if (nuX !== nuY) { | |
return false | |
} | |
// At this point `x` and `y` must each be a Boolean, a number or a string. | |
if (typeX === 'string' && typeY === 'string') { | |
return x === y | |
} | |
// All other combinations of Boolean, number and string. | |
// It's not strictly necessary to cast numbers to number but | |
// it's idempotent. Casting two Booleans to number does not | |
// alter the result of the comparison. | |
return +x === +y | |
} | |
module.exports = doubleEquals |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment