Source: lib/ObjectX.js

/**

    Advanced Object prototype functions
    @author Tilak Patidar <tilakpatidar@gmail.com>
    @constructor

*/
var _ = require("underscore");
var ObjectX = function() {


};

if (!Array.prototype.find) {
    Array.prototype.find = function(predicate) {
        if (this === null) {
            throw new TypeError('Array.prototype.find called on null or undefined');
        }
        if (typeof predicate !== 'function') {
            throw new TypeError('predicate must be a function');
        }
        var list = Object(this);
        var length = list.length >>> 0;
        var thisArg = arguments[1];
        var value;

        for (var i = 0; i < length; i++) {
            value = list[i];
            if (predicate.call(thisArg, value, i, list)) {
                return value;
            }
        }
        return undefined;
    };
}

if (!Object.freeze || typeof Object.freeze !== 'function') {
    throw Error('ES5 support required');
}
// from ES5
var O = Object,
    OP = O.prototype,
    create = O.create,
    defineProperty = O.defineProperty,
    defineProperties = O.defineProperties,
    getOwnPropertyNames = O.getOwnPropertyNames,
    getOwnPropertyDescriptor = O.getOwnPropertyDescriptor,
    getPrototypeOf = O.getPrototypeOf,
    freeze = O.freeze,
    isFrozen = O.isFrozen,
    isSealed = O.isSealed,
    seal = O.seal,
    isExtensible = O.isExtensible,
    preventExtensions = O.preventExtensions,
    hasOwnProperty = OP.hasOwnProperty,
    toString = OP.toString,
    isArray = Array.isArray,
    slice = Array.prototype.slice;
// Utility functions; some exported
function defaults(dst, src) {
    getOwnPropertyNames(src).forEach(function(k) {
        if (!hasOwnProperty.call(dst, k)) defineProperty(
            dst, k, getOwnPropertyDescriptor(src, k)
        );
    });
    return dst;
};
var isObject = function(o) {
    return o === Object(o)
};
var isPrimitive = function(o) {
    return o !== Object(o)
};
var isFunction = function(f) {
    return typeof(f) === 'function'
};
var signatureOf = function(o) {
    return toString.call(o)
};
var HASWEAKMAP = (function() { // paranoia check
    try {
        var wm = new WeakMap();
        wm.set(wm, wm);
        return wm.get(wm) === wm;
    } catch (e) {
        return false;
    }
})();
// exported
function is(x, y) {
    return x === y ?
        x !== 0 ? true :
        (1 / x === 1 / y) // +-0
        :
        (x !== x && y !== y); // NaN
};

function isnt(x, y) {
    return !is(x, y)
};
var defaultCK = {
    descriptors: true,
    extensibility: true,
    enumerator: getOwnPropertyNames
};

function equals(x, y, ck) {
    var vx, vy;
    if (HASWEAKMAP) {
        vx = new WeakMap();
        vy = new WeakMap();
    }
    ck = defaults(ck || {}, defaultCK);
    return (function _equals(x, y) {
        if (isPrimitive(x)) return is(x, y);
        if (isFunction(x)) return is(x, y);
        // check deeply
        var sx = signatureOf(x),
            sy = signatureOf(y);
        var i, l, px, py, sx, sy, kx, ky, dx, dy, dk, flt;
        if (sx !== sy) return false;
        switch (sx) {
            case '[object Array]':
            case '[object Object]':
                if (ck.extensibility) {
                    if (isExtensible(x) !== isExtensible(y)) return false;
                    if (isSealed(x) !== isSealed(y)) return false;
                    if (isFrozen(x) !== isFrozen(y)) return false;
                }
                if (vx) {
                    if (vx.has(x)) {
                        // console.log('circular ref found');
                        return vy.has(y);
                    }
                    vx.set(x, true);
                    vy.set(y, true);
                }
                px = ck.enumerator(x);
                py = ck.enumerator(y);
                if (ck.filter) {
                    flt = function(k) {
                        var d = getOwnPropertyDescriptor(this, k);
                        return ck.filter(d, k, this);
                    };
                    px = px.filter(flt, x);
                    py = py.filter(flt, y);
                }
                if (px.length != py.length) return false;
                px.sort();
                py.sort();
                for (i = 0, l = px.length; i < l; ++i) {
                    kx = px[i];
                    ky = py[i];
                    if (kx !== ky) return false;
                    dx = getOwnPropertyDescriptor(x, ky);
                    dy = getOwnPropertyDescriptor(y, ky);
                    if ('value' in dx) {
                        if (!_equals(dx.value, dy.value)) return false;
                    } else {
                        if (dx.get && dx.get !== dy.get) return false;
                        if (dx.set && dx.set !== dy.set) return false;
                    }
                    if (ck.descriptors) {
                        if (dx.enumerable !== dy.enumerable) return false;
                        if (ck.extensibility) {
                            if (dx.writable !== dy.writable)
                                return false;
                            if (dx.configurable !== dy.configurable)
                                return false;
                        }
                    }
                }
                return true;
            case '[object RegExp]':
            case '[object Date]':
            case '[object String]':
            case '[object Number]':
            case '[object Boolean]':
                return '' + x === '' + y;
            default:
                throw TypeError(sx + ' not supported');
        }
    })(x, y);
}


