CHAPTER 20
Ensuring Correct Use of Mixins

WHAT’S IN THIS CHAPTER?            

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

You can find the wrox.com code downloads for this chapter at www.wrox.com/go/reliablejavascript on the Download Code tab. The files are in the Chapter 20 download and individually named according to the filenames noted throughout this chapter.

Mixins are a mechanism for code reuse in which the properties of one object, the mixin, are utilized by another, the target. This sounds similar to the topic covered in Chapter 19, method-borrowing, and it is. Both techniques may be used to share method implementations from one object to another, but there are a few key differences:

Mixins are ideally suited for implementing functionality common to many object types, but not dependent upon the details of any of those types.

Suppose you’d like all of the objects in your system that are responsible for encapsulating data to expose an asJSON() convenience method that returns the contents of the object as a JSON string. Creating such a function is not complicated, as modern browsers provide the JSON.stringify method:

var dataObject1 = {
  propertyA: "a property",
  propertyB: "b property",

  asJSON: function asJSON(){
    return JSON.stringify(this);
  }
};

dataObject1.asJSON(); // '{ propertyA: "a property", propertyB: "b property" }';

While the function itself is simple, adding it to each of the individual data objects in a large system would lead to hundreds of lines of identical code. Not very DRY at all.

Let’s continue the example and say that, against your better judgment, you’ve added the function to each of the data objects. What happens if a third-party JSON library that can generate a JSON string ten times faster than the browser’s native method comes along and you want to update all of your data objects to employ the library? A lot of Find and Replace, that’s what. There’s a better way.

Because the asJSON method has no dependency upon the details of the object that contains it, it’s a prime candidate for implementation as a mixin:

var asJSONMixin = {
  asJSON: function asJSON(){
    return JSON.stringify(this);
  }
};

This may be used to extend the data objects:

var dataObject1 = {
  propertyA: "a property",
  propertyB: "b property"
};

// Extend dataObject1 by adding the asJSONMixin to it (the details of the
// extend function will be covered later in the chapter)
extend(dataObject1, asJSONMixin);

// dataObject1 has the asJSON method, but didn't have to implement it itself
dataObject1.asJSON(); // '{ propertyA: "a property", propertyB: "b property" }';

By extending the data objects with a traditional mixin, one that has its properties copied into its target, which implements asJSON rather than implementing asJSON directly in each object, incorporation of the hypothetical JSON library is simple: Only the mixin needs to be updated.

In the coming sections, we show you how to create the extend function used in the sample and explain how our implementation of extend differs from those provided by libraries such as jQuery and underscore. We’ll also show you how to create a traditional mixin using test-driven development. Finally, we explore functional mixins and how they differ, both positively and negatively, from traditional mixins.

CREATING AND USING MIXINS

This section revisits the attendee module that we used during the discussion of promises in Chapter 6. For reference, the attendee module is shown in Listing 20-1.

Of the eight methods in the attendee module shown in Listing 20-1, two stand out as generic and may be useful for other objects. Can you identify them? If you found getId() and setId(id), we’re on the same page.

In Chapter 6, the id-related methods were added to attendee for the purpose of retaining an attendee’s unique identifier. The unique identifier may be used to store and retrieve the attendee via a web service. It’s not beyond the realm of possibility that other entities used by the JavaScript conference’s website will also have unique identifiers.

In other words, the concept of the unique identifier is not specific to attendee, nor is it dependent upon the details of the attendee. The implementation of the unique identifier is a great candidate for refactoring into a mixin. This section explores this refactoring.

Creating and Using a Traditional Mixin

As shown in the introductory example, traditional mixins rely upon a function to “mix” them into a target object. This function, which copies the mixin object’s properties into the target, is traditionally called mixin or extend. We prefer extend, as it speaks to the action taking place: The target object is being extended by the mixin.

Creating the extend Function Using Test-Driven Development

As you might imagine, the extend function accepts two arguments:

  • The object to extend, or target
  • The object providing the extension, or mixin

As is our common practice, we’ll start by ensuring that errors are thrown in exceptional cases. For the extend function, an error should be thrown if either the target or the mixin is not an object. The unit tests provided in Listing 20-2 ensure this functionality.

Each of the tests iterate through the notObjects array whose elements, you might have guessed, are not objects. The tests provide each element as target in the case of the first test, or mixin in the case of the second test, and ensure that the appropriate error is thrown. A stub implementation of extend doesn’t satisfy these tests, as Figure 20.1 shows.

images

Figure 20.1  

