diff --git a/docs/underscore.html b/docs/underscore.html index 6e44f0cf8..d2dafbd59 100644 --- a/docs/underscore.html +++ b/docs/underscore.html @@ -1,4 +1,4 @@ - underscore.js

underscore.js

Underscore.js 1.2.1
+      underscore.js           

underscore.js

Underscore.js 1.2.2
 (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
 Underscore is freely distributable under the MIT license.
 Portions of Underscore are inspired or borrowed from Prototype,
@@ -32,7 +32,7 @@
       return _;
     });
   } else {

Exported as a string, for Closure Compiler "advanced" mode.

    root['_'] = _;
-  }

Current version.

  _.VERSION = '1.2.1';

Collection Functions

The cornerstone, an each implementation, aka forEach. + }

Current version.

  _.VERSION = '1.2.2';

Collection Functions

The cornerstone, an each implementation, aka forEach. Handles objects with the built-in forEach, arrays, and raw objects. Delegates to ECMAScript 5's native forEach if available.

  var each = _.each = _.forEach = function(obj, iterator, context) {
     if (obj == null) return;
@@ -129,7 +129,7 @@
     if (obj == null) return result;
     if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
     each(obj, function(value, index, list) {
-      if (result |= iterator.call(context, value, index, list)) return breaker;
+      if (result || (result = iterator.call(context, value, index, list))) return breaker;
     });
     return !!result;
   };

Determine if a given value is included in the array or object using ===. @@ -138,7 +138,7 @@ if (obj == null) return found; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; found = any(obj, function(value) { - if (value === target) return true; + return value === target; }); return found; };

Invoke a method (with arguments) on every item in a collection.

  _.invoke = function(obj, method) {
@@ -225,7 +225,11 @@
     return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
   };

Get the last element of an array. Passing n will return the last N values in the array. The guard check allows it to work with _.map.

  _.last = function(array, n, guard) {
-    return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1];
+    if ((n != null) && !guard) {
+      return slice.call(array, Math.max(array.length - n, 0));
+    } else {
+      return array[array.length - 1];
+    }
   };

Returns everything but the first entry of the array. Aliased as tail. Especially useful on the arguments object. Passing an index will return the rest of the values in the array from that index onward. The guard @@ -353,18 +357,22 @@ return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); };

Returns a function, that, when invoked, will only be triggered at most once during a given window of time.

  _.throttle = function(func, wait) {
-    var timeout, context, args, throttling, finishThrottle;
-    finishThrottle = _.debounce(function(){ throttling = false; }, wait);
+    var context, args, timeout, throttling, more;
+    var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
     return function() {
       context = this; args = arguments;
-      var throttler = function() {
+      var later = function() {
         timeout = null;
-        func.apply(context, args);
-        finishThrottle();
+        if (more) func.apply(context, args);
+        whenDone();
       };
-      if (!timeout) timeout = setTimeout(throttler, wait);
-      if (!throttling) func.apply(context, args);
-      if (finishThrottle) finishThrottle();
+      if (!timeout) timeout = setTimeout(later, wait);
+      if (throttling) {
+        more = true;
+      } else {
+        func.apply(context, args);
+      }
+      whenDone();
       throttling = true;
     };
   };

Returns a function, that, as long as it continues to be invoked, will not @@ -373,12 +381,12 @@ var timeout; return function() { var context = this, args = arguments; - var throttler = function() { + var later = function() { timeout = null; func.apply(context, args); }; clearTimeout(timeout); - timeout = setTimeout(throttler, wait); + timeout = setTimeout(later, wait); }; };

Returns a function that will be executed at most one time, no matter how often you call it. Useful for lazy initialization.

  _.once = function(func) {
@@ -406,6 +414,7 @@
       return args[0];
     };
   };

Returns a function that will only be executed after being called N times.

  _.after = function(times, func) {
+    if (times <= 0) return func();
     return function() {
       if (--times < 1) { return func.apply(this, arguments); }
     };
@@ -447,49 +456,61 @@
     interceptor(obj);
     return obj;
   };

