Listing 8.19 Using the same identifier for more than one formal parameter "test repeated identifiers in parameters": function { // Syntax error in ES5 strict mode function es3VsEs5a, a,
Trang 1results in only the last one to be reachable inside the function (except through arguments, in which all parameters are always reachable) Listing 8.19 shows the new behavior compared to the current one
Listing 8.19 Using the same identifier for more than one formal parameter
"test repeated identifiers in parameters": function () { // Syntax error in ES5 strict mode
function es3VsEs5(a, a, a) {
"use strict";
return a;
} // true in ES3 assertEquals(6, es3VsEs5(2, 3, 6));
} Attempts to access the caller or callee properties of the arguments object will throw a TypeError in strict mode
In ES3 (and non-strict ES5), the arguments object shares a dynamic rela-tionship with formal parameters Modify a formal parameter, and the value in the corresponding index of the argument object is modified too Modify a value of the argumentsobject, and the corresponding parameter changes In strict mode, this relationship goes away and arguments is immutable, as Listing 8.20 exemplifies
Listing 8.20 Relationship between arguments and formal parameters
function switchArgs(a, b) {
"use strict";
var c = b;
b = a;
a = c;
return [].slice.call(arguments);
} TestCase("ArgumentsParametersTest", {
"test should switch arguments": function () { // Passes on ES5 strict mode
assertEquals([3, 2], switchArgs(2, 3));
// Passes on ES3 // assertEquals([2, 3], switchArgs(2, 3));
} });
Trang 2thisis no longer coerced to an object in strict mode In ES3 and non-strict ES5, this will be coerced to an object if it is not one already For instance, when
using call or apply with function objects, passing in null or undefined will
no longer cause this inside the called function to be coerced into the global object
Neither will primitive values used as this be coerced to wrapper objects
8.3.2.3 Objects, Properties, and Variables
evaland arguments cannot be used as identifiers in ES5 strict mode Formal
parameters, variables, the exception object in a try-catch statement, and object
property identifiers are all affected by this restriction
In ES3 implementations, defining an object literal with repeated property iden-tifiers causes the latest one to overwrite the value of previous properties sharing
the identifier In strict mode, repeating an identifier in an object literal will cause a
syntax error
As we already saw, strict mode does not allow implicit globals Not only will im-plicit globals cause errors, but writing to any property of an object whose writable
attribute is false, or writing to a non-existent property of an object whose internal
[[Extensible]]property is false will throw TypeError as well
The delete operator will no longer fail silently in strict mode In ES3 and non-strict ES5, using the delete operator on a property whose configurable
attribute is false will not delete the property, and the expression will return false
to indicate that the deletion was not successful In strict mode, such deletion causes
a TypeError
8.3.2.4 Additional Restrictions
The with statement no longer exists in strict mode Using it will simply produce a
syntax error Some developers are less than impressed by this change, but the truth
is that it is too easy to use wrong, and easily makes code unpredictable and hard to
follow
Octal number literals, such as 0377 (255 decimal), are not allowed in strict mode, this also applies to parseInt("09")
8.4 Various Additions and Improvements
We have already seen most of the additions to the Object, but there is more to
ECMAScript 5 than empowered objects
Trang 38.4.1 Native JSON
ES5 introduces native JSON support, in form of the JSON object It supports two methods, JSON.stringify and JSON.parse to dump and load JSON respectively
Douglas Crockford’s json2.js provides a compatible interface for browsers that does not yet implement the new JSON interface This means that by loading this library, we can start using this particular feature today In fact, json2.js has been widely used for some time, and several browsers already support the native JSON object
Both ES5 and json2.js also adds Date.prototype.toJSON, which seri-alizes date objects as JSON by way of Date.prototype.toISOString, which
in turn uses a simplification of the ISO 8601 Extended Format The format is as follows: YYYY-MM-DDTHH:mm:ss.sssZ
8.4.2 Function.prototype.bind
The bind method, as described in Chapter 6, Applied Functions and Closures, is
native to ES5 This should mean improved performance, and less code for libraries
to maintain The previously provided implementation is mostly equivalent to the one provided by ES5 apart from a few details The native bind function returns
a native object, which itself has no prototype property Rather than creating a simple function that wraps the original function, a special type of internal object
is created that maintains the relationship to the bound function such that, e.g., the instanceofoperator works with the resulting function just like it would with the bound function
8.4.3 Array Extras
Lots of new functionality is added to arrays in ES5 Most of these stem from Mozilla’s JavaScript 1.6, which has been around for some time—long enough for, e.g., Safari’s JavaScriptCore to implement them as well ES5 also adds Array
isArray, which can determine if an object is an array by checking its internal [[Class]] property Because Object.prototype.toString exposes this prop-erty, including in ES3, it can be used to provide a conforming implementation, as seen in Listing 8.21
Listing 8.21 Implementing Array.isArray
if (!Array.isArray) { Array.isArray = (function () { function isArray(object) {
Trang 4return Object.prototype.toString.call(object) ==
"[object Array]";
} return isArray;
}());
}
In addition to the static isArray method, Array.prototype defines a host of new methods: indexOf, lastIndexOf, every, some, forEach, map,
filter, reduce, reduceRight
8.5 Summary
In this chapter we have taken a brief look at some changes in JavaScript’s (hopefully)
near future ECMAScript 5 brings the spec up to speed with innovation in the wild
and even brings some exciting new features to the language Setting the course for
future standards—specifically ECMAScript Harmony, the working group for the
next revision to the language—ES5 introduces strict mode, opt-in deprecation of
troublesome features from JavaScript’s childhood
Extensions to objects and properties open the door to interesting new ways
of structuring JavaScript programs JavaScript’s prototypal nature no longer needs
to be hidden behind class-like constructors, because new Object methods make
working with prototypal inheritance easier and clearer By finally allowing
develop-ers to both read and write property attributes, even for user-defined objects, ES5
enables better structured and more robust programs, better encapsulation, and
immutable objects
An overview of ES5, even as selective as here, can guide us in writing code that will more easily port to it once it’s widely adopted We will draw from this
inspiration in the TDD examples in Part III, Real-World Test-Driven Development
in JavaScript Before we dive into those examples, however, we will learn about
unobtrusive JavaScript and feature detection in the closing two chapters of Part II,
JavaScript for Programmers.
Trang 59
Unobtrusive JavaScript
In Chapter 2, The Test-Driven Development Process, we saw how test-driven
development can help create “clean code that works.” Unfortunately, even per-ceptibly clean code can cause problems, and on the web there are many degrees of
“working.” Unobtrusive JavaScript is a term coined to describe JavaScript applied
to websites in a manner that increases user value, stays out of the user’s way, and en-hances pages progressively in response to detected support Unobtrusive JavaScript
guides us in our quest for truly clean code; code that either works, or knowingly
doesn’t; code that behaves in any environment for any user
To illustrate the principles of unobtrusive JavaScript, we will review a particu-larly obtrusive tabbed panels implementation Equipped with our new knowledge,
we will build an improved replacement backed by unit tests
9.1 The Goal of Unobtrusive JavaScript
Accessible websites that work for as wide an audience as possible is the ultimate goal of unobtrusive JavaScript Its most important principles are separation of con-cerns and certainty over assumptions Semantic markup is in charge of document structure, and document structure only Semantic HTML not only enhances acces-sibility potential, it also provides a rich set of hooks for both CSS and JavaScript
to attach to Visual styles and layout are the responsibility of CSS; presentational attributes and elements should be avoided Behavior is the domain of JavaScript,
Trang 6and it should be applied through external scripts This means that inline scripts and
intrinsic event handlers are out of the question most of the time
The advantages of this technique are vast:
• Accessibility: A semantic document can make sense to a wider audience than
those with visual desktop browsers Describing content with suitable tags affords screen readers, search engine crawlers, and other user agents a better chance of making sense of content
• Flexibility: The document structure can be more easily modified without
requiring change to external sources The same kind of flexibility is achieved
in JavaScript and CSS Scripts can be refactored, tuned, and modified without requiring change to the underlying document Script features can more easily
be reused for new document structures
• Robustness: Building on top of a solid foundation, behavior can be added
progressively Applying feature detection, i.e., only adding features that can
be inferred to work, vastly decreases the chance of scripts blowing up and ruining the user’s experience Such a defensive approach to scripting is also known as progressive enhancement
• Performance: Using external scripts allows for better caching of scripts used
across web pages
• Extensibility: Separating scripts from the markup completely means we can
more easily add more progressive enhancement for new browsers as more advanced functionality is made available
9.2 The Rules of Unobtrusive JavaScript
Chris Heilmann is perhaps the most well-known advocate of unobtrusive JavaScript,
and he has written and talked extensively on the topic In 2007 he wrote “The Seven
Rules of Unobtrusive JavaScript”:
• Do not make any assumptions
• Find your hooks and relationships
• Leave traversing to the experts
• Understand browsers and users
• Understand Events
• Play well with others
• Work for the next developer
Trang 7Chapter 10, Feature Detection, provides a solution for the script-side of “Do not make assumptions” and Chapter 6, Applied Functions and Closures, went over some
techniques that help “Play well with others.” Test-driven development, as described
in Chapter 2, The Test-Driven Development Process, and the examples in Part III, Real-World Test-Driven Development in JavaScript, help us build clean code, which
for the most part takes care of “Work for the next developer.”
“Understanding Events” advises to use event handlers to decouple code Heil-mann promotes event delegation as an excellent technique to write lightweight scripts with loose coupling Event delegation takes advantage of the fact that most user events do not only occur on target elements, but also on every containing el-ement above it in the DOM hierarchy For instance, given a tabbed panel, there really is no need to attach click events to all the tabs in the panel It is sufficient to attach a single event handler to the parent element, and on each click determine which tab caused the event, and activate that tab Implementing events this way allows for much more flexible APIs, as for instance adding new tabs will not require any event handler logic at all Reducing the number of handlers reduces memory consumption and helps build snappier interfaces
“Find your hooks and relationships” and “Leave traversing to the experts”
both deal with separation of concerns By describing documents using rich semantic HTML, there are lots of natural hooks inherent in the document Again, imagine a tabbed panel; certain markup patterns can be discovered and converted to tabbed panels if necessary script support is available CSS can keep separate styles for
“enabled” and “disabled” scripted tab features
9.2.1 An Obtrusive Tabbed Panel
In contrast to such clean separation, consider the horribly obtrusive, yet disappoint-ingly common tabbed panel solution presented in Listing 9.1
Listing 9.1 An obtrusive implementation of a tabbed panel
<div id="cont-1">
<span class="tabs-nav tabs-selected"
style="float: left; margin-right: 5px;">
<span onclick="tabs = $('#cont-1 > tabs-nav');
tabs.removeClass('tabs-selected');
$(this).parent().addClass('tabs-selected');
var className = $(this).attr('class');
var fragment_id = /fragment-\d/.exec(className);
$('.tabs-container').addClass('tabs-hide');
Trang 8$('#'+fragment_id).removeClass('tabs-hide');"
class="fragment-1 nav">
Latest news
</span>
</span>
<span class="tabs-nav"
style="float: left; margin-right: 5px;">
<span onclick="tabs = $('#cont-1 > tabs-nav');
tabs.removeClass('tabs-selected');
$(this).parent().addClass('tabs-selected');
var className = $(this).attr('class');
var fragment_id = /fragment-\d/.exec(className);
$('.tabs-container').addClass('tabs-hide');
$('#'+fragment_id).removeClass('tabs-hide');"
class="fragment-2 nav">
Sports
</span>
</span>
</div>
<div class="tabs-container" id="fragment-1">
<div class="tabbertab">
<span style="margin: 0px 5px 0px 0px; float: left;">
<strong>Latest news</strong>
</span>
<div>
Latest news contents [ ]
</div>
</div>
</div>
<div class="tabs-container tabs-hide" id="fragment-2">
<div class="tabbertab">
<span style="margin: 0px 5px 0px 0px; float: left;">
<strong>Sports</strong>
</span>
<div>
Sports contents [ ]
</div>
</div>
</div>
<div class="tabs-container tabs-hide" id="fragment-3">
<div class="tabbertab">
<span style="margin: 0px 5px 0px 0px; float: left;">
<strong>Economy</strong>
</span>
<div>
Economy contents [ ]
Trang 9</div>
</div>
</div>
The gist of this solution is simply a list of links with inline event handlers that toggle the display of the corresponding panel of text This solution suffers from a plethora of issues:
• All panels but the default selected one will be completely inaccessible to users without JavaScript, or with insufficient JavaScript support (i.e., some screen readers, old browsers, old and new mobile devices)
• Progressive enhancement is not possible—either it works or it doesn’t
• Markup is heavyweight and senseless, reducing accessibility and increasing complexity of associated CSS
• Reusing scripts is practically impossible
• Testing scripts is practically impossible
• span elements are styled and scripted to act like internal anchors a elements provide this functionality for free
• Poorly written script introduces unintentional global variable tabs
• Script does not make use of markup context, instead using expensive selectors on every click to access panels and other tabs
9.2.2 Clean Tabbed Panel Markup
If “Find your hooks and relationships” can teach us anything, it is to start by writing semantic and valid markup, adding ids and classes sparingly to have enough hooks to add the scripted behavior Analyzing the tabbed panel as implemented in Listing 9.1, we can sum up the functionality pretty simply: One or more sections
of text is to be navigated by clicking “tabs”—links with the text section’s heading
as link text Reasonable markup for such a requirement could be as simple as the markup in Listing 9.2 Using HTML5 could further improve its clarity
Listing 9.2 Tabbed panels base; semantic markup
<div class="tabbed-panel">
<ol id="news-tabs" class="nav">
<li><a href="#news">Latest news</a></li>
<li><a href="#sports">Sports</a></li>
<li><a href="#economy">Economy</a></li>
</ol>
Trang 10<div class="section">
<h2><a name="news">Latest news</a></h2>
<p>Latest news contents [ ]</p>
</div>
<div class="section">
<h2><a name="sports">Sports</a></h2>
<p>Sports contents [ ]</p>
</div>
<div class="section">
<h2><a name="economy">Economy</a></h2>
<p>Economy contents [ ]</p>
</div>
</div>
Note that the containing element has the class name tabbed-panel This
is all we need to know The script built on top of this structure could simply look
for all elements with the class name tabs that contain an ordered list (navigation)
and sub-elements with class name section Once this structure is identified, the
script can convert the structure into a tabbed panels widget, so long as the required
functionality can be inferred to work
In the basic version we could possibly leave out the navigational markup, and add it in via script However, using anchors as a “jump to” menu can easily make
sense in a non-scripted version, and it frees us from too much script-based markup
building
This sort of markup also lends itself to easier styling The default styles for div.tabbed-panel will target the basic solution, aimed at environments in
which the panels are presented as a series of vertically stacked text boxes The
script that converts the structure into tabs and panels can add a single class name
to the containing element to trigger a separate view intended for the script-driven
tabs and panels look This way the script simply enables the functionality, and CSS
is still in complete control of the visual presentation
9.2.3 TDD and Progressive Enhancement
Incidentally, the progressive enhancement style of user interface coding goes well
with test-driven development By cleanly separating structure, layout, and behavior
we can keep the interface between script and markup at a minimum, enabling us
to unit test most of the logic without requiring the DOM Enabling TDD creates
a positive circle as code written guided by tests tends to focus even more strongly
on a clean separation of concerns The resulting decoupling allows for better code
reuse and faster tests