Listing 20-3 implements the verification that target and mixin are, in fact, objects. The expected messages are also added to the messages property of the extend function.

The guard clauses highlighted in Listing 20-3 simply ensure that target and mixin are defined and are objects. Figure 20.2 shows that the clauses allow the unit tests to pass.

images

Figure 20.2  

The next step is to ensure that the extend method operates properly when the mixin argument is an empty object. When mixin is empty, target shouldn’t be changed. Listing 20-4 provides the unit test to verify this functionality.

The test added in Listing 20-4 extends target with an empty object. It then verifies that no properties have been added to or removed from target by ensuring that target’s keys haven’t changed. Finally, the test ensures that the functionality of target’s properties haven’t changed.

Because the current implementation of extend does nothing more than ensure target and mixin are provided, the new test passes, as shown in Figure 20.3.

images

Figure 20.3  

The next test verifies that the opposite situation, a bare target extended by a mixin with properties, functions correctly. Listing 20-5 presents the test for this.

This test extends target, which is initialized as a bare object, with mixin, which contains multiple properties—both functions and data.

Because target was initially bare, the test can ensure that the properties of mixin were copied into target by ensuring that target and mixin are equivalent after the extension has occurred. The statement:

expect(target).toEqual(mixin);

performs that test. This new test fails with the current implementation of extend, as Figure 20.4 shows.

images

Figure 20.4  

Listing 20-6 improves the implementation of extend to accommodate this new test.

The code added in Listing 20-6 is simple but powerful. It iterates through the properties of mixin and adds each property to target. The new implementation of extend satisfies all of the existing unit tests, as Figure 20.5 shows.

images

Figure 20.5  

The for loop introduced to extend in Listing 20-6 copies the enumerable properties of mixin to target, including those properties that mixin may inherit through its prototype. While mixins provide a convenient mechanism to reuse code, their use can introduce complexity when you’re attempting to determine the source of an object’s behavior.

In order to reduce this complexity, we prefer to copy only the properties that the mixin directly defines. This eliminates the need to traverse a prototype chain when you’re trying to find the source of a mixed-in property.

Listing 20-7 provides a test that ensures that properties that mixin inherits aren’t copied to target.

The test added in Listing 20-7 defines mixinBase, which is then used as the prototype of mixin. As such, mixin inherits the two properties defined by mixinBase, baseProperty, and baseMethod.

The test then defines two properties directly on mixin: mixinProperty and mixinMethod. The extend method is then used to extend target with mixin.

To ensure that the properties that mixin has inherited from mixinBase have not been copied to target, the expectation

expect(Object.keys(target).sort()).toEqual(["mixinMethod", "mixinProperty"]);

verifies that target has only the properties that were directly defined on mixin. Figure 20.6 shows that the new test fails, proving that the extend function doesn’t yet have the desired behavior.

images

Figure 20.6  

Listing 20-8 presents a modified extend function that only copies the properties directly defined on mixin.

Listing 20-8 simply introduces the condition that a property is only copied from mixin to target if the property is directly defined on mixin. Figure 20.7 shows that this added condition allows the unit test to pass.

images

Figure 20.7  

One aspect of extend that you may have noticed is that it blindly assigns the properties that it’s mixing in to properties of target. If target happens to already have a property that mixin provides, target’s property will be overwritten with the one provided by mixin.