Internal recursive comparison function.

  function eq(a, b, stack) {

Identical objects are equal. 0 === -0, but they aren't identical. -See the Harmony egal proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.

    if (a === b) return a !== 0 || 1 / a == 1 / b;

A strict comparison is necessary because null == undefined.

    if ((a == null) || (b == null)) return a === b;

Unwrap any wrapped objects.

    if (a._chain) a = a._wrapped;
+See the Harmony egal proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.

    if (a === b) return a !== 0 || 1 / a == 1 / b;

A strict comparison is necessary because null == undefined.

    if (a == null || b == null) return a === b;

Unwrap any wrapped objects.

    if (a._chain) a = a._wrapped;
     if (b._chain) b = b._wrapped;

Invoke a custom isEqual method if one is provided.

    if (_.isFunction(a.isEqual)) return a.isEqual(b);
-    if (_.isFunction(b.isEqual)) return b.isEqual(a);

Compare object types.

    var typeA = typeof a;
-    if (typeA != typeof b) return false;

Optimization; ensure that both values are truthy or falsy.

    if (!a != !b) return false;

NaN values are equal.

    if (_.isNaN(a)) return _.isNaN(b);

Compare string objects by value.

    var isStringA = _.isString(a), isStringB = _.isString(b);
-    if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b);

Compare number objects by value.

    var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b);
-    if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b;

Compare boolean objects by value. The value of true is 1; the value of false is 0.

    var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b);
-    if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b;

Compare dates by their millisecond values.

    var isDateA = _.isDate(a), isDateB = _.isDate(b);
-    if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime();

Compare RegExps by their source patterns and flags.

    var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b);
-    if (isRegExpA || isRegExpB) {

Ensure commutative equality for RegExps.

      return isRegExpA && isRegExpB &&
-             a.source == b.source &&
-             a.global == b.global &&
-             a.multiline == b.multiline &&
-             a.ignoreCase == b.ignoreCase;
-    }

Ensure that both values are objects.

    if (typeA != 'object') return false;

Arrays or Arraylikes with different lengths are not equal.

    if (a.length !== b.length) return false;

Objects with different constructors are not equal.

    if (a.constructor !== b.constructor) return false;

Assume equality for cyclic structures. The algorithm for detecting cyclic + if (_.isFunction(b.isEqual)) return b.isEqual(a);

Compare [[Class]] names.

    var className = toString.call(a);
+    if (className != toString.call(b)) return false;
+    switch (className) {

Strings, numbers, dates, and booleans are compared by value.

      case '[object String]':

Primitives and their corresponding object wrappers are equivalent; thus, "5" is +equivalent to new String("5").

        return String(a) == String(b);
+      case '[object Number]':
+        a = +a;
+        b = +b;

NaNs are equivalent, but non-reflexive. An egal comparison is performed for +other numeric values.

        return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b);
+      case '[object Date]':
+      case '[object Boolean]':

Coerce dates and booleans to numeric primitive values. Dates are compared by their +millisecond representations. Note that invalid dates with millisecond representations +of NaN are not equivalent.

        return +a == +b;

RegExps are compared by their source patterns and flags.

      case '[object RegExp]':
+        return a.source == b.source &&
+               a.global == b.global &&
+               a.multiline == b.multiline &&
+               a.ignoreCase == b.ignoreCase;
+    }
+    if (typeof a != 'object' || typeof b != 'object') return false;

Assume equality for cyclic structures. The algorithm for detecting cyclic structures is adapted from ES 5.1 section 15.12.3, abstract operation JO.

    var length = stack.length;
-    while (length--) {

Linear search. Performance is inversely proportional to the number of + while (length--) {

Linear search. Performance is inversely proportional to the number of unique nested structures.

      if (stack[length] == a) return true;
-    }

Add the first object to the stack of traversed objects.

    stack.push(a);
-    var size = 0, result = true;

Deep compare objects.

    for (var key in a) {
-      if (hasOwnProperty.call(a, key)) {

Count the expected number of properties.

        size++;

Deep compare each member.

        if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
+    }

Add the first object to the stack of traversed objects.

    stack.push(a);
+    var size = 0, result = true;

Recursively compare objects and arrays.

    if (className == '[object Array]') {

Compare array lengths to determine if a deep comparison is necessary.

      size = a.length;
+      result = size == b.length;
+      if (result) {

Deep compare the contents, ignoring non-numeric properties.

        while (size--) {

Ensure commutative equality for sparse arrays.

          if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
+        }
       }
-    }

Ensure that both objects contain the same number of properties.

    if (result) {
-      for (key in b) {
-        if (hasOwnProperty.call(b, key) && !size--) break;
+    } else {

Objects with different constructors are not equivalent.

      if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false;

Deep compare objects.

      for (var key in a) {
+        if (hasOwnProperty.call(a, key)) {

Count the expected number of properties.

          size++;

Deep compare each member.

          if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break;
+        }
+      }

Ensure that both objects contain the same number of properties.

      if (result) {
+        for (key in b) {
+          if (hasOwnProperty.call(b, key) && !(size--)) break;
+        }
+        result = !size;
       }
-      result = !size;
-    }

Remove the first object from the stack of traversed objects.

    stack.pop();
+    }

Remove the first object from the stack of traversed objects.

    stack.pop();
     return result;
-  }

Perform a deep comparison to check if two objects are equal.

  _.isEqual = function(a, b) {
+  }

Perform a deep comparison to check if two objects are equal.

  _.isEqual = function(a, b) {
     return eq(a, b, []);
-  };

Is a given array, string, or object empty? + };

