Sugar | Javascript, sweetened.

August 24, 2013

Changelog | Cautionlog

This update brings a lot of changes, so please have a look at the Cautionlog above to make upgrading a lot less painful!

Also, if you are unable to upgrade, please look here for a patch that will provide better future-proofing for older versions.

Performance Enhancements

By far the biggest change in v1.4 is an across the board look at performance. Some of the more dramatic changes include:

  • Date#format up to 21,400% faster
  • Array#findAll/findIndex/map/any/count/sum up to 11,270% faster
  • Object.map up to 682% faster
  • Array#min/max/less/more up to 83% faster
  • Object.isString/isNumber/isBoolean up to 77% faster
  • Array#at and String#at up to 242% faster for single index lookups.
  • Range#every up to 52% faster for dates, 1,500% faster for numbers/strings.
  • String#assign up to 30% faster.

Although some methods have been given special attention, many of the refactorings were on internal utility methods, which means that the performance enhancements will have a very wide affect. To demonstrate this I looked at simple date creation, an area that on the surface hasn’t changed since the last version:

// v1.3.9
Date.create('the day after tomorrow'); // 1000 iterations @ 74ms

// v1.4.0
Date.create('the day after tomorrow'); // 1000 iterations @ 60ms

This method, for this format, is now 23% faster thanks to things like speeding up internal type checking etc. Different methods for different cases should also show similar gains.

Natural Sorting

Array sorting using Array#sortBy now has an added feature to its string collation system which will perform a natural sort by default. This means that if it encounters numeric values it will sort them by their numericality instead of as a string. This means that “25” will be sorted after “7”, etc. This option can be set by changing the flag Array.AlphanumericSortNatural. Additionally, the string collation function itself is now exposed globally as Array.AlphanumericSort, which means it can be passed directly into Javasript native Array#sort as well.

Ranges

Ranges were previously a concept that only existed for dates and was a dependency of the Date package. It is now moved out into its own package, simplified, and now works with numbers and strings as well! Syntax for creating a range is the same:

Date.range('yesterday', 'tomorrow');
Number.range(1, 100);
String.range('a','z');

New to ranges is the ability to deal with inverted ranges (a higher number/date can iterate down to a lower one), and the clamp method which will contain a value to within the range:

var range = Number.range(1, 100);
range.clamp(50);   // 50
range.clamp(500);  // 100
range.clamp(-500); // 1

The clamp method as well as cap (which only limits an upper number) are also now aliases on the numbers:

(5).clamp(50, 100); // 50
(5).clamp(1, 10);   // 5
(5).cap(1)          // 1

Additionally, the previous method duration is now renamed to span. Finally, Array.create can understand ranges and convert them to arrays when needed.

Array.find and Array.findIndex

These methods that are defined in the ES6 spec previously existed but worked slightly differently. They have now been aligned with spec and the previous functionality has been moved:

// v1.3.9 Second argument was previously
// the index from which to start searching
[1,2,3].find(function(el) {
  return el === 3;
}, 1);

// v1.4.0 Second argument is now the context
// that the iterator function will be run with.
// This is similar to methods like forEach, etc.
[1,2,3].find(function(el) {
  return el === 3;
}, {});

// For previous behavior use Array#findFrom
// and Array#findIndexFrom instead.
[1,2,3].findFrom(function(el) {
  return el === 3;
}, 1);

Additionally these methods will act as polyfills, so when they are implemented in later browser versions they will fall back to native implementations. Which brings us to…

Better future-proofing

Sugar previously had a very simple policy. It would never overwrite methods in the global namespace if they were there first. Although this seems to make sense on the surface, it presents a very pernicious problem: if browsers change to add a method that collides, it will effectively overwrite (“underwrite”?) the Sugar implementation. It means that a browser update initiated by the user could potentially cause a script to break.