This is the behavior of the extend function provided by both the jQuery library (http://api.jquery.com/jquery.extend/) and the underscore library (http://underscorejs.org/#extend).

We don’t believe this behavior promotes reliability. As an object can be extended by a mixin at any time in its lifespan, a mixin that changes an existing property of an object could have a negative impact on code that’s not aware the mixin has been applied.

As such, the implementation of extend presented here will be altered so that an error is thrown if the application of mixin would overwrite an existing property of target. The tests in Listing 20-9 verify this behavior.

The two tests in Listing 20-9 ensure that the extend function throws an appropriate error if mixin replaces an existing property of target. The first test ensures an error is thrown if the property replaced by mixin is defined directly on target. The second test ensures that an error is also thrown if the property replaced by mixin is inherited by target. Figure 20.8 shows that these tests fail with the current implementation of extend.

images

Figure 20.8  

The updated implementation of extend that causes the appropriate errors to be thrown is provided in Listing 20-10.

The code highlighted in Listing 20-10 implements the necessary check to ensure that a mixin that defines a property that already exists on target causes an error to be thrown.

Checking for the presence of the property in target via

if(!(item in target))

does so without regard to whether the property is directly defined or inherited. This ensures that an exception will be thrown in either case. The passing unit tests in Figure 20.9 prove this to be true.

images

Figure 20.9  

Creating a Traditional Mixin Using Test-Driven Development

With a fully implemented extend function, we can turn our attention to creating a traditional mixin that implements the unique identifier functionality that’s used by the attendee module.

In order to provide all the functionality that the attendee module requires, the idMixin needs to provide both the property in which the identifier will be stored, along with the getId and setId methods. The first test, presented in Listing 20-11, ensures that idMixin provides those properties to the object it extends.

The test suite starts with a beforeEach that initializes target to an empty object and mixin to an instance of the idMixin. The last step performed in the beforeEach is the extension of target by mixin.

The test simply ensures that target contains the properties id, getId, and setId after it has been extended. With only a stub implementation of idMixin, this test fails. Figure 20.10 shows the failure.

images

Figure 20.10  

The test can be enticed to pass by returning an object with the requisite properties from the idMixin module. Listing 20-12 updates idMixin to do so, and Figure 20.11 shows that the update causes the unit test to pass.

images

Figure 20.11  

As implemented by the attendee module, the getId method returns undefined if it is called before an attendee’s ID has been set. Once setId has been executed, subsequent execution of getId should return the value provided to setId. The tests in Listing 20-13 verify this functionality.

The first test in Listing 20-13 ensures that the value returned by getId is undefined if the ID hasn’t been previously set via a call to setId. The second test in the listing verifies that getId returns the ID that was provided to setId. Figure 20.12 shows that these tests currently fail.

images

Figure 20.12  

The implementation of idMixin that allows the test to pass is straightforward and appears in Listing 20-14.

The updated implementation of idMixin initializes the id property to undefined and provides the appropriate implementations of getId and setId.

Recall that the extend function copies references to the properties of the mixin into properties of the target object. As such, when the methods provided to the target by the mixin are executed, this will be bound to the target object (as long as the methods are executed via the target object; see Chapter 18 for more details). The idMixin is guaranteed that the object containing the getId and setId methods will also contain the id property because the mixin itself provides it.

The implementation of idMixin presented in Listing 20-14 allows all the unit tests to pass, as shown in Figure 20.13.

images

Figure 20.13  

With the idMixin fully implemented, the final step is to integrate it into the attendee module. Listing 20-15 contains an excerpt from the original unit tests for the attendee module from Chapter 6.

As you can see, the test verifies that setId and getId function appropriately. Because the idMixin should now be used to provide the getId and setId methods, they’re removed from the attendee implementation, as is the attendeeId variable used to store the ID. Unsurprisingly, doing so causes the unit test to fail, as shown in Figure 20.14.

images

Figure 20.14  

Listing 20-16 shows how to incorporate the idMixin into the attendee module to provide the getId and setId functions.

Rather than immediately returning an object, the attendee module now assigns the object containing the properties specific to an attendee into newAttendee. It then extends newAttendee with the object returned by idMixin, providing the getId/setId functionality that attendee previously defined itself. Finally, the extended newAttendee object is returned. Figure 20.15 shows that the attendee unit tests are once again passing.

images

Figure 20.15  

There’s one small, but significant, difference between attendee’s original implementation of getId and setId and the implementation of those methods as mixed in by the idMixin object. In the original implementation, the value provided to setId and returned from getId was captured in a variable that wasn’t accessible to external code, attendeeId. (Listing 20-1 shows the original version of attendee.) By hiding the attendeeId variable, the attendee module could be certain that the variable could only be manipulated via the setId method.

The idMixin, however, adds id as a property of the object being extended, and the getId and setId methods manipulate that property. Because it’s an exposed property of the object, id could be changed directly; using setId isn’t required:

// Create an attendee that's extended by idMixin
var extendedAttendee = Conference.attendee();

extendedAttendee.setId(12);
console.log(extendedAttendee.getId());  // 12

extendedAttendee.id = -1;
console.log(extendedAttendee.getId());  // -1 (!!!)

While exposing properties on the extended object may be appropriate and acceptable in some cases, it’s undesirable in this case. Does that mean the ID-related methods can’t be added to attendee via a mixin? No, it doesn’t.

Creating and Using a Functional Mixin

We were introduced to functional mixins by Angus Croll’s blog post, “A fresh look at JavaScript Mixins” (http://javascriptweblog.wordpress.com/2011/05/31/ a-fresh-look-at-javascript-mixins/). Functional mixins appeal to us for many reasons. Chief among their appealing aspects is the ability of functional mixins to hide data, something that we’d like for our mixin to implement unique identifier functionality.

In this section, we’ll illustrate the creation of a functional mixin that provides getId and setId methods and stores the ID in a variable that’s protected from external manipulation.

Unlike traditional mixins, functional mixins are designed to add themselves to the object they’re extending; use of extend (or a similar method) is not required. One drawback to this, however, is that each functional mixin must verify that the object being extended doesn’t already have a property it provides. Traditional mixins can rely on our implementation of the extend method’s behavior of verifying that a mixin will not overwrite a property of the object being extended.

That knowledge in hand, the first tests that you need to write ensure that the functional mixin addId doesn’t overwrite any existing properties, and that it throws errors instead. These tests appear in Listing 20-17.

The two tests are implemented as you might expect. The first attempts to extend a target that already implements getId, and the second attempts to extend a function that already implements setId. Both tests ensure that the correct error message is generated.

The mechanism used to extend target with the addId mixin should provide a hint about how addId will be implemented (or, if you’ve read Angus’s blog post, you already know):

Conference.mixins.addId.call(target);

The tests execute addId via call, providing the object being extended as the context to use. The call method, described in detail in Chapter 18, binds this within the function to the context object provided, in this case target. The addId mixin will add itself to target by manipulating this.

With a stub implementation of addId, the unit tests fail, as Figure 20.16 shows.

images

Figure 20.16  

Listing 20-18 provides an initial implementation of addId that performs the necessary property-checking.

Since this has been bound to the object being extended, the addIn function simply needs to examine to properties of this to ensure existing properties won’t be overwritten.

Figure 20.17 shows that the checks allow the unit tests to pass.

images

Figure 20.17  

Finally, tests that verify the following need to be written:

  • getId and setId are added to the object being extended.
  • getId returns undefined if an ID hasn’t been set.
  • getId and setId work correctly together.

With the exception of the verification of properties, the tests are identical to those that were written for the traditional mixin. The tests are provided in Listing 20-19.

From the standpoint of verification of the functionality of setId and getId, the tests for the addId functional mixin are identical to those for the idMixin traditional mixin.

A difference in the tests is highlighted in Listing 20-20: The test that ensured the correct properties were added to the extended object for idMixin needed to verify that the id property was added to the extended object; the corresponding test for addId does not. The current implementation of addId doesn’t satisfy the new unit tests, as Figure 20.18 shows.

images

Figure 20.18  

Listing 20-20 provides the full implementation of the addId functional mixin.

The most notable aspects of final implementation of the addId functional mixin are:

  • The functions getId and setId are added to the object being extended by augmenting this.
  • The variable id, manipulated by getId and setId, is not externally accessible.

Figure 20.19 shows that all of the unit tests for addId now pass.

images

Figure 20.19  

With a fully implemented addId functional mixin in hand, the final step is to integrate it into the attendee module. The first step is to remove the implementations of getId and setId, along with the variable used to store the ID, from attendee.

This is the same first step that was performed when the idMixin traditional mixin was added to the attendee; you can refer to Figure 20.14 for proof that removal of the implementations of getId and setId causes an attendee unit test to fail.

Listing 20-21 shows how to extend attendee with the addId mixin.

Once again, the attendee module captures the object containing the properties specific to attendee into newAttendee. It then extends newAttendee by calling addId with newAttendee provided as the object to which this is bound. The addId functional mixin adds the getId and setId methods to newAttendee. Finally, the extended newAttendee is returned. Because it isn’t added as a property of the extended object, the variable that is used to store the ID can only be changed via the setId method.

Figure 20.20 shows that the unit tests for attendee are all now passing.

images

Figure 20.20  

SUMMARY

This chapter explored how mixins may be used to DRY out your JavaScript by isolating common functionality, such as the maintenance of unique identifiers, into mixin objects.

Once appropriately isolated into a mixin, the functionality may be added to an object that requires it via a function that extends one object with another, in the case of a traditional mixin, or directly by the mixin itself, in the case of a functional mixin.

The chapter also discussed identifying functionality appropriate for isolation into a mixin, namely functionality that has no dependency upon the implementation of the objects into which it will be mixed. We also explained a potential pitfall of the extend methods provided by the jQuery and underscore libraries, the silent replacement of existing properties. We then avoided the pitfall in the version of extend that was implemented in this chapter.

Also, we demonstrated extension of an object via both a traditional mixin and a functional mixin by refactoring the attendee module.

When writing mixins, either traditional or functional, be sure to write unit tests to ensure:

In the next chapter, we tackle the unique and challenging aspects of ensuring the reliability of complex mediator-based and observer-based program architectures.