Add the _.support object.

Former-commit-id: b81ec9e5dbd41c729b3ad71187cb5e77e0755b9f
This commit is contained in:
John-David Dalton
2013-03-07 09:02:44 -08:00
parent 3131cc3160
commit 952afa05ce
3 changed files with 445 additions and 342 deletions

281
lodash.js
View File

@@ -188,84 +188,6 @@
isJSC = !/\n{2,}/.test(Function()),
isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera);
/* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */
var isBindFast = nativeBind && !isV8;
/* Detect if `Object.keys` exists and is inferred to be fast (Firefox, IE, Opera, V8) */
var isKeysFast = nativeKeys && (isIeOpera || isV8 || !isJSC);
/**
* Detect the JScript [[DontEnum]] bug:
*
* In IE < 9 an objects own properties, shadowing non-enumerable ones, are
* made non-enumerable as well.
*/
var hasDontEnumBug;
/**
* Detect if a `prototype` properties are enumerable by default:
*
* Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
* (if the prototype or a property on the prototype has been set)
* incorrectly sets a function's `prototype` property [[Enumerable]]
* value to `true`.
*/
var hasEnumPrototype;
/** Detect if own properties are iterated after inherited properties (IE < 9) */
var iteratesOwnLast;
/**
* Detect if `Array#shift` and `Array#splice` augment array-like objects
* incorrectly:
*
* Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
* and `splice()` functions that fail to remove the last element, `value[0]`,
* of array-like objects even though the `length` property is set to `0`.
* The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
* is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
*/
var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 },
arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]);
/** Detect if `arguments` object indexes are non-enumerable (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1) */
var nonEnumArgs = true;
(function() {
var props = [];
function ctor() { this.x = 1; }
ctor.prototype = { 'valueOf': 1, 'y': 1 };
for (var prop in new ctor) { props.push(prop); }
for (prop in arguments) { nonEnumArgs = !prop; }
hasDontEnumBug = !/valueOf/.test(props);
hasEnumPrototype = ctor.propertyIsEnumerable('prototype');
iteratesOwnLast = props[0] != 'x';
}(1));
/** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */
var argsAreObjects = arguments.constructor == Object;
/** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */
var noArgsClass = !isArguments(arguments);
/**
* Detect lack of support for accessing string characters by index:
*
* IE < 8 can't access characters by index and IE 8 can only access
* characters by index on string literals.
*/
var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx';
/**
* Detect if a DOM node's [[Class]] is unresolvable (IE < 9)
* and that the JS engine won't error when attempting to coerce an object to
* a string without a `toString` function.
*/
try {
var noNodeClass = toString.call(document) == objectClass && !({ 'toString': 0 } + '');
} catch(e) { }
/** Used to lookup a built-in constructor by [[Class]] */
var ctorByClass = {};
ctorByClass[arrayClass] = Array;
@@ -324,6 +246,141 @@
: new lodashWrapper(value);
}
/**
* An object used to flag environments features.
*
* @static
* @memberOf _
* @type Object
*/
var support = lodash.support = {};
(function() {
var ctor = function() { this.x = 1; },
object = { '0': 1, 'length': 1 },
props = [];
ctor.prototype = { 'valueOf': 1, 'y': 1 };
for (var prop in new ctor) { props.push(prop); }
/**
* Detect if `arguments` objects are `Object` objects
* (all but Opera < 10.5).
*
* @memberOf _.support
* @type Boolean
*/
support.argsObject = arguments.constructor == Object;
/**
* Detect if `arguments` objects [[Class]] are resolvable
* (all but Firefox < 4, IE < 9).
*
* @memberOf _.support
* @type Boolean
*/
support.argsClass = isArguments(arguments);
/**
* Detect if `prototype` properties are enumerable by default.
*
* Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
* (if the prototype or a property on the prototype has been set)
* incorrectly sets a function's `prototype` property [[Enumerable]]
* value to `true`.
*
* @memberOf _.support
* @type Boolean
*/
support.enumPrototypes = ctor.propertyIsEnumerable('prototype');
/**
* Detect if `Function#bind` exists and is inferred to be fast (all but V8).
*
* @memberOf _.support
* @type Boolean
*/
support.fastBind = nativeBind && !isV8;
/**
* Detect if `Object.keys` exists and is inferred to be fast
* (Firefox, IE, Opera, V8).
*
* @memberOf _.support
* @type Boolean
*/
support.fastKeys = nativeKeys && (isIeOpera || isV8 || !isJSC);
/**
* Detect if own properties are iterated after inherited properties
* (all but IE < 9).
*
* @memberOf _.support
* @type Boolean
*/
support.ownLast = props[0] != 'x';
/**
* Detect if `arguments` object indexes are non-enumerable
* (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1).
*
* @memberOf _.support
* @type Boolean
*/
support.nonEnumArgs = !arguments.propertyIsEnumerable(0);
/**
* Detect if properties shadowing those on `Object.prototype` are non-enumerable.
*
* In IE < 9 an objects own properties, shadowing non-enumerable ones, are
* made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug).
*
* @memberOf _.support
* @type Boolean
*/
support.nonEnumShadows = !/valueOf/.test(props);
/**
* Detect if `Array#shift` and `Array#splice` augment array-like
* objects correctly.
*
* Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()`
* and `splice()` functions that fail to remove the last element, `value[0]`,
* of array-like objects even though the `length` property is set to `0`.
* The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
* is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
*
* @memberOf _.support
* @type Boolean
*/
support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]);
/**
* Detect lack of support for accessing string characters by index.
*
* IE < 8 can't access characters by index and IE 8 can only access
* characters by index on string literals.
*
* @memberOf _.support
* @type Boolean
*/
support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
/**
* Detect if a DOM node's [[Class]] is resolvable (all but IE < 9)
* and that the JS engine errors when attempting to coerce an object to
* a string without a `toString` function.
*
* @memberOf _.support
* @type Boolean
*/
try {
support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + ''));
} catch(e) {
support.nodeClass = true;
}
}(1));
/**
* By default, the template delimiters used by Lo-Dash are similar to those in
* embedded Ruby (ERB). Change the following template settings to use alternative
@@ -410,7 +467,7 @@
'if (<%= arrays %>) {' +
// add support for accessing string characters by index if needed
' <% if (noCharByIndex) { %>\n' +
' <% if (support.unindexedChars) { %>\n' +
' if (isString(iterable)) {\n' +
" iterable = iterable.split('')\n" +
' }' +
@@ -425,7 +482,7 @@
// object iteration:
// add support for iterating over `arguments` objects if needed
' <% } else if (nonEnumArgs) { %>\n' +
' <% } else if (support.nonEnumArgs) { %>\n' +
' var length = iterable.length; index = -1;\n' +
' if (length && isArguments(iterable)) {\n' +
' while (++index < length) {\n' +
@@ -436,33 +493,33 @@
' <% } %>' +
// avoid iterating over `prototype` properties in older Firefox, Opera, and Safari
' <% if (hasEnumPrototype) { %>\n' +
' <% if (support.enumPrototypes) { %>\n' +
" var skipProto = typeof iterable == 'function';\n" +
' <% } %>' +
// iterate own properties using `Object.keys` if it's fast
' <% if (isKeysFast && useHas) { %>\n' +
' <% if (support.fastKeys && useHas) { %>\n' +
' var ownIndex = -1,\n' +
' ownProps = objectTypes[typeof iterable] ? nativeKeys(iterable) : [],\n' +
' length = ownProps.length;\n\n' +
' while (++ownIndex < length) {\n' +
' index = ownProps[ownIndex];\n' +
" <% if (hasEnumPrototype) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" +
" <% if (support.enumPrototypes) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" +
' <%= loop %>\n' +
' <% if (hasEnumPrototype) { %>}\n<% } %>' +
' <% if (support.enumPrototypes) { %>}\n<% } %>' +
' }' +
// else using a for-in loop
' <% } else { %>\n' +
' for (index in iterable) {<%' +
' if (hasEnumPrototype || useHas) { %>\n if (<%' +
" if (hasEnumPrototype) { %>!(skipProto && index == 'prototype')<% }" +
' if (hasEnumPrototype && useHas) { %> && <% }' +
' if (support.enumPrototypes || useHas) { %>\n if (<%' +
" if (support.enumPrototypes) { %>!(skipProto && index == 'prototype')<% }" +
' if (support.enumPrototypes && useHas) { %> && <% }' +
' if (useHas) { %>hasOwnProperty.call(iterable, index)<% }' +
' %>) {' +
' <% } %>\n' +
' <%= loop %>;' +
' <% if (hasEnumPrototype || useHas) { %>\n }<% } %>\n' +
' <% if (support.enumPrototypes || useHas) { %>\n }<% } %>\n' +
' }' +
' <% } %>' +
@@ -470,7 +527,7 @@
// existing property and the `constructor` property of a prototype
// defaults to non-enumerable, Lo-Dash skips the `constructor`
// property when it infers it's iterating over a `prototype` object.
' <% if (hasDontEnumBug) { %>\n\n' +
' <% if (support.nonEnumShadows) { %>\n\n' +
' var ctor = iterable.constructor;\n' +
' <% for (var k = 0; k < 7; k++) { %>\n' +
" index = '<%= shadowedProps[k] %>';\n" +
@@ -482,7 +539,7 @@
' }' +
' <% } %>' +
' <% } %>' +
' <% if (arrays || nonEnumArgs) { %>\n}<% } %>\n' +
' <% if (arrays || support.nonEnumArgs) { %>\n}<% } %>\n' +
// add code to the bottom of the iteration function
'<%= bottom %>;\n' +
@@ -668,13 +725,9 @@
*/
function createIterator() {
var data = {
// support properties
'hasDontEnumBug': hasDontEnumBug,
'hasEnumPrototype': hasEnumPrototype,
'isKeysFast': isKeysFast,
'nonEnumArgs': nonEnumArgs,
'noCharByIndex': noCharByIndex,
// data properties
'shadowedProps': shadowedProps,
'support': support,
// iterator options
'arrays': 'isArray(iterable)',
@@ -841,7 +894,7 @@
return toString.call(value) == argsClass;
}
// fallback for browsers that can't detect `arguments` objects by [[Class]]
if (noArgsClass) {
if (!support.argsClass) {
isArguments = function(value) {
return value ? hasOwnProperty.call(value, 'callee') : false;
};
@@ -922,7 +975,7 @@
var isArray = nativeIsArray || function(value) {
// `instanceof` may cause a memory leak in IE 7 if `value` is a host object
// http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak
return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass;
return (support.argsObject && value instanceof Array) || toString.call(value) == arrayClass;
};
/**
@@ -942,8 +995,8 @@
if (!isObject(object)) {
return [];
}
if ((hasEnumPrototype && typeof object == 'function') ||
(nonEnumArgs && object.length && isArguments(object))) {
if ((support.enumPrototypes && typeof object == 'function') ||
(support.nonEnumArgs && object.length && isArguments(object))) {
return shimKeys(object);
}
return nativeKeys(object);
@@ -967,16 +1020,16 @@
}
// check that the constructor is `Object` (i.e. `Object instanceof Object`)
var ctor = value.constructor;
if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) {
if ((!isFunction(ctor) && (support.nodeClass || !isNode(value))) || ctor instanceof ctor) {
// IE < 9 iterates inherited properties before own properties. If the first
// iterated property is an object's own property then there are no inherited
// enumerable properties.
if (iteratesOwnLast) {
if (support.ownLast) {
forIn(value, function(value, key, object) {
result = !hasOwnProperty.call(object, key);
result = hasOwnProperty.call(object, key);
return false;
});
return result === false;
return result === true;
}
// In most environments an object's own properties are iterated before
// its inherited properties. If the last iterated property is an object's
@@ -1136,7 +1189,7 @@
var isObj = isObject(result);
if (isObj) {
var className = toString.call(result);
if (!cloneableClasses[className] || (noNodeClass && isNode(result))) {
if (!cloneableClasses[className] || (!support.nodeClass && isNode(result))) {
return result;
}
var isArr = isArray(result);
@@ -1413,7 +1466,7 @@
length = value.length;
if ((className == arrayClass || className == stringClass ||
className == argsClass || (noArgsClass && isArguments(value))) ||
(support.argsClass ? className == argsClass : isArguments(value))) ||
(className == objectClass && typeof length == 'number' && isFunction(value.splice))) {
return !length;
}
@@ -1535,12 +1588,12 @@
return isEqual(a.__wrapped__ || a, b.__wrapped__ || b, callback, thisArg, stackA, stackB);
}
// exit for functions and DOM nodes
if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) {
if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) {
return false;
}
// in older versions of Opera, `arguments` objects have `Array` constructors
var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor,
ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor;
var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor,
ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor;
// non `Object` object instances with different constructors are not equal
if (ctorA != ctorB && !(
@@ -2191,7 +2244,7 @@
length = props.length,
result = Array(length);
if (noCharByIndex && isString(collection)) {
if (support.unindexedChars && isString(collection)) {
collection = collection.split('');
}
while(++index < length) {
@@ -2890,7 +2943,7 @@
if (typeof length != 'number') {
var props = keys(collection);
length = props.length;
} else if (noCharByIndex && isString(collection)) {
} else if (support.unindexedChars && isString(collection)) {
iterable = collection.split('');
}
callback = lodash.createCallback(callback, thisArg, 4);
@@ -3136,7 +3189,7 @@
*/
function toArray(collection) {
if (collection && typeof collection.length == 'number') {
return (noCharByIndex && isString(collection))
return (support.unindexedChars && isString(collection))
? collection.split('')
: slice(collection);
}
@@ -4099,7 +4152,7 @@
function bind(func, thisArg) {
// use `Function#bind` if it exists and is fast
// (in V8 `Function#bind` is slower except when partially applied)
return isBindFast || (nativeBind && arguments.length > 2)
return support.fastBind || (nativeBind && arguments.length > 2)
? nativeBind.call.apply(nativeBind, arguments)
: createBound(func, thisArg, slice(arguments, 2));
}
@@ -5233,7 +5286,7 @@
// avoid array-like object bugs with `Array#shift` and `Array#splice`
// in Firefox < 10 and IE < 9
if (hasObjectSpliceBug) {
if (!support.spliceObjects) {
each(['pop', 'shift', 'splice'], function(methodName) {
var func = arrayRef[methodName],
isSplice = methodName == 'splice';