Skip to content

Instantly share code, notes, and snippets.

@royling
Last active August 29, 2015 14:08
Show Gist options
  • Save royling/70830c14ecb8ecd2ce22 to your computer and use it in GitHub Desktop.
Save royling/70830c14ecb8ecd2ce22 to your computer and use it in GitHub Desktop.
#explain-js-type-coercion-in-js: EcmaScript Internal [[ToPrimitive]]
describe("isPrimitive", function() {
it("should return true for primitive values", function() {
expect(isPrimitive(0)).toBe(true);
expect(isPrimitive('')).toBe(true);
expect(isPrimitive(false)).toBe(true);
expect(isPrimitive(null)).toBe(true);
expect(isPrimitive(undefined)).toBe(true);
expect(isPrimitive(NaN)).toBe(true);
});
it("should return false for others", function() {
expect(isPrimitive({})).toBe(false);
expect(isPrimitive([])).toBe(false);
});
});
function toPrimitive(value, preferredType) {
if (isPrimitive(value)) return value;
if (undefined === preferredType) {
// omitted
// convert to string for dates and number for other values
preferredType = value.constructor === Date ? 'string' : 'number';
}
var ret;
if ('number' === preferredType) {
ret = use(value, 'valueOf');
if (ret.done) return ret.value;
ret = use(value, 'toString');
if (ret.done) return ret.value;
fail();
} else if ('string' === preferredType) {
ret = use(value, 'toString');
if (ret.done) return ret.value;
ret = use(value, 'valueOf');
if (ret.done) return ret.value;
fail();
}
}
function isPrimitive(value) {
// nonvalues: null or undefined
if (value === null || value === undefined) return true;
switch (typeof value) {
case 'string': case 'number': case 'boolean':
return true;
default:
return false;
}
}
function use(value, method) {
var result, done = false;
if (typeof value[method] == 'function') {
result = value[method]();
done = isPrimitive(result);
}
return { done: done, value: result };
}
function fail() {
throw new TypeError('Cannot convert to a primitive value!');
}
describe("toPrimitive", function() {
it("should return primitive as is", function() {
[0, NaN, '', false, null, undefined].forEach(function(value) {
expect(toPrimitive(value)).toEqual(value);
});
});
it("should use valueOf() if preferred is number", function() {
var obj = Object.create(null);
obj.valueOf = function() {
return 'test';
};
expect(toPrimitive(obj, 'number')).toEqual('test');
});
it("should use toString() if preferred is number and no valueOf()", function() {
var obj = Object.create(null);
obj.toString = function() {
return 'test';
};
expect(toPrimitive(obj, 'number')).toEqual('test');
});
it("should use valueOf() if preferred is string", function() {
var obj = Object.create(null);
obj.toString = function() {
return 'test';
};
expect(toPrimitive(obj, 'string')).toEqual('test');
});
it("should use valueOf() if preferred is string and no toString()", function() {
var obj = Object.create(null);
obj.valueOf = function() {
return 'test';
};
expect(toPrimitive(obj, 'string')).toEqual('test');
});
it("should fail if neither valueOf() nor toString() exists", function() {
var f = function() {
return toPrimitive(Object.create(null));
};
expect(f).toThrow();
});
it("should default preferredType as number if it's omitted", function() {
var obj = Object.create(null);
obj.valueOf = function() {
return 'valueOf';
};
obj.toString = function() {
return 'toString';
};
expect(toPrimitive(obj)).toEqual('valueOf');
});
it("should default preferredType as string if it's omitted and a Date object", function() {
var now = new Date();
now.toString = function() {
return 'now';
};
now.valueOf = function() {
return 'valueOf';
};
expect(toPrimitive(now)).toEqual('now');
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment