diff --git a/README.md b/README.md index 1aa8ba0..b1d9225 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ npm install bue ## Usage ```javascript -var biu = require('bue'); +var bue = require('bue'); bue.isArray([1,2,3]); // => true @@ -33,6 +33,11 @@ All the functions used in Bue is based on lodash(v4.0.0-pre), some of them have ``` bue.clone(value) ``` +- eq + Compare two values(arrays, booleans, Date objects, numbers) if they ware equivalent. + ``` + bue.eq(object, other); + ``` - isArray Checks if `value` is classified as an `Array` object. ``` @@ -198,6 +203,11 @@ All the functions used in Bue is based on lodash(v4.0.0-pre), some of them have ### Collection Functions > `bue.iteratee`: a function that a invokes `func` with the arguments of the created function. +- contains + Check if `target` is in `collection` + ``` + bue.contains(collection, value, [fromIndex=0]) + ``` - each Iterates over elements of `collection` invoking `iteratee` for each element. The iteratee is invoked with three arguments: (value, index|key, collection). Iteratee functions may exit iteration early by explicitly returning `false`. ``` diff --git a/lib/collection/includes.js b/lib/collection/includes.js new file mode 100644 index 0000000..b03dc09 --- /dev/null +++ b/lib/collection/includes.js @@ -0,0 +1,57 @@ +var baseIndexOf = require('../internal/baseIndexOf'), + getLength = require('../internal/getLength'), + isArray = require('../lang/isArray'), + isIterateeCall = require('../internal/isIterateeCall'), + isLength = require('../internal/isLength'), + isString = require('../lang/isString'), + values = require('../object/values'); + +/* Native method references for those with the same name as other `lodash` methods. */ +var nativeMax = Math.max; + +/** + * Checks if `target` is in `collection` using + * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero) + * for equality comparisons. If `fromIndex` is negative, it's used as the offset + * from the end of `collection`. + * + * @static + * @memberOf bue + * @alias contains, include + * @category Collection + * @param {Array|Object|string} collection The collection to search. + * @param {*} target The value to search for. + * @param {number} [fromIndex=0] The index to search from. + * @param- {Object} [guard] Enables use as a callback for functions like `bue.reduce`. + * @returns {boolean} Returns `true` if a matching element is found, else `false`. + * @example + * + * bue.includes([1, 2, 3], 1); + * // => true + * + * bue.includes([1, 2, 3], 1, 2); + * // => false + * + * bue.includes({ 'user': 'fred', 'age': 40 }, 'fred'); + * // => true + * + * bue.includes('pebbles', 'eb'); + * // => true + */ +function includes(collection, target, fromIndex, guard) { + var length = collection ? getLength(collection) : 0; + if (!isLength(length)) { + collection = values(collection); + length = collection.length; + } + if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) { + fromIndex = 0; + } else { + fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0); + } + return (typeof collection == 'string' || !isArray(collection) && isString(collection)) + ? (fromIndex <= length && collection.indexOf(target, fromIndex) > -1) + : (!!length && baseIndexOf(collection, target, fromIndex) > -1); +} + +module.exports = includes; diff --git a/lib/index.js b/lib/index.js index 691d8a9..c86c2e3 100755 --- a/lib/index.js +++ b/lib/index.js @@ -1,6 +1,7 @@ module.exports = { // Language Functions 'clone': require('./lang/clone'), + 'eq': require('./lang/isEqual'), 'isArray': require('./lang/isArray'), 'isBoolean': require('./lang/isBoolean'), 'isDate': require('./lang/isDate'), @@ -40,6 +41,7 @@ module.exports = { 'escapeRegExp': require('./string/escapeRegExp'), // Collection Functions + 'contains': require('./collection/includes'), 'each': require('./collection/forEach'), 'eachRight': require('./collection/forEachRight'), 'map': require('./collection/map'), diff --git a/lib/internal/baseIndexOf.js b/lib/internal/baseIndexOf.js new file mode 100644 index 0000000..a3182f8 --- /dev/null +++ b/lib/internal/baseIndexOf.js @@ -0,0 +1,27 @@ +var indexOfNaN = require('./indexOfNaN'); + +/** + * The base implementation of `bue.indexOf` without support for binary searches. + * + * @private + * @param {Array} array The array to search. + * @param {*} value The value to search for. + * @param {number} fromIndex The index to search from. + * @returns {number} Returns the index of the matched value, else `-1`. + */ +function baseIndexOf(array, value, fromIndex) { + if (value !== value) { + return indexOfNaN(array, fromIndex); + } + var index = fromIndex - 1, + length = array.length; + + while (++index < length) { + if (array[index] === value) { + return index; + } + } + return -1; +} + +module.exports = baseIndexOf; diff --git a/lib/internal/bindCallback.js b/lib/internal/bindCallback.js new file mode 100644 index 0000000..cdc7f49 --- /dev/null +++ b/lib/internal/bindCallback.js @@ -0,0 +1,39 @@ +var identity = require('../utility/identity'); + +/** + * A specialized version of `baseCallback` which only supports `this` binding + * and specifying the number of arguments to provide to `func`. + * + * @private + * @param {Function} func The function to bind. + * @param {*} thisArg The `this` binding of `func`. + * @param {number} [argCount] The number of arguments to provide to `func`. + * @returns {Function} Returns the callback. + */ +function bindCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; + } + if (thisArg === undefined) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 3: return function(value, index, collection) { + return func.call(thisArg, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(thisArg, accumulator, value, index, collection); + }; + case 5: return function(value, other, key, object, source) { + return func.call(thisArg, value, other, key, object, source); + }; + } + return function() { + return func.apply(thisArg, arguments); + }; +} + +module.exports = bindCallback; diff --git a/lib/internal/indexOfNaN.js b/lib/internal/indexOfNaN.js new file mode 100644 index 0000000..05b8207 --- /dev/null +++ b/lib/internal/indexOfNaN.js @@ -0,0 +1,23 @@ +/** + * Gets the index at which the first occurrence of `NaN` is found in `array`. + * + * @private + * @param {Array} array The array to search. + * @param {number} fromIndex The index to search from. + * @param {boolean} [fromRight] Specify iterating from right to left. + * @returns {number} Returns the index of the matched `NaN`, else `-1`. + */ +function indexOfNaN(array, fromIndex, fromRight) { + var length = array.length, + index = fromIndex + (fromRight ? 0 : -1); + + while ((fromRight ? index-- : ++index < length)) { + var other = array[index]; + if (other !== other) { + return index; + } + } + return -1; +} + +module.exports = indexOfNaN; diff --git a/lib/internal/isLength.js b/lib/internal/isLength.js new file mode 100644 index 0000000..2092987 --- /dev/null +++ b/lib/internal/isLength.js @@ -0,0 +1,20 @@ +/** + * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer) + * of an array-like value. + */ +var MAX_SAFE_INTEGER = 9007199254740991; + +/** + * Checks if `value` is a valid array-like length. + * + * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a valid length, else `false`. + */ +function isLength(value) { + return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; +} + +module.exports = isLength; diff --git a/lib/lang/isEqual.js b/lib/lang/isEqual.js new file mode 100644 index 0000000..7207178 --- /dev/null +++ b/lib/lang/isEqual.js @@ -0,0 +1,54 @@ +var baseIsEqual = require('../internal/baseIsEqual'), + bindCallback = require('../internal/bindCallback'); + +/** + * Performs a deep comparison between two values to determine if they are + * equivalent. If `customizer` is provided it's invoked to compare values. + * If `customizer` returns `undefined` comparisons are handled by the method + * instead. The `customizer` is bound to `thisArg` and invoked with up to + * three arguments: (value, other [, index|key]). + * + * **Note:** This method supports comparing arrays, booleans, `Date` objects, + * numbers, `Object` objects, regexes, and strings. Objects are compared by + * their own, not inherited, enumerable properties. Functions and DOM nodes + * are **not** supported. Provide a customizer function to extend support + * for comparing other values. + * + * @static + * @memberOf bue + * @alias eq + * @category Lang + * @param {*} value The value to compare. + * @param {*} other The other value to compare. + * @param {Function} [customizer] The function to customize value comparisons. + * @param {*} [thisArg] The `this` binding of `customizer`. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. + * @example + * + * var object = { 'user': 'fred' }; + * var other = { 'user': 'fred' }; + * + * object == other; + * // => false + * + * bue.isEqual(object, other); + * // => true + * + * // using a customizer callback + * var array = ['hello', 'goodbye']; + * var other = ['hi', 'goodbye']; + * + * bue.isEqual(array, other, function(value, other) { + * if (bue.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) { + * return true; + * } + * }); + * // => true + */ +function isEqual(value, other, customizer, thisArg) { + customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined; + var result = customizer ? customizer(value, other) : undefined; + return result === undefined ? baseIsEqual(value, other, customizer) : !!result; +} + +module.exports = isEqual; diff --git a/test/collection.js b/test/collection.js index dd32a2c..4d1f3fe 100644 --- a/test/collection.js +++ b/test/collection.js @@ -9,7 +9,16 @@ describe ('Collection', function() { beforeEach(function() { iterator = 0; - }) + }); + + describe('#contains(collection, value)', function() { + it('Returns true if the equivalent values found', function() { + expect(bue.contains([1, 2, 3], 1)).to.equal(true); + expect(bue.contains([1, 2, 3], 1, 2)).to.equal(false); + expect(bue.contains({ 'user': 'fred', 'age': 40 }, 'fred')).to.equal(true); + expect(bue.contains('pebbles', 'eb')).to.equal(true); + }); + }); describe('#each(collection)', function() { it('should return each element key & value', function() { diff --git a/test/language.js b/test/language.js index 94cdda1..23f5166 100644 --- a/test/language.js +++ b/test/language.js @@ -17,6 +17,16 @@ describe ('Language', function() { }); }); + describe('#eq(object, target)', function() { + var object = { 'user': 'fred' }; + var other = { 'user': 'fred' }; + + it('Returns true if the values are equivalent', function() { + expect(bue.eq(object, other)).to.equal(true); + expect(object === other).to.equal(false); + }); + }); + describe('#isArray(value)', function() { it('should return if `value` is classified as an `Array` object', function() { expect(bue.isArray('abc')).to.equal(false);