Sugar | Javascript, sweetened.

Type Checking Hash Methods Enumerable Methods Extended Objects Object.extend()

Objects

Sugar provides a number of methods that make working with objects in Javascript easier. As Sugar will not modify the Object prototype by default, all methods exist directly on the Object class. However, Sugar also gives you the ability to create extended objects which have these methods available to them as instance methods. Finally, for the adventurous, you can call Object.extend() which will map all methods to Object.prototype, making them available to all Javascript objects.

Definition: What is an "object"?

It first helps to define exactly what an "object" is in Javascript, as the term itself can be a source of confusion. "object" is commonly used to mean any of the following:

Object literals

An object in its most basic form is a simple key/value store, often called an "object literal". The syntax { key: 'value' } is an example of an object literal, similar to JSON, which is a string representation of an object literal. Programmers from other backgrounds may also know them as "hashes", "dictionaries", and "associative arrays". However, the Sugar docs try to avoid these terms to prevent confusion as, in the end, object literals are still simply "objects" to Javascript.

Instances of classes*

Any instance of a class in Javascript can also be considered an "object", as all instances ultimately inherit from the Object class. This includes instances of built-in data types such as Array, RegExp, and Date.

The root class*

"Object" (note the capital "O") is the root class from which all other objects in Javascript inherit.

Primitive data types

Javascript "primitives" are a potential source of confusion as certain types (strings, numbers, booleans) do inherit from the Object class, however exhibit some unique behavior as primitives. undefined and null are not objects, however null responds to typeof as if it was. The exact behavior is beyond the scope of this document, however due to the complexity and confusion, it's not uncommon to see any Javascript data type potentially being referred to (sometimes mistakenly) as an "object".

* "class" is being used here and throughout the docs as a term of convenience. Javascript, which implements prototype based inheritance, does not have any intrinsic concept of a "class".

Type Methods

To alleviate some of the issues with determining data types of built-in and primitive objects, Sugar provides a number of type checking methods. These all follow the pattern is + type, and can be considered more reliable than typeof and instanceof, both of which are known to produce unintended results in various cases. All type methods are available directly on the Object class. Their instance method form is only available when explicitly opted-in by Object.extend(). Note that null and undefined can be checked by simply using the strict equality operator ===.

Method
Description
isString Returns true if the object is a string.
isNumber Returns true if the object is a number
isBoolean Returns true if the object is a boolean.
isRegExp Returns true if the object is a regexp.
isFunction Returns true if the object is a function.
isDate Returns true if the object is a date.
isArray Returns true if the object is an array.
isObject Returns true if the object is a plain object, such as an object literal. Anything that is NOT an object literal will return false. This includes arrays, inherited classes, and null. Extended objects are also true here.
isNaN Returns true if, and only if, the object is NaN. This is notably different from the global function isNaN, which means "not a number" and returns true for all data types that are not numbers.
Object.isString('wasabi')
Object.isNumber(32)
Object.isBoolean(false)
Object.isRegExp(/wasabi/)
Object.isFunction(function(){})
Object.isDate(new Date())
Object.isObject({ foo: 'bar' })
Object.isNaN(NaN)
var f; f === undefined;
var f = null; f === null;

Hash Methods

Sugar provides a number of methods for working with Javascript object literals (often called "hashes" in other languages). As Javascript makes no distinction between "hashes" and other objects, these methods exist as class methods directly on Object. However, they can be used as instance methods through Object.extend() or in extended objects.