Is a given array, string, or object empty? An "empty" object has no enumerable own-properties.

  _.isEmpty = function(obj) {
     if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
     for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
     return true;
-  };

Is a given value a DOM element?

  _.isElement = function(obj) {
+  };

Is a given value a DOM element?

  _.isElement = function(obj) {
     return !!(obj && obj.nodeType == 1);
-  };

Is a given value an array? + };

Is a given value an array? Delegates to ECMA5's native Array.isArray

  _.isArray = nativeIsArray || function(obj) {
     return toString.call(obj) == '[object Array]';
-  };

Is a given variable an object?

  _.isObject = function(obj) {
+  };

Is a given variable an object?

  _.isObject = function(obj) {
     return obj === Object(obj);
-  };

Is a given variable an arguments object?

  if (toString.call(arguments) == '[object Arguments]') {
+  };

Is a given variable an arguments object?

  if (toString.call(arguments) == '[object Arguments]') {
     _.isArguments = function(obj) {
       return toString.call(obj) == '[object Arguments]';
     };
@@ -497,49 +518,49 @@
     _.isArguments = function(obj) {
       return !!(obj && hasOwnProperty.call(obj, 'callee'));
     };
-  }

Is a given value a function?

  _.isFunction = function(obj) {
+  }

Is a given value a function?

  _.isFunction = function(obj) {
     return toString.call(obj) == '[object Function]';
-  };

Is a given value a string?

  _.isString = function(obj) {
+  };

Is a given value a string?

  _.isString = function(obj) {
     return toString.call(obj) == '[object String]';
-  };

Is a given value a number?

  _.isNumber = function(obj) {
+  };

Is a given value a number?

  _.isNumber = function(obj) {
     return toString.call(obj) == '[object Number]';
-  };

Is the given value NaN?

  _.isNaN = function(obj) {

NaN is the only value for which === is not reflexive.

    return obj !== obj;
-  };

Is a given value a boolean?

  _.isBoolean = function(obj) {
+  };

Is the given value NaN?

  _.isNaN = function(obj) {

NaN is the only value for which === is not reflexive.

    return obj !== obj;
+  };

Is a given value a boolean?

  _.isBoolean = function(obj) {
     return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
-  };

Is a given value a date?

  _.isDate = function(obj) {
+  };

Is a given value a date?

  _.isDate = function(obj) {
     return toString.call(obj) == '[object Date]';
-  };

Is the given value a regular expression?

  _.isRegExp = function(obj) {
+  };

Is the given value a regular expression?

  _.isRegExp = function(obj) {
     return toString.call(obj) == '[object RegExp]';
-  };

Is a given value equal to null?

  _.isNull = function(obj) {
+  };

Is a given value equal to null?

  _.isNull = function(obj) {
     return obj === null;
-  };

Is a given variable undefined?

  _.isUndefined = function(obj) {
+  };

Is a given variable undefined?

  _.isUndefined = function(obj) {
     return obj === void 0;
-  };

Utility Functions

Run Underscore.js in noConflict mode, returning the _ variable to its + };

Utility Functions

Run Underscore.js in noConflict mode, returning the _ variable to its previous owner. Returns a reference to the Underscore object.

  _.noConflict = function() {
     root._ = previousUnderscore;
     return this;
-  };

Keep the identity function around for default iterators.

  _.identity = function(value) {
+  };

Keep the identity function around for default iterators.

  _.identity = function(value) {
     return value;
-  };

Run a function n times.

  _.times = function (n, iterator, context) {
+  };

Run a function n times.

  _.times = function (n, iterator, context) {
     for (var i = 0; i < n; i++) iterator.call(context, i);
-  };

Escape a string for HTML interpolation.

  _.escape = function(string) {
-    return (''+string).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
-  };

Add your own custom functions to the Underscore object, ensuring that + };

Escape a string for HTML interpolation.

  _.escape = function(string) {
+    return (''+string).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g,'&#x2F;');
+  };

Add your own custom functions to the Underscore object, ensuring that they're correctly added to the OOP wrapper as well.

  _.mixin = function(obj) {
     each(_.functions(obj), function(name){
       addToWrapper(name, _[name] = obj[name]);
     });
-  };

Generate a unique integer id (unique within the entire client session). + };

Generate a unique integer id (unique within the entire client session). Useful for temporary DOM ids.

  var idCounter = 0;
   _.uniqueId = function(prefix) {
     var id = idCounter++;
     return prefix ? prefix + id : id;
-  };

