Add baseFunctions to ensure _.mixin only iterates over own keys of source objects.

This commit is contained in:
John-David Dalton
2014-05-08 23:42:40 -07:00
parent 4155e8e0ce
commit 9b920cf813
2 changed files with 51 additions and 26 deletions

View File

@@ -1599,6 +1599,30 @@
return baseForRight(object, callback, keys); return baseForRight(object, callback, keys);
} }
/**
* The base implementation of `_.functions` which creates a sorted array of
* function property names from those returned by `keysFunc`.
*
* @private
* @param {Object} object The object to inspect.
* @param {Function} keysFunc The function to get the keys of `object`.
* @returns {Array} Returns the new sorted array of property names.
*/
function baseFunctions(object, keysFunc) {
var index = -1,
props = keysFunc(object),
length = props.length,
result = [];
while (++index < length) {
var key = props[index];
if (isFunction(object[key])) {
result.push(key);
}
}
return result.sort();
}
/** /**
* The base implementation of `_.isEqual`, without support for `thisArg` * The base implementation of `_.isEqual`, without support for `thisArg`
* binding, that allows partial "_.where" style comparisons. * binding, that allows partial "_.where" style comparisons.
@@ -6000,7 +6024,7 @@
} }
/** /**
* Creates a sorted array of property names of all enumerable function * Creates a sorted array of function property names from all enumerable
* properties, own and inherited, of `object`. * properties, own and inherited, of `object`.
* *
* @static * @static
@@ -6015,14 +6039,7 @@
* // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...]
*/ */
function functions(object) { function functions(object) {
var result = []; return baseFunctions(object, keysIn);
baseForIn(object, function(value, key) {
if (isFunction(value)) {
result.push(key);
}
});
return result.sort();
} }
/** /**
@@ -7922,8 +7939,9 @@
} }
/** /**
* Adds function properties of a source object to the destination object. * Adds all own enumerable function properties of a source object to the
* If `object` is a function methods will be added to its prototype as well. * destination object. If `object` is a function methods will be added to
* its prototype as well.
* *
* @static * @static
* @memberOf _ * @memberOf _
@@ -7955,7 +7973,7 @@
*/ */
function mixin(object, source, options) { function mixin(object, source, options) {
var chain = true, var chain = true,
methodNames = source && functions(source); methodNames = source && baseFunctions(source, keys);
if (!source || (!options && !methodNames.length)) { if (!source || (!options && !methodNames.length)) {
if (options == null) { if (options == null) {
@@ -7963,7 +7981,7 @@
} }
source = object; source = object;
object = this; object = this;
methodNames = functions(source); methodNames = baseFunctions(source, keys);
} }
if (options === false) { if (options === false) {
chain = false; chain = false;

View File

@@ -6383,6 +6383,22 @@
var value = ['a'], var value = ['a'],
source = { 'a': function(array) { return array[0]; }, 'b': 'B' }; source = { 'a': function(array) { return array[0]; }, 'b': 'B' };
test('should mixin `source` methods into lodash', 4, function() {
_.mixin(source);
strictEqual(_.a(value), 'a');
strictEqual(_(value).a().__wrapped__, 'a');
delete _.a;
delete _.prototype.a;
ok(!('b' in _));
ok(!('b' in _.prototype));
delete _.b;
delete _.prototype.b;
});
test('should use `this` as the default `object` value', 3, function() { test('should use `this` as the default `object` value', 3, function() {
var object = _.create(_); var object = _.create(_);
object.mixin(source); object.mixin(source);
@@ -6425,20 +6441,11 @@
delete wrapper.prototype.b; delete wrapper.prototype.b;
}); });
test('should mixin `source` methods into lodash', 4, function() { test('should not assign inherited `source` properties', 1, function() {
_.mixin(source); function Foo() {}
Foo.prototype = { 'a': _.noop };
strictEqual(_.a(value), 'a'); deepEqual(_.mixin({}, new Foo, {}), {});
strictEqual(_(value).a().__wrapped__, 'a');
delete _.a;
delete _.prototype.a;
ok(!('b' in _));
ok(!('b' in _.prototype));
delete _.b;
delete _.prototype.b;
}); });
test('should accept an `options` argument', 16, function() { test('should accept an `options` argument', 16, function() {