Of course staying on top of the spec and anticipating such changes is the real name of the game (with the above changes to Array#find and Array#findIndex and a couple others, Sugar is now fully in line with the ES6 spec for the foreseeable future). However there may be many reasons that a user can’t upgrade, and a browser update should not cause breakages.

For this reason, Sugar has made the reluctant decision to instead default to overriding those methods that it has not deemed polyfills (which it will fall back to, conversely). Although this unfortunately makes Sugar feel less “friendly”, real world usage has shown this to be a much better solution. In the end, if a user explicitly includes the Sugar library, they need to be guaranteed that the methods they are expecting will actually be there and not suddenly change.

More natural padding

Previously String#pad, String#padLeft, and String#padRight worked in a rather counter-intuitive manner — if 20 was passed they would put 20 characters on the ends of the string. They have now changed to be analagous to Number#pad and will add only enough padding to reach the specified length. If they string is already larger than this length, the method effectively does nothing.

More control over date creation

New to this version is Date.SugarNewDate. This hook is a way to override the Sugar internal date creation mechanism, which is now consolidated into one place. The common use case for this is timezones. The topic gets a bit complicated — Javascript dates have no concept of timezones except the one of the current browser locale, which makes having a date in another timezone tricky. Often to get around this dates are created and then shifted to act as though they were in another timezone on the surface (even though the underlying zone is the same). While this method may work, there are many cases when Sugar creates, uses, and disposes of dates before a user can get at them to shift the zone. This often happens in date comparison date.is('the day after tomorrow'). Date.SugarNewDate serves as a hook to allow any new date created to pass through this function before being used:

Date.SugarNewDate = function() {
  var d = new Date();
  // Effectively removes the current locale's timezone,
  // putting the date in UTC time.
  d.setTime(d.getTime() + (d.getTimezoneOffset() * 60 * 1000));
  return d;
}

Exposing this as a function gives the user maximum control, allowing even use of full timezone libraries that take into account Daylight Savings Time, etc, for maximum precision:

Date.SugarNewDate = function() {
  return someAwesomeTimezoneLib.createDateInZone('America/New_York');
}

Other changes

  • Added Function#every which executes a function every ms milliseconds or until canceled.
  • Added String#truncateOnWords. Part of the String#truncate functionality is now here.
  • Timezone formatting tokens changed to align with Moment.js better.
  • Added Date#beginningOfISOWeek and Date#endOfISOWeek
  • Removed deep argument from Object.fromQueryString and replaced with optional boolean casting.
  • Removed String#normalize.
  • Object.clone now only works on known object types and does not work on instances of user-created classes.
  • String#assign now can be passed an array as well as enumerated arguments.
  • Fix for isThisWeek being false when not "en" locale.
  • Fix for Array#create not working on argument objects of zero-length (Issue #299).
  • Fix for String#capitalize capitalizing after apostrophes (Issue #325).
  • Fix for extended objects select and reject returning plain objects.
  • Fix for Object.merge not merging certain deep objects.
  • Fix for environments where regexes are functions.
  • Fix for Function#cancel not properly clearing all timers (Issue #346).
  • Fix for lazy functions not being able to recursively call themselves.
  • Added option immediate to Function#lazy, which is now false by default.
  • Fixed bug with array like objects iterated over with loop = true.
  • Fixed String#truncate not returning primitives.
  • String#repeat is now aligned more with spec. String#pad follows suit.

January 25, 2013

Changelog | Cautionlog

jQuery 1.9 Fix

The most major point of this release was fixing jQuery 1.9, which broke in one area when Sugar was included in the page. The cause was complex, on jQuery’s side there was an issue that I submitted a pull request for, however the most direct culprit was String#namespace which is a method that never really fit into the library anyway. As a result I have removed this method from String entirely. At the moment I’m not sure if it will be re-added as it feels better for another lib.

Object.toQueryString

The inverse to Object.fromQueryString, this method will take an object and turn it into a query string for use in a url. In addition to the main method it is also mapped onto extended objects, making it even easier. Finally, a namespace can be added to scope all parameters by the given namespace.

var o1 = { foo: 'bar' };
var o2 = { foo: [1,2,3] };
Object.toQueryString(o1)        // 'foo=bar'
Object.toQueryString(o2)        // 'foo[0]=1&foo[1]=2&foo[2]=3'
Object.toQueryString(o2, 'bar') // 'bar[foo][0]=1&bar[foo][1]=2&foo[2]=3'

var o1 = Object.extended({ foo: 'bar' });
var o2 = Object.extended({ foo: [1,2,3] });

o1.toQueryString()      // 'foo=bar'
o2.toQueryString()      // 'foo[0]=1&foo[1]=2&foo[2]=3'
o2.toQueryString('bar') // 'bar[foo][0]=1&bar[foo][1]=2&foo[2]=3'

Other Minor Fixes

  • Fixed issue with timezone offsets like “-0330”.
  • Fixed issue with date comparison methods like isToday not working when another locale is set.

January 13, 2013

Changelog | Cautionlog

New caching mechanism

The Function#throttle method was intended as a way to rate limit heavy operations, however not much thought was given to the returned value. v1.3.8 adds a mechanism that memoizes the result of a throttled function so that it will always return the last calculated value. This means that it can now be used as a way to cache a value for a specific period of time:

var cachedOperation = heavyOperation.throttle(5000);
cachedOperation(); // Runs the operation
cachedOperation(); // Returns the result of the last operation
setTimeout(function() {
  cachedOperation(); // Runs the operation again
}, 6000);

Function#once now makes use of this mechanism internally. It is now equivalent to .throttle(Infinity), or in other words a cached function that never expires.

Bugfixes

  • Updating Date#setWeek (now Date#setISOWeek) to follow ISO-8601 standard.
  • Renamed Date#getWeek and Date#setWeek to Date#getISOWeek and Date#setISOWeek.
  • Performance improvement to return early using typeof for type checks.
  • Performance improvement for loops.
  • Fix for Array#sample sometimes returning undefined.
  • Fix for French locales.
  • Fix for conflict with Coffeescript.
  • Fix for Object.clone not preserving date _utc flag.

November 29, 2012

Changelog | Cautionlog

select/reject

Analogous to Underscore’s Object.pick and Object.omit, Sugar now has Object.select and Object.reject. These methods do pretty much what you would expect from the names — returning a new object that contains properties that match/do not match the keys passed — but have some special hidden features. Multiple keys may be specified through enumerated arguments, or also by passing an array of keys. Additionally, regexes will match against the key. And finally, passing another object will result in a match if that object also has the given key. This in effect will produce something like an “intersect” operation in the case of select, and a “subtract” operation in the case of reject. Some examples:

// Returns an object with the single member "user"
Object.select(obj, 'user');

// Returns an object with all members except "user"
Object.reject(obj, 'user');

// Returns an object with the members "user" or "count"
Object.select(obj, 'user','count');

// Returns an object with all members except "user" and "count"
Object.reject(obj, 'user','count');

// Returns an object with the members "user" or "count"
Object.select(obj, ['user','count']);

// Returns an object with any members that match /^user/
Object.select(obj, /^user/);

// Returns an object with any members that do not match /^user/
Object.reject(obj, /^user/);

// Returns an object with members whose keys match those in obj2
Object.select(obj, obj2);

// Returns an object with all members except those in obj2
Object.reject(obj, obj2);

As “hash” methods, select and reject are most powerful when used in extended objects (Sugar’s answer to hashes):

var obj = Object.extended(obj);
obj.select('user');
obj.select('user', 'count');
obj.select(['user', 'count']);

Other changes

String#startsWith and String#endsWith were modified to allow a second argument that is the position from which startsWith or to which endsWith the operation will be performed on. This is to bring in line these methods with those of the Harmony proposals of the same name. The “case sensitivity” flag that was previously the second argument is now the third, and as always Sugar will detect when/if to defer to the native method if available.

Fixes

  • Fix for Date.create not preserving the UTC flag when the source is also a date.
  • Object.clone on arrays with the “deep” flag set to true should create a deep clone of the array.
  • Array#min/max should throw an error when comparing to undefined.
  • Fix for dates that fallback to native parsing when forcing UTC flag.
  • Date#since/fromNow aliases will now count “past” integers instead of rounding.

November 2, 2012

Changelog | Cautionlog

Basic bugfixes for this release. Skipped 1.3.5 (more simple fixes).

  • Faster String#repeat
  • Fixed issue with Array#sample skewing randomization.
  • Limiting minute/second parsing to 0-59.
  • Fixed issue with addMonths traversing into other months.
  • Fixed issue with NaN being true for isOdd.
  • Fixed issue with HTML escaping.
  • Fixed issue with float values parsing in Object.fromQueryString.
  • Internal refactoring of Object.each.
  • Fixed issue with 7 July date format.
  • Added “‘yy” as a valid year format.
  • Allowing empty strings for thousands separator and decimal in Number#format.

1 2 3 4 5 Next