isotropic-instance-of
v0.1.0
Published
A mixin aware instanceof
Maintainers
Readme
isotropic-instance-of
An instanceof check that understands mixins.
Why Use This?
- Recognizes Mixins: The native
instanceofoperator can't see them - A Superset of instanceof: Anything
instanceofreports as true is reported as true here, so it's a safe substitute - Follows Mixin Ancestry: A mixin's superclasses and a mixin's own mixins are all recognized
- Familiar Signature: Two arguments in the same order as the operator's operands
- Structured Errors: An invalid constructor throws an
isotropic-errornamedTypeError
The Problem
isotropic-make implements mixins by copying the mixin's prototype properties onto the new class's prototype rather than by linking prototypes. That is what makes multiple mixins possible, but it means a mixin never appears in an instance's prototype chain, and instanceof only looks at the prototype chain.
import _make from 'isotropic-make';
const _Serializable = _make('Serializable', {
serialize () {
return JSON.stringify(this);
}
}),
_Model = _make('Model', [
_Serializable
], {});
{
const model = _Model();
console.log(typeof model.serialize); // function
console.log(model instanceof _Serializable); // false
}The object has everything the mixin provides, but the operator says it is not an instance of it. This module answers the question the operator was meant to answer.
import _instanceOf from 'isotropic-instance-of';
console.log(_instanceOf(model, _Serializable)); // trueInstallation
npm install isotropic-instance-ofUsage
The arguments are in the same order as the operands of instanceof.
import _instanceOf from 'isotropic-instance-of';
if (_instanceOf(value, _Model)) {
// value is a Model, or has Model mixed in
}What Counts as an Instance
instanceOf(instance, ConstructorFunction) returns true when any of the following is true:
instance instanceof ConstructorFunctionis true, including through a customSymbol.hasInstancemethodConstructorFunctionis mixed into the instance's classConstructorFunctionis mixed into any class in the instance's prototype chainConstructorFunctionis an ancestor of one of those mixinsConstructorFunctionis mixed into one of those mixins, to any depth
import _instanceOf from 'isotropic-instance-of';
import _make from 'isotropic-make';
const _Base = _make('Base', {}),
_MixinBase = _make('MixinBase', {}),
_NestedMixin = _make('NestedMixin', {}),
_Mixin = _make('Mixin', _MixinBase, [
_NestedMixin
], {}),
_Model = _make('Model', _Base, [
_Mixin
], {}),
_SubModel = _make('SubModel', _Model, {});
{
const subModel = _SubModel();
console.log(_instanceOf(subModel, _SubModel)); // true, its own class
console.log(_instanceOf(subModel, _Model)); // true, an ancestor class
console.log(_instanceOf(subModel, _Base)); // true, an ancestor class
console.log(_instanceOf(subModel, _Mixin)); // true, mixed into an ancestor
console.log(_instanceOf(subModel, _MixinBase)); // true, an ancestor of that mixin
console.log(_instanceOf(subModel, _NestedMixin)); // true, mixed into that mixin
}Traversal is handled by isotropic-mixin-prototype-chain.
Compatibility with instanceof
The result is a superset of the operator's, so replacing instanceof with this function never turns a true into a false.
console.log(_instanceOf({}, Object)); // true
console.log(_instanceOf([], Array)); // true
console.log(_instanceOf([], Object)); // true
console.log(_instanceOf(new Map(), Map)); // true
console.log(_instanceOf(() => null, Function)); // trueNative classes behave exactly as they do with the operator, because there are no mixins to find.
class Collection extends Array {
}
console.log(_instanceOf(new Collection(), Array)); // true
console.log(_instanceOf([], Collection)); // falsePrimitives, null, and undefined return false, and never throw.
console.log(_instanceOf(null, Object)); // false
console.log(_instanceOf(void null, Object)); // false
console.log(_instanceOf(1, Number)); // false
console.log(_instanceOf('string', String)); // false
console.log(_instanceOf(Object.create(null), Object)); // falseCompatibility with Symbol.hasInstance
A custom Symbol.hasInstance method is honored when it returns true.
class Magic {
static [Symbol.hasInstance] (value) {
return value === 'magic';
}
}
console.log(_instanceOf('magic', Magic)); // trueWhen a custom Symbol.hasInstance method returns false, this package continues searching the prototype chain and may return true anyway. This package exists to answer a broader question than the language’s instanceof: "Is this constructor somewhere in the full prototype chain, including mixins?" A custom Symbol.hasInstance method that deliberately returns false is expressing an opinion about the narrower language operator. It can't answer the broader question this utility is designed to answer.
Errors
Where the operator throws a native TypeError, this function throws an isotropic-error named TypeError, with the arguments in details.
A ConstructorFunction that is not a function:
_instanceOf({}, 'Model');
// TypeError: ConstructorFunction must be a functionA ConstructorFunction that has no prototype object and no custom Symbol.hasInstance method, such as an arrow function or a concise method:
_instanceOf({}, () => null);
// TypeError: ConstructorFunction must have a prototype objectBoth carry details.ConstructorFunction and details.instance, so the offending arguments are in the report.
try {
_instanceOf(value, notAConstructor);
} catch (error) {
console.log(error.name); // TypeError
console.log(error.details.ConstructorFunction);
}Only the ConstructorFunction is validated. Any value at all is acceptable as the instance.
API Reference
instanceOf(instance, ConstructorFunction)
| Parameter | Type | Description |
| --- | --- | --- |
| instance | Any | The value to test |
| ConstructorFunction | Function | The constructor to test against |
Returns a boolean. Throws an isotropic-error named TypeError when ConstructorFunction cannot be used as the right operand of instanceof.
Examples
Validating an Argument
import _Error from 'isotropic-error';
import _instanceOf from 'isotropic-instance-of';
const connect = ({
transport
}) => {
if (!_instanceOf(transport, _Transport)) {
throw _Error({
details: {
transport
},
message: 'transport must be a Transport'
});
}
// ...
};This accepts a class that mixes in _Transport as readily as one that extends it, which is usually what a caller expects when a library documents a capability rather than a base class.
Dispatching on Capability
const render = value => {
if (_instanceOf(value, _Renderable)) {
return value.render();
}
if (_instanceOf(value, _Serializable)) {
return value.serialize();
}
return String(value);
};_Renderable and _Serializable are mixins, so no class needs to inherit from either one to participate.
Filtering a Collection
const widgets = children.filter(child => _instanceOf(child, _Widget));Notes
Mixins Are Found Through the Class, Not the Object
The mixins recognized are the ones declared when the classes were made. Copying a mixin's methods onto an object by hand does not make that object an instance of the mixin.
The Prototype Chain Is Walked From the Instance's Prototype
An object is not an instance of a constructor merely because it is that constructor's prototype, which matches the operator.
console.log(_instanceOf(_Model.prototype, _Model)); // false
console.log(_instanceOf(Object.create(_Model.prototype), _Model)); // trueCost
The operator is a single prototype chain walk. This function does that first and only walks mixins when the operator says no, so the common true case is no slower than instanceof. A negative result walks the full mixin graph, which is proportional to the number of classes and mixins involved. Where a check is on a hot path and the classes involved have no mixins, instanceof is still the cheaper tool.
Integration with Other Isotropic Modules
- isotropic-make: Creates the classes and mixins this recognizes
- isotropic-mixin-prototype-chain: Walks the prototype chain of an object and its mixins
- isotropic-error: Produces the structured
TypeError
Contributing
Please refer to CONTRIBUTING.md for information on how to contribute.
Issues
Please refer to the issue tracker.
License
BSD-3-Clause