Method
Description
keys Returns an array populated with the keys of the object.
values Returns an array populated with the values of the object.
equal Tests equality between two objects, including primitives and deep objects / arrays. Note that the instance method form is equals.
clone Creates a clone of the object. By default this is a shallow clone (nested objects/arrays will be copied by reference only), however passing true as the second parameter will create a deep clone.
merge Merges two objects. The first 2 arguments are the objects to be merged. The third parameter is a boolean that, like Object.clone will determine whether the merge is shallow or deep. The last parameter passed to this method is used to direct how conflicts should be handled. true (the default) indicates that existing properties should always be overwritten. false indicates that existing properties should always be preserved. Finally a callback can be passed here to allow dynamic resolution of the conflict.
watch Watches a given property of an object and allows a callback to be run when the property has changed. This method is useful to allow property validation. It unfortunately does not work in all browsers, notably it will not work in IE8 and below.
Object.keys({ foo:'bar' })
Object.values({ foo:'bar' })
Object.isEmpty({ foo:'bar' })
Object.clone({ foo:'bar' })
Object.equal({ foo:'bar' }, { foo: 'bar' })
Object.merge({ foo:'bar' }, { foo: 'car' })
Object.merge({ foo:'bar' }, { foo: 'car' }, false)
Object.each({ foo: 'bar' }, function(key, value) { console.log(key, value); });
var f = {}; Object.watch(f, 'foo', function() { console.log('property changed!'); }); f.foo = 'bar!';

Enumerable Methods

As part of the Array package, methods that can enumerate over objects are also provided on the Object class, and are also available to extended objects as instance methods. These methods have one exception to their array counterparts: when a callback is passed, it will receive the key and value as its first two arguments, where arrays would instead receive the index and element.

Method
Description
each Steps through each of the members in the object, calling a callback for each one. Returns the object.
find Find the first property in the object that matches a pattern.
findAll Find all properties in the object that match a pattern.
any Returns true if any properties in the object match the pattern.
all Returns true if all properties in the object match the pattern.
none Returns true if no properties in the object match the pattern.
count Counts all members in the object that match a pattern.
map Creates a new object and maps members of the original object to it.
reduce Reduces all properties in the object to a single value.
isEmpty Returns true if no properties are defined in the object.
sum Sums all members in the object.
average Averages all members in the object.
min Returns the minimum members of the object.
max Returns the maximum members of the object.
least Returns the members of the object that occur the least.
most Returns the members of the object that occur the most.
Object.findAll({ foo:'bar' }, 'bar')
Object.sum({a:8,b:8,c:2})
Object.count({ foo:'bar' }, /^b/)
Object.map({ foo:'bar' }, 'length')
Object.any({ foo: 'bar' }, function(key, value) { return key === 'foo'; });

Extended Objects

As Sugar does not modify Object.prototype by default, all methods exist directly on the Object class. However when working with data it can be much more intuitive to have these methods available as instance methods. Extended objects in Sugar bridge this gap by providing objects with both hash methods and enumerable methods available to them.

Extended objects are created through Object.extended, which can be passed a standard object literal that will determine the initial properties. Extended objects can be used exactly like standard Javascript objects, except that they will have these methods available to them. All arguments, minus passing the object itself, are identical.

Object.extended({ foo:'bar' }).keys()
Object.extended({ foo:'bar' }).values()
Object.extended({ foo:'bar' }).isEmpty()
Object.extended({ foo:'bar' }).clone()
Object.extended({ foo:'bar' }).merge({ foo: 'car' })
Object.extended({ foo:'bar' }).merge({ foo: 'car' }, false)
Object.extended({ foo: 'bar' }).each(function(key, value) { console.log(key, value); });

Object.extend()

The extend method exists on all built-in classes in Sugar as a way to extend natives with methods of your own. However, calling Object.extend() with no arguments will perform a special function, mapping the type, hash, and enumerable methods to Object.prototype, allowing all Javascript objects, including primitives, access to them as instance methods.

While this is powerful and can add expressiveness to your code, it is recommended to use this option with caution. First, it is discouraged if you are a plugin developer and there is any chance that your code will be run by others outside your control. Additionally, if it is causing unexpected errors, consider removing it while debugging, as conflicts on certain object properties may be to blame.

Issues that arise when using Object.extend() can nearly always be attributed to bad practice somewhere in your code (most notably iteration over objects using a for..in loop without using hasOwnProperty(), more on this here). However, unfortunately developers' code is often not their own, and tracking down other developer's errors is often too costly and time consuming. This is the main reasoning behind keeping this method as opt-in.