milojs/proto

View on GitHub
lib/dot/object.jst

Summary

Maintainability
Test Coverage
'use strict';
{{# def.definitions }}
{{# def.keys }}

/**
 * - [extend](#extend)
 * - [clone](#clone)
 * - [defineProperty](#defineProperty)
 * - [defineProperties](#defineProperties)
 * - [deepExtend](#deepExtend)
 * - [deepClone](#deepClone)
 * - [keys](#keys)
 * - [allKeys](#allKeys)
 * - [values](#values)
 * - [keyOf](#keyOf)
 * - [allKeysOf](#allKeysOf)
 * - [eachKey](#eachKey)
 * - [mapKeys](#mapKeys)
 * - [reduceKeys](#reduceKeys)
 * - [filterKeys](#filterKeys)
 * - [someKey](#someKey)
 * - [everyKey](#everyKey)
 * - [findValue](#findValue)
 * - [findKey](#findKey)
 * - [pickKeys](#pickKeys)
 * - [omitKeys](#omitKeys)
 * - [isEqual](#isEqual)
 * - [isNot](#isNot)
 *
 * All these methods can be [chained](proto.js.html#Proto)
 */
module.exports = {
    extend: extend,
    clone: clone,
    findValue: findValue,
    findKey: findKey,
    defineProperty: defineProperty,
    defineProperties: defineProperties,
    deepExtend: deepExtend,
    deepClone: deepClone,
    keys: keys,
    allKeys: allKeys,
    values: values,
    keyOf: keyOf,
    allKeysOf: allKeysOf,
    eachKey: eachKey,
    mapKeys: mapKeys,
    reduceKeys: reduceKeys,
    filterKeys: filterKeys,
    someKey: someKey,
    everyKey: everyKey,
    pickKeys: pickKeys,
    omitKeys: omitKeys,
    isEqual: isEqual,
    isNot: isNot
};


var concat = Array.prototype.concat;


/**
 * ####Property descriptor constants####
 * The sum of these constants can be used as last parameter of defineProperty and defineProperties to determine types of properties.
 */
var constants = module.exports._constants = {
    ENUMERABLE: 1,
    ENUM: 1,
    CONFIGURABLE: 2,
    CONF: 2,
    WRITABLE: 4,
    WRIT: 4
};


/**
 * Extends object `self` with the properties of the object `obj` copying all own properties (not those inherited via prototype chain), including non-enumerable properties (unless `onlyEnumerable` is truthy).
 * Created properties will have the same descriptors as the propertis of `obj`.
 * Returns `self` to allow chaining with other functions.
 * Can be used with functions, to copy class methods, e.g.
 *
 * @param {Object} self An object to be extended
 * @param {Object} obj An object which properties will be copied to self
 * @param {Boolean} onlyEnumerable Optional flag to prevent copying non-enumerable properties, `false` by default
 * @return {Object}
 */
{{## def.getDescriptor:_obj:
    var desc = Object.getOwnPropertyDescriptor(_obj, key);
    if (desc) descriptors[key] = desc;
#}}

function extend({{# def.arg }} obj, onlyEnumerable) {
    var descriptors = {};
    {{ var params = { obj: 'obj', code: '{{# def.getDescriptor:obj }}' }; }}
    {{# def.eachKey:params }}

    Object.defineProperties({{# def.self }}, descriptors);
    return {{# def.this }};
}


/**
 * Makes a shallow clone of object `obj` creating an instance of the same class; the properties will have the same descriptors.
 * To clone an array use
 * ```
 * var clonedArray = [].concat(arr);
 * ```
 * This function should not be used to clone an array, because it is inefficient.
 *
 * @param {Object} self An object to be cloned
 * @param {Boolean} onlyEnumerable Optional flag to prevent copying non-enumerable properties, `false` by default
 * @return {Object}
 */
{{## def.clone:
    var descriptors = {};
    {{ var params = { obj: 'self', code: '{{# def.getDescriptor:self }}' }; }}
    {{# def.eachKey:params }}
    clonedObject = Object.create(self.constructor.prototype, descriptors);
#}}

function clone({{# def.arg }} onlyEnumerable) {
    {{# def.varSelf }}
    var clonedObject;
    if (Array.isArray(self)) clonedObject = self.slice();
    else if (self instanceof Date) clonedObject =  new Date(self);
    else if (self instanceof RegExp) clonedObject = new RegExp(self);
    if (!clonedObject) { {{# def.clone }} }
    {{# def.return:clonedObject }}
}


{{## def.findCode:
    var item = self[key];
    if (callback.call(thisArg, item, key, self)) {
        result = {{= params.result }};
        break;
    }
#}}

{{## def.findInObj:params:
    {{# def.varSelf }}
    var result = {{= params.notFound }};
    {{ var iterParams = { obj: 'self', code: '{{# def.findCode }}' }; }}
    {{# def.eachKey:iterParams }}
    {{# def.return:result }}
#}}

/**
 * Analogue of ES6 [Array __find__ method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find).
 * Returns the value of object property that passes callback test.
 *
 * @param {Object} self object to search in
 * @param {Function} callback should return `true` for item to pass the test, passed `value`, `key` and `self` as parameters
 * @param {Object} thisArg optional context (`this`) of callback call
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Any}
 */
function findValue({{# def.arg }} callback, thisArg, onlyEnumerable) {
    {{ var params = { result: 'item', notFound: 'undefined' }; }}
    {{# def.findInObj:params }}
}


/**
 * Analogue of ES6 [Array __findIndex__ method](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex).
 * Returns the key of object property that passes callback test. Returns `undefined` if not found (unlike `findIndex`, that returns -1 in this case).
 *
 * @param {Object} self object to search in
 * @param {Function} callback should return `true` for item to pass the test, passed `value`, `key` and `self` as parameters
 * @param {Object} thisArg optional context (`this`) of callback call
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Integer}
 */
function findKey({{# def.arg }} callback, thisArg, onlyEnumerable) {
    {{ var params = { result: 'key', notFound: 'undefined' }; }}
    {{# def.findInObj:params }}
}


/**
 * Syntax sugar to shorten usage of `Object.defineProperty`.
 * The simplest usage (to add non-enumerable, non-configurable, non-writable property):
 * ```
 * _.defineProperty(obj, 'key', value);
 * ```
 *
 * To define some other properties use sum of the flags `_.ENUMERABLE` (or `_.ENUM`), `_.CONFIGURABLE` (or `_.CONF`) and `_.WRITABLE` (or `_.WRIT`):
 * ```
 * _.defineProperty(obj, 'key', value, _.ENUM + _.WRIT);
 * ```
 * Returns `self`.
 *
 * @param {Object} self An object to add a property to
 * @param {String} propertyName the name of the property that will be added
 * @param {Any} value the value of added property
 * @param {Integer} decriptorFlags bit mask of property descriptor properties composed from `_.ENUMERABLE` (or `_.ENUM`), `_.CONFIGURABLE` (or `_.CONF`) and `_.WRITABLE` (or `_.WRIT`)
 * @return {Object}
 */
{{## def.createDescriptor:
    var descriptor = { value: value };
    if (decriptorFlags) {
        descriptor.enumerable = !!(decriptorFlags & constants.ENUMERABLE);
        descriptor.configurable = !! (decriptorFlags & constants.CONFIGURABLE);
        descriptor.writable = !! (decriptorFlags & constants.WRITABLE);
    }
#}}

function defineProperty({{# def.arg }} propertyName, value, decriptorFlags) {
    {{# def.createDescriptor }}
    Object.defineProperty({{# def.self }}, propertyName, descriptor);
    return {{# def.this }};
}


/**
 * Syntax sugar to shorten usage of `Object.defineProperties`.
 * The simplest usage (to add non-enumerable, non-configurable, non-writable properties):
 * ```
 * _.defineProperties(obj, {
 *     key1: value1,
 *     key2: value2
 * });
 * ```
 * To define some other properties use sum of the flags `_.ENUMERABLE` (or `_.ENUM`), `_.CONFIGURABLE` (or `_.CONF`) and `_.WRITABLE` (or `_.WRIT`):
 * ```
 * _.defineProperties(obj, {
 *     key1: value1,
 *     key2: value2
 * }, _.ENUM + _.WRIT);
 * ```
 * Returns `self`.
 *
 * @param {Object} self An object to add a property to
 * @param {Object} propertyValues A map of keys and values of properties thatwill be added. The descriptors of properties will be defined by the following parameters.
 * @param {Integer} decriptorFlags bit mask of property descriptor properties composed from `_.ENUMERABLE` (or `_.ENUM`), `_.CONFIGURABLE` (or `_.CONF`) and `_.WRITABLE` (or `_.WRIT`)
 * @return {Object}
 */
{{## def.defPropsCode:
    var value = propertyValues[key];
    {{# def.createDescriptor }}
    descriptors[key] = descriptor;
#}}

function defineProperties({{# def.arg }} propertyValues, decriptorFlags) {
    {{# def.varSelf }}
    var descriptors = {};
    {{ var params = { obj: 'propertyValues', code: '{{# def.defPropsCode }}' }; }}
    {{# def.eachKeyAll:params }}
    Object.defineProperties(self, descriptors);
    {{# def.return:self }}
}


/**
 * Extends object `self` with properties of `obj` to any depth, without overwrtiting existing object properties of `self` with object properties of `obj`.
 * Scalar properties of `obj` will overwrite properties of `self`. Scalar porperties of `self` will also be overwritten.
 * Correctly works with recursive objects.
 * Usage:
 * ```
 * var obj = {
 *     inner: {
 *         a: 1
 *     }
 * };
 *
 * _.deepExtend(obj, {
 *     inner: {
 *         b: 2
 *     }
 * });
 *
 * assert.deepEqual(obj, {
 *     inner: {
 *         a: 1,
 *         b: 2
 *     }
 * }); // assert passes
 * ```
 * Returns `self`.
 *
 * @param {Object} self An object to be extended
 * @param {Object} obj An object with properties to copy to
 * @param {Boolean} onlyEnumerable Optional `true` to use only enumerable properties
 * @param {Boolean} preserveStructure if true will throw at the attempt to overwrite object with scalar value (including Date and Regex) and vice versa
 * @return {Object}
 */
function deepExtend({{# def.arg }} obj, onlyEnumerable, preserveStructure) {
    var result = _extendTree({{# def.self }}, obj, onlyEnumerable, preserveStructure, []);
    {{# def.return:result }}
}

{{## def.isNormalObject:_value:
    typeof _value == "object"
        && _value != null
        && !(_value instanceof RegExp) && !(_value instanceof Date)
#}}

{{## def.deepExtendCode:
    var value = objNode[key];
    var hasProp = selfNode.hasOwnProperty(key);
    var selfValue = selfNode[key];
    var isSelfObj = {{# def.isNormalObject:selfValue }};
    var isValueObj = {{# def.isNormalObject:value }};

    if (preserveStructure && hasProp && isSelfObj != isValueObj)
        throw new Error("deepExtend");

    if (isValueObj) {
        if (!hasProp || !isSelfObj)
            selfNode[key] = (Array.isArray(value)) ? [] : {};

        _extendTree(selfNode[key], value, onlyEnumerable, preserveStructure, objTraversed);
    } else {
        var descriptor = Object.getOwnPropertyDescriptor(objNode, key);
        if (descriptor) Object.defineProperty(selfNode, key, descriptor);
    }
#}}

function _extendTree(selfNode, objNode, onlyEnumerable, preserveStructure, objTraversed) {
    if (objTraversed.indexOf(objNode) >= 0) return selfNode; // node already traversed, obj has recursion

    // store node to recognise recursion
    objTraversed.push(objNode);

    if (Array.isArray(objNode)) {
        for (var key=0; key<objNode.length; key++) {
            {{# def.deepExtendCode }}
        }
    } else {
        {{ var params = { obj: 'objNode', code: '{{# def.deepExtendCode }}' }; }}
        {{# def.eachKey:params }}
    }

    objTraversed.pop();

    return selfNode;
}


/**
 * Clones all object tree. Class of original object is not preserved. Returns `self`
 *
 * @param {Object} self An object to be extended
 * @param {Boolean} onlyEnumerable Optional `true` to use only enumerable properties
 * @return {Object}
 */
function deepClone({{# def.arg }} onlyEnumerable) {
    {{# def.varSelf }}
    var clonedObject;
    if (self instanceof Date) clonedObject = new Date(self);
    else if (self instanceof RegExp) clonedObject = new RegExp(self);
    else {
        clonedObject = Array.isArray(self) ? [] : {};
        _extendTree(clonedObject, self, onlyEnumerable, false, []);
    }
    {{# def.return:clonedObject }}
}


/**
 * Returns array of enumerable properties of the object
 *
 * @param {Object} self object to return keys of
 * @return {Array}
 */
function keys({{# def.oneArg }}) {
    var keys = Object.keys({{# def.self }});
    {{# def.return:keys }}
}


/**
 * Returns array of all property names of an object `self` (including non-enumerbale).
 * To get only enumerable properties, use `Object.keys()`.
 *
 * @param {Object} self An object to get all properties of.
 * @return {Array}
 */
function allKeys({{# def.oneArg }}) {
    var keys = Object.getOwnPropertyNames({{# def.self }});
    {{# def.return:keys }}
}


/**
 * Returns array of values of the object's keys
 *
 * @param {Object} self object to return values from
 * @return {Array}
 */
{{## def.valuesCode:
    arr[arr.length] = self[key];
#}}

function values({{# def.arg }} onlyEnumerable) {
    var arr = [];
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.valuesCode }}' }; }}
    {{# def.eachKey:params }}
    {{# def.return:arr }}
}


/**
 * An analogue of `indexOf` method of Array prototype.
 * Returns the `key` of `searchElement` in the object `self`.
 * As object keys are unsorted, if there are several keys that hold `searchElement` any of them can be returned. Use `allKeysOf` to return all keys.
 * All own properties are searched (not those inherited via prototype chain), including non-enumerable properties (unless `onlyEnumerable` is truthy).
 *
 * @param {Object} self An object to search a value in
 * @param {Any} searchElement An element that will be searched. An exact equality is tested, so `0` is not the same as `'0'`.
 * @param {Boolean} onlyEnumerable An optional true to search among enumerable properties only.
 * @return {String}
 */
{{## def.keyOfCode:
    if (searchElement === self[key]) {
        foundKey = key;
        break;
    }
#}}

function keyOf({{# def.arg }} searchElement, onlyEnumerable) {
    var foundKey;
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.keyOfCode }}' }; }}
    {{# def.eachKey:params }}
    {{# def.return:foundKey }}
}


/**
 * Works similarly to the previous function, but returns the array of keys holding `searchElement` as their value.
 *
 * @param {Object} self An object to search a value in
 * @param {Any} searchElement An element that will be searched. An exact equality is tested, so `0` is not the same as `'0'`.
 * @param {Boolean} onlyEnumerable An optional true to search among enumerable properties only.
 * @return {Array<String>}
 */
{{## def.allKeysOfCode:
    if (searchElement === self[key]) {
        foundKeys[foundKeys.length] = key;
    }
#}}

function allKeysOf({{# def.arg }} searchElement, onlyEnumerable) {
    var foundKeys = [];
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.allKeysOfCode }}' }; }}
    {{# def.eachKey:params }}
    {{# def.return:foundKeys }}
}


/**
 * An analogue of [forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) method of Array prototype.
 * Iterates all own properties of `self` (or only enumerable own properties if `onlyEnumerable` is truthy) calling callback for each key.
 * This method should not be used with arrays, it will include `length` property in iteration.
 * To iterate array-like objects (e.g., `arguments` pseudo-array) use:
 * ```
 * _.forEach(arguments, callback, thisArg);
 * ```
 * Function returns `self` to allow [chaining](proto.js.html)
 *
 * @param {Object} self An object which properties will be iterated
 * @param {Function} callback Callback is passed `value`, `key` and `self`, its return value is not used.
 * @param {Object} thisArg An optional context of iteration (the valueof `this`), will be undefined if this parameter is not passed.
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 */
{{## def.eachKeyCode:
    callback.call(thisArg, self[key], key, self);
#}}

function eachKey({{# def.arg }} callback, thisArg, onlyEnumerable) {
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.eachKeyCode }}' }; }}
    {{# def.eachKey:params }}
    return {{# def.this }};
}


/**
 * An analogue of [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) method of Array prototype.
 * Returns the object that is the result of the application of callback to values in all own properties of `self` (or only enumerable own properties if `onlyEnumerable` is truthy).
 * The returned object will be the instance of the same class as `self`.
 * Property descriptors of the returned object will have the same `enumerable`, `configurable` and `writable` settings as the properties of `self`.
 * This method should not be used with arrays, it will include `length` property in iteration.
 * To map array-like objects use:
 * ```
 * var result = _.map(arguments, callback, thisArg);
 * ```
 *
 * @param {Object} self An object which properties will be iterated
 * @param {Function} callback Callback is passed `value`, `key` and `self` and should return value that will be included in the map.
 * @param {Object} thisArg An optional context of iteration (the valueof `this`), will be undefined if this parameter is not passed.
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Object}
 */
{{## def.mapCode:
    descriptors[key] = Object.getOwnPropertyDescriptor(self, key);
    descriptors[key].value = callback.call(thisArg, self[key], key, self);
#}}

function mapKeys({{# def.arg }} callback, thisArg, onlyEnumerable) {
    {{# def.varSelf }}
    var descriptors = {};
    {{ var params = { obj: 'self', code: '{{# def.mapCode}}' }; }}
    {{# def.eachKey:params }}

    var obj = Object.create(self.constructor.prototype, descriptors);
    {{# def.return:obj }}
}


/**
 * An analogue of [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) method of Array prototype.
 * This method reduces the object to a single value. Iteration order is impossible to control with object.
 * This method should not be used with arrays, it will include `length` property in iteration.
 * To reduce array-like objects use:
 * ```
 * var result = _.reduce(arguments, callback, initialValue, thisArg);
 * ```
 *
 * @param {Object} self An object which properties will be iterated
 * @param {Function} callback Callback is passed `previousValue`, `value`, `key` and `self` and should return value that will be used as the `previousValue` for the next `callback` call.
 * @param {Any} initialValue The initial value passed to callback as the first parameter on the first call.
 * @param {Object} thisArg An optional context of iteration (the valueof `this`), will be undefined if this parameter is not passed.
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Any}
 */
{{## def.reduceKeysCode:
    memo = callback.call(thisArg, memo, self[key], key, self);
#}}

function reduceKeys({{# def.arg }} callback, initialValue, thisArg, onlyEnumerable) {
    var memo = initialValue;
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.reduceKeysCode }}' }; }}
    {{# def.eachKey:params }}
    {{# def.return:memo }}
}


/**
 * An analogue of [filter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) method of Array prototype.
 * Returns the new object with keys for which callback returns true.
 * Property descriptors of the returned object will have the same `enumerable`, `configurable` and `writable` settings as the properties of `self`.
 * To filter array-like objects use:
 * ```
 * var result = _.filter(arguments, callback, thisArg);
 * ```
 *
 * @param {Object} self An object which properties will be iterated
 * @param {Function} callback Callback is passed `value`, `key` and `self`. If it returns truthy value, the key/value will be included in the resulting object.
 * @param {Object} thisArg An optional context of iteration (the valueof `this`), will be undefined if this parameter is not passed.
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Object}
 */
{{## def.filterKeysCode:
    if (callback.call(thisArg, self[key], key, self))
        descriptors[key] = Object.getOwnPropertyDescriptor(self, key);
#}}

function filterKeys({{# def.arg }} callback, thisArg, onlyEnumerable) {
    var descriptors = {};
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.filterKeysCode }}' }; }}
    {{# def.eachKey:params }}
    var obj = Object.create(self.constructor.prototype, descriptors);
    {{# def.return:obj }}
}


var _passed = {}
    , _didNotPass = {};

/**
 * An analogue of [some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) method of Array prototype.
 *
 * @param {Object} self An object which properties will be iterated
 * @param {Function} callback Callback is passed `value`, `key` and `self`. If it returns truthy value, the function immeaditely returns `true`.
 * @param {Object} thisArg An optional context of iteration (the valueof `this`), will be undefined if this parameter is not passed.
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Boolean}
 */
{{## def.someKeyCode:
    if (callback.call(thisArg, self[key], key, self))
        return true;
#}}

function someKey({{# def.arg }} callback, thisArg, onlyEnumerable) {
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.someKeyCode }}' }; }}
    {{# def.eachKey:params }}
    {{# def.return:false }}
}


/**
 * An analogue of [every](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) method of Array prototype.
 *
 * @param {Object} self An object which properties will be iterated
 * @param {Function} callback Callback is passed `value`, `key` and `self`. If it returns falsy value, the function immeaditely returns `false`.
 * @param {Object} thisArg An optional context of iteration (the valueof `this`), will be undefined if this parameter is not passed.
 * @param {Boolean} onlyEnumerable An optional `true` to iterate enumerable properties only.
 * @return {Boolean}
 */
{{## def.everyKeyCode:
    if (!callback.call(thisArg, self[key], key, self))
        return false;
#}}

function everyKey({{# def.arg }} callback, thisArg, onlyEnumerable) {
    {{# def.varSelf }}
    {{ var params = { obj: 'self', code: '{{# def.everyKeyCode }}' }; }}
    {{# def.eachKey:params }}
    {{# def.return:true }}
}


/**
 * Returns object of the same class with only specified keys, that are passed as string parameters or array(s) of keys.
 *
 * @param {Object} self an object to pick keys from
 * @param {List<String|Array>} arguments list of keys (or array(s) of keys)
 * @return {Object}
 */
{{## def.start: {{= it.mode == 'functions' ? '1' : '0' }} #}}
{{## def.argKeys: var keys = concat.apply(Array.prototype, arguments); #}}

function pickKeys({{# def.oneArg }}) { // , ... keys
    {{# def.varSelf }}
    {{# def.argKeys }}
    var obj = Object.create(self.constructor.prototype);
    for (var i={{#def.start}}; i<keys.length; i++) {
        var key = keys[i];
        if (self.hasOwnProperty(key)) obj[key] = self[key];
    }
    {{# def.return:obj }};
}


/**
 * Returns object of the same class without specified keys, that are passed as string parameters or array(s) of keys.
 *
 * @param {Object} self an object to omit keys in
 * @param {List<String|Array>} arguments list of keys (or array of keys)
 * @return {Object}
 */
function omitKeys({{# def.oneArg}}) { // , ... keys
    {{# def.varSelf }}
    var clonedObject, onlyEnumerable;
    {{# def.clone }}
    {{# def.argKeys }}
    for (var i={{#def.start}}; i<keys.length; i++) {
        delete clonedObject[keys[i]];
    }
    {{# def.return:clonedObject }};    
}


/**
 * Performs deep equality test of the object. Does not work with recursive objects
 * @param  {Any} self object to compare
 * @param  {Any} obj object to compare
 * @return {Boolean}
 */
function isEqual({{# def.arg }} obj) {
    {{# def.varSelf }}
    var result;
    if (self === obj) {
        result = self !== 0 || 1/self == 1/obj; // 0 and -0 are considered not equal, although 0 === -0 is true
        {{# def.return:result }}
    }
    if (self == null || obj == null) {
        {{# def.return:false }}
    }
    var className = self.constructor.name;
    if (className != obj.constructor.name) {
        {{# def.return:false }}
    }
    switch (className) {
        case 'String':
            result = self == String(obj);
            break;
        case 'Number':
            result = self != +self ? obj != +obj : (self == 0 ? 1/self == 1/obj : self == +obj);
            break;
        case 'Date':
        case 'Boolean':
            result = +self == +obj;
            break;
        case 'RegExp':
            result = self.source == obj.source
                    && self.global == obj.global
                    && self.multiline == obj.multiline
                    && self.ignoreCase == obj.ignoreCase;
            break;
        default:
            if (typeof self != 'object' || typeof obj != 'object') {
                {{# def.return:false }}
            }

            if (Array.isArray(self)) {
                if (self.length != obj.length) {
                    {{# def.return:false }}
                }
                {{# def.iter:self }} {
                    result = {{? it.mode == 'functions' }}
                        isEqual(self[i], obj[i]);
                    {{??}}
                        isEqual.call({self: self[i]}, obj[i]).self;
                    {{?}}
                    if (!result) {
                        {{# def.return:false }}
                    }
                }
                {{# def.return:true }}
            } else {
                if ({{#def.allKeys:'self'}}.length != {{#def.allKeys:'obj'}}.length) {
                    {{# def.return:false }}
                }
                {{
                    var isEqualCode = 'result = '
                                    + (it.mode == 'functions'
                                        ? 'isEqual(self[key], obj[key]);'
                                        : 'isEqual.call({self: self[key]}, obj[key]).self;' )
                                    + 'if (!result) break;';
                }}
                result = true;
                {{ var params = { obj: 'self', code: isEqualCode }; }}
                {{# def.eachKeyAll:params }}
                {{# def.return:result }}
            }
    }

    {{# def.return:result }}
}


/**
 * The opposite of isEqual
 * @param  {Any} self object to compare
 * @param  {Any} obj object to compare
 * @return {Boolean}
 */
function isNot({{# def.arg }} obj) {
    var equal = {{? it.mode == 'functions' }}
        !isEqual(self, obj);
    {{??}}
        !isEqual.call({self: this.self}, obj).self;
    {{?}}

    {{# def.return:equal }}
}