libcore
Version:
Kicks-start helpers for cross-browser libraries and different versions of nodejs
577 lines (457 loc) • 14.9 kB
JavaScript
;
/**
* @external libcore
*/
import {
object,
string,
number,
array,
regex,
date,
nativeObject,
signature as objectSignature,
REGEX,
DATE,
ARRAY,
OBJECT,
METHOD
} from "./type.js";
var Obj = Object,
O = Obj.prototype,
EACH = typeof Obj.getOwnPropertyNames === 'function' ?
es5each : es3each,
OHasOwn = O.hasOwnProperty,
ARRAY_INDEX_RE = /^[1-9][0-9]*|0$/;
function empty() {
}
function isValidObject(target) {
var signature = objectSignature(target);
switch (signature) {
case REGEX:
case DATE:
case ARRAY:
case OBJECT:
case METHOD: return signature;
}
return false;
}
/**
* Assign properties of source Object to target Object
* @alias module:libcore.assign
* @param {Object} target - the target object
* @param {Object} source - the source object containing properties
* to be assigned to target object
* @param {Object} [defaults] - object containing default properties
* which will be assigned first to
* target before source.
* @param {Boolean} [ownedOnly] - only assign properties owned by "source"
* @returns {Object} target object from first parameter
*/
function apply(value, name) {
/*jshint validthis:true */
this[name] = value;
}
/**
* Relocate and rename properties of source Object into target Object.
*
* @name libcore.rehash
* @function
* @param {Object|Function} target - the target object
* @param {Object|Function} source - the source object containing properties
* to be relocated.
* @param {Object} access - the rename map object containing "renamed property"
* as map object's property name, and
* "source property name" as map object's
* property value. (e.g. { "newname": "from source" })
* @returns {Object} target object from first parameter
*/
function applyProperties(value, name) {
/*jshint validthis:true */
var target = this;
target[0][name] = target[1][value];
target = null;
}
function assignAll(target, source, defaults) {
var onAssign = apply,
eachProperty = EACH;
if (defaults) {
eachProperty(defaults, onAssign, target, false);
}
eachProperty(source, onAssign, target);
return target;
}
/**
* Iterates all iteratable property of an object calling "handler" parameter on
* each iteration.
* @name libcore.each
* @function
* @param {Object} subject
* @param {Function} handler - the callback of each iteration of
* "subject" object's property.
* @param {*} [scope] - "this" object to use inside the "handler" parameter
* @param {boolean} [hasown] - performs checking to only include
* source object property that is overridden
* (Object.protototype.hasOwnProperty() returns true)
* when this parameter is set to true.
* @returns {Object} The subject parameter
*/
function es3each(subject, handler, scope, hasown) {
var hasOwn = OHasOwn,
noChecking = hasown === false;
var name;
if (!isValidObject(subject)) {
throw new Error("Invalid [subject] parameter.");
}
if (arguments.length > 3 && typeof hasown !== 'boolean') {
throw new Error("Invalid [hasown] hasOwnProperty parameter.");
}
if (scope === void(0)) {
scope = null;
}
for (name in subject) {
if ((noChecking || hasOwn.call(subject, name)) &&
handler.call(scope, subject[name], name, subject) === false) {
break;
}
}
return subject;
}
function es5each(subject, handler, scope, hasown) {
var hasOwn = OHasOwn,
noChecking = hasown === false;
var names, name, c, l;
if (!isValidObject(subject)) {
throw new Error("Invalid [subject] parameter.");
}
if (arguments.length > 3 && typeof hasown !== 'boolean') {
throw new Error("Invalid [hasown] hasOwnProperty parameter.");
}
if (scope === void(0)) {
scope = null;
}
names = Obj.getOwnPropertyNames(subject);
for (c = -1, l = names.length; l--;) {
name = names[++c];
if ((noChecking || hasOwn.call(subject, name)) &&
handler.call(scope, subject[name], name, subject) === false) {
break;
}
}
return subject;
}
/**
* Checks if "subject" Object contains overridden property.
* The same symantics of Object.prototype.hasOwnProperty.
*
* @name libcore.contains
* @function
* @param {Object} subject
* @param {String} property - Property Name to inspect
* @returns {boolean} True if subject Object contains property and dirty.
* False if subject Object's property do not exist or not
* dirty.
*/
/**
* Clears Object properties. This method only deletes overridden properties and
* will not fill "undefined" to non-owned properties from its prototype.
* @name libcore.clear
* @function
* @param {Object} subject
* @returns {Object} subject parameter.
*/
function applyClear() {
delete arguments[2][arguments[1]];
}
function applyFillin(value, name) {
/* jshint validthis:true */
var target = this;
if (!contains(target, name)) {
target[name] = value;
}
target = null;
}
/**
* Builds instance of "Class" parameter without executing its constructor.
* @name libcore.instantiate
* @function
* @param {Function} Class
* @param {Object} overrides
* @returns {Object} Instance created from Class without executing
* its constructor.
*/
/**
* Deep compares two scalar, array, object, regex and date objects
* @name libcore.compare
* @function
* @param {*} object1
* @param {*} object2
* @returns {boolean} True if scalar, regex, date, object properties, or array
* items of object1 is identical to object2.
*/
function compareLookback(object1, object2, references) {
var isObject = object,
isArray = array,
isRegex = regex,
isDate = date,
onCompare = onEachCompareObject,
each = EACH,
me = compareLookback,
depth = references.length;
var len, context;
switch (true) {
// prioritize same object, same type comparison
case object1 === object2: return true;
// native object comparison
case isObject(object1):
if (!isObject(object2)) {
return false;
}
// check if object is in references
if (references.lastIndexOf(object1) !== -1 &&
references.lastIndexOf(object2) !== -1) {
return true;
}
// proceed
references[depth] = object1;
references[depth + 1] = object2;
context = [object2, references, true];
each(object1, onCompare, context);
if (!context[2]) {
return false;
}
context[0] = object1;
each(object2, onCompare, context);
if (!context[2]) {
return false;
}
references.length = depth;
return true;
// array comparison
case isArray(object1):
if (!isArray(object2)) {
return false;
}
// check references
if (references.lastIndexOf(object1) !== -1 &&
references.lastIndexOf(object2) !== -1) {
return true;
}
len = object1.length;
if (len !== object2.length) {
return false;
}
// proceed
references[depth] = object1;
references[depth + 1] = object2;
for (; len--;) {
if (!me(object1[len], object2[len], references)) {
return false;
}
}
references.length = depth;
return true;
// RegExp compare
case isRegex(object1):
return isRegex(object2) && object1.source === object2.source;
// Date compare
case isDate(object1):
return isDate(object2) && object1.toString() === object2.toString();
}
return false;
}
function onEachCompareObject(value, name) {
/* jshint validthis:true */
var context = this,
target = context[0],
result = name in target ?
compareLookback(value, target[name], context[1]) :
false;
context[2] = result;
return result;
}
/**
* Clones scalar, array, object, regex or date objects
* @name libcore.clone
* @function
* @param {*} data - scalar, array, object, regex or date object to clone.
* @param {boolean} [deep] - apply deep clone to object properties or
* array items.
* @returns {*} Cloned object based from data
*/
function cloneObject(data, parents, cloned) {
var depth = parents.length,
recreated = {},
context = [recreated,
parents,
cloned];
parents[depth] = data;
cloned[depth] = recreated;
EACH(data, onEachClonedProperty, context);
parents.length = cloned.length = depth;
return recreated;
}
function cloneArray(data, parents, cloned) {
var depth = parents.length,
onProperty = onEachClonedProperty,
recreated = [],
context = [recreated,
parents,
cloned],
c = 0,
l = data.length;
parents[depth] = data;
cloned[depth] = recreated;
for (; l--; c++) {
onProperty.call(context,
data[c],
c,
data);
}
parents.length = cloned.length = depth;
return recreated;
}
function onEachClonedProperty(value, name) {
var /* jshint validthis:true */
context = this,
isNative = nativeObject(value),
parents = context[1],
cloned = context[2];
var index;
if (isNative || array(value)) {
index = parents.lastIndexOf(value);
value = index === -1 ?
(isNative ?
cloneObject :
cloneArray)(value, parents, cloned) :
cloned[index];
}
else {
value = clone(value, false);
}
context[0][name] = value;
}
function onMaxNumericIndex(value, name, context) {
if (ARRAY_INDEX_RE.test(name)) {
context[0] = Math.max(1 * name, context[0]);
}
}
export { EACH as each };
export function assign(target, source, defaults, ownedOnly) {
var onAssign = apply,
is = isValidObject,
eachProperty = EACH,
len = arguments.length;
if (!is(target)) {
throw new Error("Invalid [target] parameter.");
}
if (!is(source)) {
throw new Error("Invalid [source] parameter.");
}
if (typeof defaults === 'boolean') {
ownedOnly = defaults;
len = 2;
}
else {
ownedOnly = ownedOnly !== false;
}
if (is(defaults)) {
eachProperty(defaults, onAssign, target, ownedOnly);
}
else if (len > 2) {
throw new Error("Invalid [defaults] parameter.");
}
eachProperty(source, onAssign, target, ownedOnly);
return target;
}
export function rehash(target, source, access) {
var is = isValidObject,
context = [target, source];
if (!is(target)) {
throw new Error("Invalid [target] parameter.");
}
if (!is(source)) {
throw new Error("Invalid [source] parameter.");
}
if (!object(access)) {
throw new Error("Invalid [access] parameter.");
}
EACH(access, applyProperties, context);
context = context[0] = context[1] = null;
return target;
}
export function contains(subject, property) {
if (!string(property) && !number(property)) {
throw new Error("Invalid [property] parameter.");
}
return OHasOwn.call(subject, property);
}
export function instantiate(Class, overrides) {
empty.prototype = Class.prototype;
if (object(overrides)) {
return assign(new empty(), overrides);
}
return new empty();
}
export function clone(data, deep) {
var isNative = nativeObject(data);
deep = deep === true;
if (isNative || array(data)) {
return deep ?
(isNative ? cloneObject : cloneArray)(data, [], []) :
(isNative ? assignAll({}, data) : data.slice(0));
}
if (regex(data)) {
return new RegExp(data.source, data.flags);
}
else if (date(data)) {
return new Date(data.getFullYear(),
data.getMonth(),
data.getDate(),
data.getHours(),
data.getMinutes(),
data.getSeconds(),
data.getMilliseconds());
}
return data;
}
export function compare(object1, object2) {
return compareLookback(object1, object2, []);
}
/**
* Assign properties of source Object to target Object only if property do not
* exist or not overridden from the target Object.
* @name libcore.fillin
* @function
* @param {Object} target - the target object
* @param {Object} source - the source object containing properties
* to be assigned to target object
* @param {boolean} [hasown] - performs checking to only include
* source object property that is overridden
* (Object.protototype.hasOwnProperty() returns true)
* when this parameter is set to true.
* @returns {Object} subject parameter.
*/
export function fillin(target, source, hasown) {
if (!isValidObject(target)) {
throw new Error("Invalid [target] parameter");
}
EACH(source, applyFillin, target, hasown !== false);
return target;
}
export function clear(subject) {
EACH(subject, applyClear, null, true);
return subject;
}
export function maxObjectIndex(subject) {
var context;
if (array(subject)) {
return subject.length - 1;
}
if (isValidObject(subject)) {
context = [-1];
EACH(subject, onMaxNumericIndex, context);
return context[0];
}
return false;
}