By default, Underscore uses ERB-style template delimiters, change the + };

By default, Underscore uses ERB-style template delimiters, change the following template settings to use alternative delimiters.

  _.templateSettings = {
     evaluate    : /<%([\s\S]+?)%>/g,
     interpolate : /<%=([\s\S]+?)%>/g,
     escape      : /<%-([\s\S]+?)%>/g
-  };

JavaScript micro-templating, similar to John Resig's implementation. + };

JavaScript micro-templating, similar to John Resig's implementation. Underscore templating handles arbitrary delimiters, preserves whitespace, and correctly escapes quotes within interpolated code.

  _.template = function(str, data) {
     var c  = _.templateSettings;
@@ -555,42 +576,42 @@
          })
          .replace(c.evaluate || null, function(match, code) {
            return "');" + code.replace(/\\'/g, "'")
-                              .replace(/[\r\n\t]/g, ' ') + "__p.push('";
+                              .replace(/[\r\n\t]/g, ' ') + ";__p.push('";
          })
          .replace(/\r/g, '\\r')
          .replace(/\n/g, '\\n')
          .replace(/\t/g, '\\t')
          + "');}return __p.join('');";
-    var func = new Function('obj', tmpl);
-    return data ? func(data) : func;
-  };

The OOP Wrapper

If Underscore is called as a function, it returns a wrapped object that + var func = new Function('obj', '_', tmpl); + return data ? func(data, _) : function(data) { return func(data, _) }; + };

The OOP Wrapper

If Underscore is called as a function, it returns a wrapped object that can be used OO-style. This wrapper holds altered versions of all the -underscore functions. Wrapped objects may be chained.

  var wrapper = function(obj) { this._wrapped = obj; };

Expose wrapper.prototype as _.prototype

  _.prototype = wrapper.prototype;

Helper function to continue chaining intermediate results.

  var result = function(obj, chain) {
+underscore functions. Wrapped objects may be chained.

  var wrapper = function(obj) { this._wrapped = obj; };

Expose wrapper.prototype as _.prototype

  _.prototype = wrapper.prototype;

Helper function to continue chaining intermediate results.

  var result = function(obj, chain) {
     return chain ? _(obj).chain() : obj;
-  };

A method to easily add functions to the OOP wrapper.

  var addToWrapper = function(name, func) {
+  };

A method to easily add functions to the OOP wrapper.

  var addToWrapper = function(name, func) {
     wrapper.prototype[name] = function() {
       var args = slice.call(arguments);
       unshift.call(args, this._wrapped);
       return result(func.apply(_, args), this._chain);
     };
-  };

Add all of the Underscore functions to the wrapper object.

  _.mixin(_);

Add all mutator Array functions to the wrapper.

  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+  };

Add all of the Underscore functions to the wrapper object.

  _.mixin(_);

Add all mutator Array functions to the wrapper.

  each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
     var method = ArrayProto[name];
     wrapper.prototype[name] = function() {
       method.apply(this._wrapped, arguments);
       return result(this._wrapped, this._chain);
     };
-  });

Add all accessor Array functions to the wrapper.

  each(['concat', 'join', 'slice'], function(name) {
+  });

Add all accessor Array functions to the wrapper.

  each(['concat', 'join', 'slice'], function(name) {
     var method = ArrayProto[name];
     wrapper.prototype[name] = function() {
       return result(method.apply(this._wrapped, arguments), this._chain);
     };
-  });

Start chaining a wrapped Underscore object.

  wrapper.prototype.chain = function() {
+  });

Start chaining a wrapped Underscore object.

  wrapper.prototype.chain = function() {
     this._chain = true;
     return this;
-  };

Extracts the result from a wrapped and chained object.

  wrapper.prototype.value = function() {
+  };

Extracts the result from a wrapped and chained object.

  wrapper.prototype.value = function() {
     return this._wrapped;
   };
 
-})();
+}).call(this);
 
 
\ No newline at end of file diff --git a/index.html b/index.html index 8c8e7c914..fe8321aef 100644 --- a/index.html +++ b/index.html @@ -126,11 +126,11 @@