//  Install
var obj2specs = function(src) {
    var specs = create(null);
    getOwnPropertyNames(src).forEach(function(k) {
        specs[k] = {
            value: src[k],
            configurable: true,
            writable: true,
            enumerable: false
        };
    });
    return specs;
};
var defaultProperties = function(dst, descs) {
    getOwnPropertyNames(descs).forEach(function(k) {
        if (!hasOwnProperty.call(dst, k)) defineProperty(
            dst, k, descs[k]
        );
    });
    return dst;
};
/**
       Returns deep copy of any Object. Similar to java's Object.clone
       @static
       @param {Object} src
*/
ObjectX.clone = function clone(src, deep, ck) {
    var wm;
    if (deep && HASWEAKMAP) {
        wm = new WeakMap();
    }
    ck = defaults(ck || {}, defaultCK);
    return (function _clone(src) {
        // primitives and functions
        if (isPrimitive(src)) return src;
        if (isFunction(src)) return src;
        var sig = signatureOf(src);
        switch (sig) {
            case '[object Array]':
            case '[object Object]':
                if (wm) {
                    if (wm.has(src)) {
                        // console.log('circular ref found');
                        return src;
                    }
                    wm.set(src, true);
                }
                var isarray = isArray(src);
                var dst = isarray ? [] : create(getPrototypeOf(src));
                ck.enumerator(src).forEach(function(k) {
                    // Firefox forbids defineProperty(obj, 'length' desc)
                    if (isarray && k === 'length') {
                        dst.length = src.length;
                    } else {
                        if (ck.descriptors) {
                            var desc = getOwnPropertyDescriptor(src, k);
                            if (ck.filter && !ck.filter(desc, k, src)) return;
                            if (deep && 'value' in desc)
                                desc.value = _clone(src[k]);
                            defineProperty(dst, k, desc);
                        } else {
                            dst[k] = _clone(src[k]);
                        }
                    }
                });
                if (ck.extensibility) {
                    if (!isExtensible(src)) preventExtensions(dst);
                    if (isSealed(src)) seal(dst);
                    if (isFrozen(src)) freeze(dst);
                }
                return dst;
            case '[object RegExp]':
            case '[object Date]':
            case '[object String]':
            case '[object Number]':
            case '[object Boolean]':
                return deep ? new src.constructor(src.valueOf()) : src;
            default:
                throw TypeError(sig + ' is not supported');
        }
    })(src);
};

/**
        Check if two objects are equivalent.
        @param {Object} a
        @param {Object} b
        @static
        @returns {boolean}

*/
ObjectX.isEquivalent = function isEquivalent(a, b) {
    var anil = false;
    var bnil = false;

    if (a === undefined || a === null) {
        anil = true;
    }
    if (b === undefined || b === null) {
        bnil = true;
    }
    if (anil && bnil) {
        return true;
    } else if (anil === true && bnil === false) {
        return false;
    } else if (bnil === true && anil === false) {
        return false;
    }

    if (a.constructor.name !== b.constructor.name) {
        return false;
    }

    if (typeof a === "number") {
        if (a === b) {
            return true;
        } else {
            return false;
        }
    } else if (typeof a === "string") {
        if (a === b) {
            return true;
        } else {
            return false;
        }
    } else if (a.constructor.name === "Array" && b.constructor.name === "Array") {
        a.sort();
        b.sort();
    }
    // Create arrays of property names
    var aProps = Object.getOwnPropertyNames(a);
    var bProps = Object.getOwnPropertyNames(b);

    // If number of properties is different,
    // objects are not equivalent
    if (aProps.length != bProps.length) {
        return false;
    }

    for (var i = 0; i < aProps.length; i++) {
        var propName = aProps[i];

        // If values of same property are not equal,
        // objects are not equivalent
        if (typeof a[propName] === "object") {
            if (!isEquivalent(a[propName], b[propName])) {
                return false;
            }
        } else {
            if (a[propName] !== b[propName]) {
                return false;
            }
        }

    }

    // If we made it this far, objects
    // are considered equivalent
    return true;
};

/**
    Returns values of Object.
    @static
    @returns {Array}
*/
ObjectX.values = function values(dic) {
    var li = []
    _.each(dic, function(e, key) {
        li.push(key);
    });
    return li;
};
module.exports = ObjectX;