Compare to underscorejs if it pleases you. Once again, I hope this is well commented. Please let me know what comments/improvements I can add.
Please review all aspects of this code.
/***************************************************************************************************
**UTILITY
- additional coverage for looping, type checking, extending ...
- consistent ordering, naming conventions ...
- fewer function branches
- increased type checking and modularity
- vector based unit testing
***************************************************************************************************/
// self used to hold client or server side global ( window or exports )
(function (self, undef) {
"use strict";
// holds (P)ublic properties
var $P = {},
// holds p(R)ivate properties
$R = {},
// native methods (alphabetical order)
nativeFilter = Array.prototype.filter,
nativeIsArray = Array.isArray,
nativeSlice = Array.prototype.slice,
nativeSome = Array.prototype.some,
nativeToString = Object.prototype.toString;
$P.noConflict = (function () {
// $R.g holds the single global variable, used to hold all packages
// methods
$R.g = '$A';
$R.previous = self[$R.g];
// utility is required by all other packages
// start the "pack"age list
$P.pack = {
utility: true
};
return function () {
var temp = self[$R.g];
self[$R.g] = $R.previous;
return temp;
};
}());
$P.isType = function (type, obj) {
return $P.getType(obj) === type;
};
// returns type in a captialized string form
// typeof is only accurate for function, string, number, boolean, and
// undefined. null and array are both incorrectly reported as object
$P.getType = function (obj) {
return nativeToString.call(obj).slice(8, -1);
};
$P.isFalse = function (obj) {
return obj === false;
};
$P.isUndefined = function (obj) {
return obj === undef;
};
$P.isNull = function (obj) {
return obj === null;
};
$P.isNumber = function (value) {
return (typeof value === 'number') && isFinite(value);
};
// detects null or undefined
$P.isGone = function (obj) {
return obj == null;
};
// detects anything but null or undefined
$P.isHere = function (obj) {
return obj != null;
};
// detects null, undefined, NaN, ('' ""), 0, -0, false
$P.isFalsy = function (obj) {
return !obj;
};
// detects any thing but null, undefined, NaN, ('' ""), 0, -0, false
$P.isTruthy = function (obj) {
return !!obj;
};
// shortcut as their are only two primitive boolean values
// detects a "boxed" boolean as well
$P.isBoolean = function (obj) {
return obj === true || obj === false ||
nativeToString.call(obj) === '[object Boolean]';
};
// delegates to native
$P.isArray = nativeIsArray || function (obj) {
return nativeToString.call(obj) === '[object Array]';
};
// jslint prefers {}.constructor(obj) over Object(obj)
$P.isObjectAbstract = function (obj) {
// return obj === Object(obj);
return !!(obj && (obj === {}.constructor(obj)));
};
// has a numeric length property
$P.isArrayAbstract = function (obj) {
return !!(obj && obj.length === +obj.length);
};
$P.someIndex = function (arr, func, con) {
var ind,
len;
// prevent type errors, note function is validated
if ((arr == null) || (arr.length !== +arr.length) || (typeof func !== 'function')) {
return;
}
// delegate to native some()
if (nativeSome && arr.some === nativeSome) {
return arr.some(func, con);
}
// if the function passes back a truthy value, the loop will terminate
for (ind = 0, len = arr.length; ind < len; ind++) {
if (func.call(con, arr[ind], ind, arr)) {
return true;
}
}
return false;
};
$P.someKey = function (obj, func, con) {
var key;
// prevent type errors
// for-in will filter out null and undefined
if ((obj == null) || (typeof func !== 'function')) {
return;
}
for (key in obj) {
if (obj.hasOwnProperty(key)) {
// if the function passes back a truthy value,
// the loop will terminate
if (func.call(con, obj[key], key, obj)) {
return true;
}
}
}
return false;
};
// loop through space separated "tokens" in a string
$P.eachString = function (str, func, con) {
// prevent type errors
if (typeof str !== 'string' || str === "" ||
typeof func !== 'function') {
return;
}
$P.someIndex(str.split(/\s+/), func, con);
};
// does not extend through the prototype chain
// implemented for objects only
$P.extend = function (obj) {
// loop througth elements beyond obj
$P.someIndex(nativeSlice.call(arguments, 1), function (val) {
// extend it
$P.someKey(val, function (val_inner, key) {
obj[key] = val_inner;
});
});
return obj;
};
// extends non-prototype properties from obj2 on to obj1
$P.extendSafe = function (obj1, obj2) {
var key;
for (key in obj2) {
if (obj2.hasOwnProperty(key)) {
if (obj1.hasOwnProperty(key)) {
throw "naming collision: " + key;
}
obj1[key] = obj2[key];
}
}
return obj1;
};
// if incorrect types are passed, it will return an empty array
// arrays only
$P.filter = function (arr, func, con) {
var results = [];
if ((arr == null) || (typeof func !== 'function')) {
return results;
}
if (nativeFilter && arr.filter === nativeFilter) {
return arr.filter(func, con);
}
$P.someIndex(arr, function (val, ind, arr) {
if (func.call(con, val, ind, arr)) {
results.push(val);
}
});
return results;
};
$P.clone = function (obj) {
return $P.extend({}, obj);
};
$P.someIndex(['Arguments', 'Function', 'String', 'Number',
'Date', 'RegExp', 'Object'], function (val) {
$P['is' + val] = function (obj) {
return $P.isType(val, obj);
};
});
$P.addU = function (str, suf) {
return str + '_' + suf;
};
$P.removeU = function (str) {
if (typeof str !== 'string' || str === "") {
return false;
}
return str.slice(str, str.lastIndexOf("_"));
};
$P.removeSuffix = function (str, len) {
if (typeof str !== 'string' || str === "") {
return false;
}
return str.slice(0, -len);
};
// equivalent to IIFE but "nicer" syntax
$P.runTest = (function () {
var tests = {};
return function (name, arr, func) {
tests[name] = func.apply(this, arr);
};
}());
self[$R.g] = $P.extendSafe($P, {});
// this will hold the global object - window or exports
}(this));
someIndex()
and someKey()
compared to underscore each()
So the way I chose to write someIndex()
and someKey()
was organically or as a process. I wanted to abstract the looping idioms I got tired of writing into methods. Because for
and for-in
can be broke out of I decided that some()
was a better building block then each()
from my perspective. After, I did this I compared what I had to underscore. My design choices were different in other ways as well. -- Please note, if you need speed just use a for or for-in loop, if you need clean concise code, use the abstraction -- The other way I diverged from underscore is that I wrote some separately for arrays and objects, because when I code I always know which one I am looping through, hence I wanted to express this knowledge in my code - someIndex() and someKey(). Another way I diverged was that I type check all the inputs in one line, for some reason underscore chooses not to type check the callback function.
The other way I diverged is I don't check for any internal breaks, that are internal use only, in underscore this is shown as an equality check for breaker
. Also, as in all my code b.c. JS is not block scoped yet, I put my var definitions up top.
Also, regarding elegance, I feel my code is more elegant, as the type checking is consistent and consolidated, my var declarations are more naturally inline with how the interpreter reads them, and comments, white space, and a 4 space indent level make the code easier to read.
That's the gist of it, please let me know if I can elaborate more.
Below is the code for both:
$P.someIndex = function (arr, func, con) {
var ind,
len;
// prevent type errors, note function is validated
if ((arr == null) || (arr.length !== +arr.length) || (typeof func !== 'function')) {
return;
}
// delegate to native some()
if (nativeSome && arr.some === nativeSome) {
return arr.some(func, con);
}
// if the function passes back a truthy value, the loop will terminate
for (ind = 0, len = arr.length; ind < len; ind++) {
if (func.call(con, arr[ind], ind, arr)) {
return true;
}
}
return false;
};
var each = _.each = _.forEach = function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = _.keys(obj);
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
};