Downloads (Right-click, and u - - + + - +
Development Version (1.2.1)33kb, Uncompressed with CommentsDevelopment Version (1.2.2)34kb, Uncompressed with Comments
Production Version (1.2.1)Production Version (1.2.2) < 4kb, Minified and Gzipped
@@ -192,7 +192,7 @@

Table of Contents

noConflict, identity, times, mixin, uniqueId, - template + escape, template

@@ -1183,6 +1183,16 @@

Utility Functions

_.uniqueId('contact_'); => 'contact_104'

+

+ escape_.escape(string) +
+ Escapes a string for insertion into HTML, replacing + &, <, >, ", ', and / characters. +

+
+_.escape('Curly, Larry & Moe');
+=> "Curly, Larry &amp; Moe"
+

template_.template(templateString, [context])
@@ -1328,6 +1338,29 @@

Links & Suggested Reading

Change Log

+

+ 1.2.2Nov. 14, 2011
+

    +
  • + Continued tweaks to _.isEqual semantics. Now JS primitives are + considered equivalent to their wrapped versions, and arrays are compared + by their numeric properties only (#351). +
  • +
  • + _.escape no longer tries to be smart about not double-escaping + already-escaped HTML entities. Now it just escapes regardless (#350). +
  • +
  • + In _.template, you may now leave semicolons out of evaluated + statements if you wish: <% }) %> (#369). +
  • +
  • + _.after(callback, 0) will now trigger the callback immediately, + making "after" easier to use with asynchronous APIs (#366). +
  • +
+

+

1.2.1Oct. 24, 2011

    diff --git a/package.json b/package.json index 54f839863..8481b773a 100644 --- a/package.json +++ b/package.json @@ -8,5 +8,5 @@ "dependencies" : [], "repository" : {"type": "git", "url": "git://github.com/documentcloud/underscore.git"}, "main" : "underscore.js", - "version" : "1.2.1" + "version" : "1.2.2" } diff --git a/test/arrays.js b/test/arrays.js index c648f081b..ae00faf0a 100644 --- a/test/arrays.js +++ b/test/arrays.js @@ -7,6 +7,7 @@ $(document).ready(function() { equals(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"'); equals(_.first([1,2,3], 0).join(', '), "", 'can pass an index to first'); equals(_.first([1,2,3], 2).join(', '), '1, 2', 'can pass an index to first'); + equals(_.first([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to first'); var result = (function(){ return _.first(arguments); })(4, 3, 2, 1); equals(result, 4, 'works on an arguments object.'); result = _.map([[1,2,3],[1,2,3]], _.first); @@ -37,6 +38,7 @@ $(document).ready(function() { equals(_.last([1,2,3]), 3, 'can pull out the last element of an array'); equals(_.last([1,2,3], 0).join(', '), "", 'can pass an index to last'); equals(_.last([1,2,3], 2).join(', '), '2, 3', 'can pass an index to last'); + equals(_.last([1,2,3], 5).join(', '), '1, 2, 3', 'can pass an index to last'); var result = (function(){ return _(arguments).last(); })(1, 2, 3, 4); equals(result, 4, 'works on an arguments object'); result = _.map([[1,2,3],[1,2,3]], _.last); diff --git a/test/collections.js b/test/collections.js index 009d2f327..1726629eb 100644 --- a/test/collections.js +++ b/test/collections.js @@ -140,6 +140,8 @@ $(document).ready(function() { ok(!_.any([]), 'the empty set'); ok(!_.any([false, false, false]), 'all false values'); ok(_.any([false, false, true]), 'one true value'); + ok(_.any([null, 0, 'yes', false]), 'a string'); + ok(!_.any([null, 0, '', false]), 'falsy values'); ok(!_.any([1, 11, 29], function(num){ return num % 2 == 0; }), 'all odd numbers'); ok(_.any([1, 10, 29], function(num){ return num % 2 == 0; }), 'an even number'); ok(_.some([false, false, true]), 'aliased as "some"'); diff --git a/test/functions.js b/test/functions.js index 5bcf215b7..f084b015e 100644 --- a/test/functions.js +++ b/test/functions.js @@ -118,6 +118,22 @@ $(document).ready(function() { _.delay(function(){ ok(value == 7, "updated to latest value"); start(); }, 400); }); + asyncTest("functions: throttle once", 1, function() { + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 100); + throttledIncr(); + _.delay(function(){ ok(counter == 1, "incr was called once"); start(); }, 220); + }); + + asyncTest("functions: throttle twice", 1, function() { + var counter = 0; + var incr = function(){ counter++; }; + var throttledIncr = _.throttle(incr, 100); + throttledIncr(); throttledIncr(); + _.delay(function(){ ok(counter == 2, "incr was called twice"); start(); }, 220); + }); + asyncTest("functions: debounce", 1, function() { var counter = 0; var incr = function(){ counter++; }; @@ -172,6 +188,7 @@ $(document).ready(function() { equals(testAfter(5, 5), 1, "after(N) should fire after being called N times"); equals(testAfter(5, 4), 0, "after(N) should not fire unless called N times"); + equals(testAfter(0, 0), 1, "after(0) should fire immediately"); }); }); diff --git a/test/objects.js b/test/objects.js index 7e6cd7cc1..6d432033a 100644 --- a/test/objects.js +++ b/test/objects.js @@ -94,19 +94,21 @@ $(document).ready(function() { // String object and primitive comparisons. ok(_.isEqual("Curly", "Curly"), "Identical string primitives are equal"); ok(_.isEqual(new String("Curly"), new String("Curly")), "String objects with identical primitive values are equal"); + ok(_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are equal"); + ok(_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives"); ok(!_.isEqual("Curly", "Larry"), "String primitives with different values are not equal"); - ok(!_.isEqual(new String("Curly"), "Curly"), "String primitives and their corresponding object wrappers are not equal"); - ok(!_.isEqual("Curly", new String("Curly")), "Commutative equality is implemented for string objects and primitives"); ok(!_.isEqual(new String("Curly"), new String("Larry")), "String objects with different primitive values are not equal"); ok(!_.isEqual(new String("Curly"), {toString: function(){ return "Curly"; }}), "String objects and objects with a custom `toString` method are not equal"); // Number object and primitive comparisons. ok(_.isEqual(75, 75), "Identical number primitives are equal"); ok(_.isEqual(new Number(75), new Number(75)), "Number objects with identical primitive values are equal"); + ok(_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are equal"); + ok(_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives"); + ok(!_.isEqual(new Number(0), -0), "`new Number(0)` and `-0` are not equal"); + ok(!_.isEqual(0, new Number(-0)), "Commutative equality is implemented for `new Number(0)` and `-0`"); - ok(!_.isEqual(75, new Number(75)), "Number primitives and their corresponding object wrappers are not equal"); - ok(!_.isEqual(new Number(75), 75), "Commutative equality is implemented for number objects and primitives"); ok(!_.isEqual(new Number(75), new Number(63)), "Number objects with different primitive values are not equal"); ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), "Number objects and objects with a `valueOf` method are not equal"); @@ -119,8 +121,8 @@ $(document).ready(function() { // Boolean object and primitive comparisons. ok(_.isEqual(true, true), "Identical boolean primitives are equal"); ok(_.isEqual(new Boolean, new Boolean), "Boolean objects with identical primitive values are equal"); - ok(!_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are not equal"); - ok(!_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans"); + ok(_.isEqual(true, new Boolean(true)), "Boolean primitives and their corresponding object wrappers are equal"); + ok(_.isEqual(new Boolean(true), true), "Commutative equality is implemented for booleans"); ok(!_.isEqual(new Boolean(true), new Boolean), "Boolean objects with different primitive values are not equal"); // Common type coercions. @@ -178,7 +180,7 @@ $(document).ready(function() { b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null; // Array elements and properties. - ok(!_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are not equal"); + ok(_.isEqual(a, b), "Arrays containing equivalent elements and different non-numeric properties are equal"); a.push("White Rocks"); ok(!_.isEqual(a, b), "Arrays of different lengths are not equal"); a.push("East Boulder"); @@ -240,7 +242,7 @@ $(document).ready(function() { // Instances. ok(_.isEqual(new First, new First), "Object instances are equal"); ok(!_.isEqual(new First, new Second), "Objects with different constructors and identical own properties are not equal"); - ok(!_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are not identical"); + ok(!_.isEqual({value: 1}, new First), "Object instances and objects sharing equivalent properties are not equal"); ok(!_.isEqual({value: 2}, new Second), "The prototype chain of objects should not be examined"); // Circular Arrays. diff --git a/test/utility.js b/test/utility.js index 976b3b996..0f53bd510 100644 --- a/test/utility.js +++ b/test/utility.js @@ -41,11 +41,19 @@ $(document).ready(function() { equals(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper'); }); + test("utility: _.escape", function() { + equals(_.escape("Curly & Moe"), "Curly & Moe"); + equals(_.escape("Curly & Moe"), "Curly &amp; Moe"); + }); + test("utility: template", function() { var basicTemplate = _.template("<%= thing %> is gettin' on my noives!"); var result = basicTemplate({thing : 'This'}); equals(result, "This is gettin' on my noives!", 'can do basic attribute interpolation'); + var sansSemicolonTemplate = _.template("A <% this %> B"); + equals(sansSemicolonTemplate(), "A B"); + var backslashTemplate = _.template("<%= thing %> is \\ridanculous"); equals(backslashTemplate({thing: 'This'}), "This is \\ridanculous"); diff --git a/underscore-min.js b/underscore-min.js index ad3756028..fadc96eca 100644 --- a/underscore-min.js +++ b/underscore-min.js @@ -1,30 +1,30 @@ -// Underscore.js 1.2.1 +// Underscore.js 1.2.2 // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore -(function(){function u(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=typeof a;if(e!=typeof c)return false;if(!a!=!c)return false;if(b.isNaN(a))return b.isNaN(c);var g=b.isString(a),f=b.isString(c);if(g||f)return g&&f&&String(a)==String(c);g=b.isNumber(a);f=b.isNumber(c);if(g||f)return g&&f&&+a==+c;g=b.isBoolean(a);f=b.isBoolean(c); -if(g||f)return g&&f&&+a==+c;g=b.isDate(a);f=b.isDate(c);if(g||f)return g&&f&&a.getTime()==c.getTime();g=b.isRegExp(a);f=b.isRegExp(c);if(g||f)return g&&f&&a.source==c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase;if(e!="object")return false;if(a.length!==c.length)return false;if(a.constructor!==c.constructor)return false;for(e=d.length;e--;)if(d[e]==a)return true;d.push(a);var e=0,g=true,h;for(h in a)if(m.call(a,h)&&(e++,!(g=m.call(c,h)&&u(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, -h)&&!e--)break;g=!e}d.pop();return g}var r=this,F=r._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,s=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd? -define("underscore",function(){return b}):r._=b;b.VERSION="1.2.1";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,g=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,g=a.length;e< -g;){var f=e+g>>1;d(a[f])=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!==Object(a))throw new TypeError("Invalid object"); -var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({}, -a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return u(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}:function(a){return!(!a||!m.call(a, -"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a=== -void 0};b.noConflict=function(){r._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g, -interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+"__p.push('"}).replace(/\r/g, -"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",d=new Function("obj",d);return c?d(c):d};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var t=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return t(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,arguments);return t(this._wrapped, -this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return t(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}})(); +(function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return String(a)==String(c);case "[object Number]":return a=+a,c=+c,a!=a?c!=c:a==0?1/a==1/c:a==c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== +c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c, +h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd? +define("underscore",function(){return b}):s._=b;b.VERSION="1.2.2";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,c){var b=e(a,c);(d[b]||(d[b]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e< +f;){var g=e+f>>1;d(a[g])=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!== +Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)? +a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}: +function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null}; +b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g, +interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g, +"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e(a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return u(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped, +arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this); diff --git a/underscore.js b/underscore.js index ab4c26094..5579c07d3 100644 --- a/underscore.js +++ b/underscore.js @@ -1,4 +1,4 @@ -// Underscore.js 1.2.1 +// Underscore.js 1.2.2 // (c) 2011 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, @@ -67,7 +67,7 @@ } // Current version. - _.VERSION = '1.2.1'; + _.VERSION = '1.2.2'; // Collection Functions // -------------------- @@ -194,7 +194,7 @@ if (obj == null) return result; if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); each(obj, function(value, index, list) { - if (result |= iterator.call(context, value, index, list)) return breaker; + if (result || (result = iterator.call(context, value, index, list))) return breaker; }); return !!result; }; @@ -206,7 +206,7 @@ if (obj == null) return found; if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; found = any(obj, function(value) { - if (value === target) return true; + return value === target; }); return found; }; @@ -335,7 +335,11 @@ // Get the last element of an array. Passing **n** will return the last N // values in the array. The **guard** check allows it to work with `_.map`. _.last = function(array, n, guard) { - return (n != null) && !guard ? slice.call(array, array.length - n) : array[array.length - 1]; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } }; // Returns everything but the first entry of the array. Aliased as `tail`. @@ -523,18 +527,22 @@ // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. _.throttle = function(func, wait) { - var timeout, context, args, throttling, finishThrottle; - finishThrottle = _.debounce(function(){ throttling = false; }, wait); + var context, args, timeout, throttling, more; + var whenDone = _.debounce(function(){ more = throttling = false; }, wait); return function() { context = this; args = arguments; - var throttler = function() { + var later = function() { timeout = null; - func.apply(context, args); - finishThrottle(); + if (more) func.apply(context, args); + whenDone(); }; - if (!timeout) timeout = setTimeout(throttler, wait); - if (!throttling) func.apply(context, args); - if (finishThrottle) finishThrottle(); + if (!timeout) timeout = setTimeout(later, wait); + if (throttling) { + more = true; + } else { + func.apply(context, args); + } + whenDone(); throttling = true; }; }; @@ -546,12 +554,12 @@ var timeout; return function() { var context = this, args = arguments; - var throttler = function() { + var later = function() { timeout = null; func.apply(context, args); }; clearTimeout(timeout); - timeout = setTimeout(throttler, wait); + timeout = setTimeout(later, wait); }; }; @@ -591,6 +599,7 @@ // Returns a function that will only be executed after being called N times. _.after = function(times, func) { + if (times <= 0) return func(); return function() { if (--times < 1) { return func.apply(this, arguments); } }; @@ -663,48 +672,42 @@ // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. if (a === b) return a !== 0 || 1 / a == 1 / b; // A strict comparison is necessary because `null == undefined`. - if ((a == null) || (b == null)) return a === b; + if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a._chain) a = a._wrapped; if (b._chain) b = b._wrapped; // Invoke a custom `isEqual` method if one is provided. if (_.isFunction(a.isEqual)) return a.isEqual(b); if (_.isFunction(b.isEqual)) return b.isEqual(a); - // Compare object types. - var typeA = typeof a; - if (typeA != typeof b) return false; - // Optimization; ensure that both values are truthy or falsy. - if (!a != !b) return false; - // `NaN` values are equal. - if (_.isNaN(a)) return _.isNaN(b); - // Compare string objects by value. - var isStringA = _.isString(a), isStringB = _.isString(b); - if (isStringA || isStringB) return isStringA && isStringB && String(a) == String(b); - // Compare number objects by value. - var isNumberA = _.isNumber(a), isNumberB = _.isNumber(b); - if (isNumberA || isNumberB) return isNumberA && isNumberB && +a == +b; - // Compare boolean objects by value. The value of `true` is 1; the value of `false` is 0. - var isBooleanA = _.isBoolean(a), isBooleanB = _.isBoolean(b); - if (isBooleanA || isBooleanB) return isBooleanA && isBooleanB && +a == +b; - // Compare dates by their millisecond values. - var isDateA = _.isDate(a), isDateB = _.isDate(b); - if (isDateA || isDateB) return isDateA && isDateB && a.getTime() == b.getTime(); - // Compare RegExps by their source patterns and flags. - var isRegExpA = _.isRegExp(a), isRegExpB = _.isRegExp(b); - if (isRegExpA || isRegExpB) { - // Ensure commutative equality for RegExps. - return isRegExpA && isRegExpB && - a.source == b.source && - a.global == b.global && - a.multiline == b.multiline && - a.ignoreCase == b.ignoreCase; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return String(a) == String(b); + case '[object Number]': + a = +a; + b = +b; + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != a ? b != b : (a == 0 ? 1 / a == 1 / b : a == b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; } - // Ensure that both values are objects. - if (typeA != 'object') return false; - // Arrays or Arraylikes with different lengths are not equal. - if (a.length !== b.length) return false; - // Objects with different constructors are not equal. - if (a.constructor !== b.constructor) return false; + if (typeof a != 'object' || typeof b != 'object') return false; // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. var length = stack.length; @@ -716,21 +719,37 @@ // Add the first object to the stack of traversed objects. stack.push(a); var size = 0, result = true; - // Deep compare objects. - for (var key in a) { - if (hasOwnProperty.call(a, key)) { - // Count the expected number of properties. - size++; - // Deep compare each member. - if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + // Ensure commutative equality for sparse arrays. + if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; + } } - } - // Ensure that both objects contain the same number of properties. - if (result) { - for (key in b) { - if (hasOwnProperty.call(b, key) && !size--) break; + } else { + // Objects with different constructors are not equivalent. + if ("constructor" in a != "constructor" in b || a.constructor != b.constructor) return false; + // Deep compare objects. + for (var key in a) { + if (hasOwnProperty.call(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = hasOwnProperty.call(b, key) && eq(a[key], b[key], stack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (hasOwnProperty.call(b, key) && !(size--)) break; + } + result = !size; } - result = !size; } // Remove the first object from the stack of traversed objects. stack.pop(); @@ -845,7 +864,7 @@ // Escape a string for HTML interpolation. _.escape = function(string) { - return (''+string).replace(/&(?!\w+;|#\d+;|#x[\da-f]+;)/gi, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); + return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); }; // Add your own custom functions to the Underscore object, ensuring that @@ -889,14 +908,14 @@ }) .replace(c.evaluate || null, function(match, code) { return "');" + code.replace(/\\'/g, "'") - .replace(/[\r\n\t]/g, ' ') + "__p.push('"; + .replace(/[\r\n\t]/g, ' ') + ";__p.push('"; }) .replace(/\r/g, '\\r') .replace(/\n/g, '\\n') .replace(/\t/g, '\\t') + "');}return __p.join('');"; - var func = new Function('obj', tmpl); - return data ? func(data) : func; + var func = new Function('obj', '_', tmpl); + return data ? func(data, _) : function(data) { return func(data, _) }; }; // The OOP Wrapper @@ -955,4 +974,4 @@ return this._wrapped; }; -})(); +}).call(this);