Chapter 1. The JavaScript Not-So-Simple Building Blocks
Most JavaScript functionality comes to us via a very basic set of objects and data types. The functionality associated with strings, numbers, and booleans is provided via the String, Number, and Boolean data types. Other fundamental functionality, including regular expressions, dates, and necessary mathematical operations, are provided via the built-in RegExp, Date, and Math objects, respectively.
The fundamental building blocks of JavaScript have changed over time, but their core functionality remains the same. In this chapter, I’m focusing less on the syntax associated with the objects, and more on understanding their place in JavaScript.
Note
A good, introductory overview of the JavaScript standard built-in objects can be found in the Mozilla Developer Network JavaScript Reference.
Differentiating Between a JavaScript Object, Primitive, and Literal
Problem
People toss around terms like object, primitive, and literal. What is the difference between the three, and how can you tell which is which?
Solution
A JavaScript literal represents a value of a specific type, such as a quoted string (String), floating-point number (Number), or boolean (Boolean):
"this is a string"
1.45
true
A JavaScript primitive is an instance of a particular data type, and there are five such in the language: String, Number, Boolean, null
, and undefined
. The following are examples of JavaScript primitives:
"this is a string"
null
Of the primitive data types, three have complementary constructor objects: String, Number, and Boolean. These objects provide access to the built-in properties and methods that allow us to do more than simple assignment and subsequent access:
var
str1
=
"this is a string"
;
console
.
log
(
str1
.
length
);
// using String object's length property
Note
Many of the examples in this book use the console.log()
function to display JavaScript results. The Console Is Your Friend provides a quick how-to on accessing the JavaScript console in modern browers, and Appendix A also provides directions for setting up your environment and running the code snippets found in the solutions.
Discussion
It may seem as if we’re working with simple strings or numbers when we declare a variable:
var
str1
=
"this is a simple string"
;
However, we’re actually creating doorways into an extensive set of functionality. Without reliance on JavaScript objects, we can assign a string, number, or boolean value to a variable and then access it at a later time. However, if we want to do more with the variable, we’ll need to use the data type’s complementary JavaScript object and its properties.
As an example, if we want to see the length of a string, we’ll access the String object’s length
property:
var
str1
=
"this is a simple string"
;
console
.
log
(
str1
.
length
);
// prints out 23 to browser console
Behind the scenes, when the code accesses a String object’s property on the literal, a new String object is created and its value is set to the value of the string contained in the variable. The length
property is accessed and printed out, and the newly created String object is discarded.
Note
JavaScript engines don’t have to actually create an object to wrap the primitive when you access object properties; they only have to emulate this type behavior.
There are exactly five primitive data types in JavaScript: string, number, boolean, null
, and undefined
. Only the string, number, and boolean data types have complementary constructor objects. The actual representation of strings, floating-point numbers, integers, and booleans are literals:
var
str1
=
"this is a simple string"
;
// the quoted string is the literal
var
num1
=
1.45
;
// the value of 1.45 is the literal
var
answer
=
true
;
// the values of true and false are boolean literals
We can create primitive boolean, string, and number variables either by using a literal representation or using the object without using the new
operator:
var
str1
=
String
(
"this is a simple string"
);
// primitive string
var
num1
=
Number
(
1.45
);
// primitive number
var
bool1
=
Boolean
(
true
);
// primitive boolean
To deliberately instantiate an object, use the new
operator:
var
str2
=
new
String
(
"this is a simple string"
);
// String object instance
var
num2
=
new
Number
(
1.45
);
// Number object instance
var
bool2
=
new
Boolean
(
true
);
// primitive boolean
You can quickly tell the difference between a primitive and an object instance when you compare an object instance to a literal value using strict equality. For example, running the following code in a browser:
var
str1
=
String
(
"string"
);
var
num1
=
Number
(
1.45
);
var
bool1
=
Boolean
(
true
);
if
(
str1
===
"string"
)
{
console
.
log
(
'equal'
);
}
if
(
num1
===
1.45
)
{
console
.
log
(
'equal'
);
}
if
(
bool1
===
true
)
{
console
.
log
(
'equal'
);
}
var
str2
=
new
String
(
"string"
);
var
num2
=
new
Number
(
1.45
);
var
bool2
=
new
Boolean
(
true
);
if
(
str2
===
"string"
)
{
console
.
log
(
'equal'
);
}
else
{
console
.
log
(
'not equal'
);
}
if
(
num2
===
1.45
)
{
console
.
log
(
'equal'
);
}
else
{
console
.
log
(
'not equal'
);
}
if
(
bool2
===
true
)
{
console
.
log
(
'equal'
);
}
else
{
console
.
log
(
'not equal'
);
}
Results in the following print outs to the console
:
equal
equal
equal
not
equal
not
equal
not
equal
The primitive variables (those not created with new
) are strictly equal to the literals, while the object instances are not. Why are the primitive variables strictly equal to the literals? Because primitives are compared by value, and values are literals.
For the most part, JavaScript developers don’t directly create object instances for the three primitive data types. Developers just want a number, boolean, or string variable to act like a number, boolean, or string, rather than an object; we don’t need the enhanced functionality of the object. More importantly, when developers use strict equality or type checking in the code, they want a variable to match their expectations of data type, rather than be defined as “object”:
var
num1
=
1.45
;
var
num2
=
new
Number
(
1.45
);
console
.
log
(
typeof
num1
);
// prints out number
console
.
log
(
typeof
num2
);
// prints out object
Code validators, such as JSHint, output a warning if you instantiate a primitive data type object directly for just this reason.
See Also
Checking for an Existing, Nonempty String has a more detailed look at the strict equality operators, as compared to the standard equality operators.
Extracting a List from a String
Problem
You have a string with several sentences, one of which includes a list of items. The list begins with a colon (:) and ends with a period (.), and each item is separated by a comma. You want to extract just the list.
Before:
This
is
a
list
of
items
:
cherries
,
limes
,
oranges
,
apples
.
After:
[
'cherries'
,
'limes'
,
'oranges'
,
'apples'
]
Solution
The solution requires two actions: extract out the string containing the list of items, and then convert this string into a list.
Use String’s indexOf()
to locate the colon, and then use it again to find the first period following the colon. With these two locations, extract the string using String’s substring()
:
var
sentence
=
'This is one sentence. This is a sentence with a list of items:'
+
'cherries, oranges, apples, bananas. That was the list of items.'
;
var
start
=
sentence
.
indexOf
(
':'
);
var
end
=
sentence
.
indexOf
(
'.'
,
start
+
1
);
var
listStr
=
sentence
.
substring
(
start
+
1
,
end
);
Once you have the string consisting of the list items, use the String split()
to break the string into an array:
var
fruits
=
listStr
.
split
(
','
);
console
.
log
(
fruits
);
// ['cherries', ' oranges', ' apples', ' bananas']
Discussion
The indexOf()
method takes a search value, as first parameter, and an optional beginning index position, as second.
The list is delimited by a beginning colon character and an ending period. The indexOf()
method is used without the second parameter in the first search, to find the colon. The method is used again but the colon’s position (plus 1
) is used to modify the beginning location of the search for the period:
var
end
=
sentence
.
indexOf
(
'.'
,
start
+
1
);
If we didn’t modify the search for the ending period, we’d end up with the location of the first sentence’s period rather than the period for the sentence containing the list.
Once we have the beginning and ending location for the list, we’ll use the substring()
method, passing in the two index values representing the beginning and ending positions of the string:
var
listStr
=
sentence
.
substring
(
start
+
1
,
end
);
The extracted string is:
cherries
,
oranges
,
apples
,
bananas
We’ll finish by using split()
to split the list into its individual values:
var
fruits
=
listStr
.
split
(
','
)
;
// ['cherries', ' oranges',
' apples'
,
' bananas'
]
There is another string extraction method, substr()
, that begins extraction at an index position marking the start of the substring and passing in the length of the substring as the second parameter. We can easily find the length just by subtracting the beginning position of the string from the end position:
var
listStr
=
sentence
.
substr
(
start
+
1
,
end
-
start
);
var
fruits
=
listStr
.
split
(
','
);
See Also
Another way to extract the string is to use regular expressions and the RegExp object, covered beginning in Replacing Patterns with New Strings.
Advanced
The result of splitting the extracted string is an array of list items. However, the items come with artifacts (leading spaces) from sentence white space. In most applications, we’ll want to clean up the resulting array elements.
We’ll discuss the Array object in more detail in Chapter 2, but for now, we’ll use the Array forEach()
method in addition to the String object’s trim()
method to clean up the array:
fruits
=
listStr
.
split
(
','
);
console
.
log
(
fruits
);
// [' cherries', ' oranges', ' apples', ' bananas']
fruits
.
forEach
(
function
(
elmnt
,
indx
,
arry
)
{
arry
[
indx
]
=
elmnt
.
trim
();
});
console
.
log
(
fruits
);
// ['cherries', 'oranges', 'apples", "bananas"]
The forEach()
method applies the function passed as parameter (the callback) to each array element. The callback function supports three arguments: the array element value, and optionally, the array element index and the array itself.
Another simpler approach is to pass a regular expression to the split()
that trims the result before it’s returned:
var
fruits
=
listStr
.
split
(
/\s*,\s*/
);
Now the matching returned value is just the string without the surrounding white space.
Note
The forEach()
method is also covered in Applying a Function Against Each Array Element. The code in this section mutates the array in place, which means it actually modifies the array as it’s traversed. Another nondestructive approach is to use the newer map()
Array method, covered in Applying a Function to Every Element in an Array and Returning a New Array.
Extra: Simplifying the Code Using Chaining
The example code in this recipe is correct, but a little verbose. We can compress the code by using JavaScript’s method chaining, allowing us to attach one method call to the end of a previous method call if the object and methods allow it. In this case, we can chain the split()
method directly to the substring()
method:
var
start
=
sentence
.
indexOf
(
":"
);
var
end
=
sentence
.
indexOf
(
"."
,
start
+
1
);
var
fruits
=
sentence
.
substring
(
start
+
1
,
end
).
split
(
","
);
The code isn’t more accurate, but it uses less space and can be easier to read. I’ll cover method chaining in more detail in Chaining Your Object’s Methods.
Checking for an Existing, Nonempty String
Solution
The simplest solution when testing for a nonempty string is the following:
if
(
typeof
unknownVariable
===
'string'
&&
unknownVariable
.
length
>
0
)
If the variable isn’t a string, the test will fail, and if the string’s length isn’t longer than zero (0), it will fail.
However, if you’re interested in testing for a string, regardless of whether it’s a String object or a string literal, you’ll need a different typeof
test, as well as test to ensure the variable isn’t null:
if
(((
typeof
unknownVariable
!=
'undefined'
&&
unknownVariable
)
&&
unknownVariable
.
length
()
>
0
)
&&
typeof
unknownVariable
.
valueOf
()
==
'string'
)
...
Discussion
You can use length
to find out how long the string is and test whether the string variable is an empty string (zero length):
if
(
strFromFormElement
.
length
==
0
)
// testing for empty string
However, when you’re working with strings and aren’t sure whether they’re set or not, you can’t just check their length, as you’ll get an undefined JavaScript error if the variable has never been set (or even declared). You have to combine the length test with another test for existence and this brings us to the typeof
operator.
The JavaScript typeof
operator returns the type of a variable. The list of possible returned values are:
-
number
if the variable is a number -
string
if the variable is a string -
boolean
if the variable is a Boolean -
function
if the variable is a function -
object
if the variable is null, an array, or another JavaScript object -
undefined
if the variable is undefined
Combining the test for a string and a test on the string length ensures our app knows if the variable is a non-zero length string or not:
if
(
typeof
unknownVariable
==
'string'
&&
unknownVariable
.
length
>
0
)
...
However, if you’re looking for a nonempty string regardless of whether the string is a literal or an object, than things get a little more interesting. A string that’s created using the String constructor:
var
str
=
new
String
(
'test'
);
has a typeof
value equal to object
not string
. We need a more sophisticated test.
First, we need a way to test whether a variable has been defined and isn’t null. The typeof
can be used to ensure the variable isn’t undefined:
if
(
typeof
unknownVariable
!=
'undefined'
)...
But it’s not sufficient, because null
variables have a typeof
value equal to object
.
So the defined and not null test is changed to check to see if the variable is defined and isn’t null
:
if
(
typeof
unknownVariable
!=
'undefined'
&&
unknownVariable
)
...
Just listing the variable is sufficient to test whether it’s null
or not.
We still don’t know, though, if the variable is a nonempty string. We’ll return the length
test, which should allow us to test whether the variable is a string, and is not empty:
if
((
typeof
unknownVariable
!=
'undefined'
&&
unknownVariable
)
&&
unknownVariable
.
length
>
0
)
...
If the variable is a number, the test fails because a number doesn’t have a length
. The String object and string literal variables succeed, because both support length
. However, an array also succeeds, because the Array object also supports length
.
To finish the test, turn to a little used method, valueOf()
. The valueOf()
method is available on all JavaScript objects, and returns the primitive (unwrapped) value of the object. For Number, String, and Boolean, valueOf()
returns the primitive value. So if the variable is a String object, valueOf()
returns a string literal. If the variable is already a string literal, applying the valueOf()
method temporarily wraps it in a String object, which means the valueOf()
method will still return a string literal.
Our finished test then becomes:
if
(((
typeof
unknownVariable
!=
"undefined"
&&
unknownVariable
)
&&
(
typeof
unknownVariable
.
valueOf
()
==
"string"
))
&&
Now, the test functions without throwing an error regardless of the value and type of the unknown variable, and only succeeds with a nonempty string, regardless of whether the string is a string object or literal.
Note
Our use of valueOf()
is limited. The method is primarily used by the JavaScript engine, itself, to handle conversions in instances when a primitive is expected and the engine is given an object.
The process is complex, and normally your application usually won’t have to be this extensive when testing a value. You’ll typically only need to test whether a variable has been set (typeof
returns the correct data type), or find the length of a string in order to ensure it’s not an empty string.
Extra: Loose and Strict Equality Operators
I used loose equality (== and !=) in this section, but I use strict equality (=== and !==) elsewhere in the book. My use of both types of operators is not a typo.
Some folks (Douglas Crockford being the most outspoken) consider the loose equality operators (== and !=) to be evil, and discourage their use. The main reason many developers eschew loose equality operators is that they test primitive values rather than the variable object, in totality, and the results of the test can be unexpected.
For instance, the following code succeeds:
var
str1
=
new
String
(
'test'
);
if
(
str1
==
'test'
)
{
...}
whereas this code fails:
var
str1
=
new
String
(
'test'
);
if
(
str1
===
'test'
)
{
...}
The first code snippet succeeds because the string literal (test
) and the primitive value the str1
variable contains are identical. The second code snippet fails the conditional test because the objects being compared are not equivalent, though they both share the same primitive value (test
): the str1
variable is a String object, while the compared value is a string literal.
While results of this comparison are relatively intuitive, others are less so. In the following code snippet, a string is compared to a number, and the results may be unexpected:
var
num
=
0
;
var
str
=
'0'
;
console
.
log
(
num
==
str
);
// true
console
.
log
(
num
===
str
);
// false
In the Abstract Equality Comparison Algorithm, when a string is compared to a number, the loose equality comparison is treated as if the following occurs:
console
.
log
(
num
===
toNumber
(
str
));
And therein lies the risk of loose equality: not understanding the implicit type conversion.
Sometimes, though, the very nature of the loose equality operator can be useful. For instance, the following code snippet demonstrates how the loose equality operator saves us time and code. The test to see if the variable is “bad” succeeds with standard equality regardless of whether the variable is undefined
or null
, where it wouldn’t succeed if strict equality had been used:
var
str1
;
if
(
str1
==
null
)
{
console
.
log
(
'bad variable'
);
}
Rather than using the first typeof
in the solution, I could shorten the test to the following and get the same result:
if
((
unknownVariable
!=
null
&&
unknownVariable
.
length
>
0
)
&&
typeof
unknownVariable
.
valueOf
()
==
'string'
)
...
Should you always use strict equality except in these rare instances? Just to ensure you don’t get unexpected results?
I’m going to buck the industry trend and say “No.” As long as you’re cognizant of how the equality operators work, and your code is such that you’re either only interested in primitive values or you want the object type coercion I just demonstrated, you can use loose equality operators in addition to strict equality.
Consider the following scenario: in a function, you’re testing to see if a counter is a certain value (100, for example). You’re expecting the counter, passed into the function, to be a number, but the developer who sent the value to your function passed it as a string.
When you perform the test, you are only interested in the value of the variable, not whether it’s a string or number. In this case, strict equality would fail, but not because the value isn’t what you’re testing for, but because the tested value and the function argument are different types. And the failure may be such that the developer using your function thinks that the application generating the value is in error, not that a type conversion hasn’t been made.
You don’t care in your function that the variable is a string and not a number. In this case, what you’re implicitly doing is converting the variable to what you expect and then doing the comparison. The following are equivalent:
if
(
counter
==
100
)
...
if
(
parseInt
(
counter
)
===
100
)
...
Note
If the type is critically important, then a first test should be on the type and a relevant error generated. But this is what I mean by being cognizant of your code.
In a more realistic scenario, you may be working with a string, and you don’t care if the person who passed the string value to your function used a String constructor to create the string, or used a string literal—all you care about is the primitive value:
var
str
=
'test'
;
var
str2
=
new
String
(
'test'
);
doSomething
(
str
);
doSomething
(
str2
);
...
function
doSomething
(
passedString
)
{
if
(
passedString
==
'test'
)
...
}
See Also
For more on the equality operators and their differences, as well as a view from the other side on the issue, I recommend JS101: Equality. The Mozilla Developer Network has a lovely, in-depth overview of the comparison operators and how they work in their documentation on comparison operators. And do check out the Abstract Equality Comparison Algorithm, directly.
Inserting Special Characters
Solution
Use one of the escape sequences in the string. For instance, to include the copyright symbol in a block of text to be added to the page (shown in Figure 1-1), use the escape sequence \u00A9
:
var
resultString
=
"<p>This page \u00A9 Shelley Powers </p>"
;
// print out to page
var
blk
=
document
.
getElementById
(
"result"
);
blk
.
innerHTML
=
resultString
;
Discussion
The escape sequences in JavaScript all begin with the backslash character, (\). This character signals the application processing the string that what follows is a sequence of characters that need special handling. Table 1-1 lists the other escape sequences.
Sequence | Character |
| Single quote |
| Double quote |
| Backslash |
| Backspace |
| Form feed |
| Newline |
| Carriage return |
| Horizontal tab |
| Octal sequence (3 digits: |
| Hexadecimal sequence (2 digits: |
| Unicode sequence (4 hex digits: |
The last three escape sequences in Table 1-1 are patterns, where providing different numeric values will result in differing escape sequences. The copyright symbol in the solution is an example of the Unicode sequence pattern.
The escape sequences listed in Table 1-1 can also be represented as a Unicode sequence. Unicode is a computing standard for consistent encoding, and a Unicode sequence is a specific pattern for a given character. For instance, the horizontal tab (\t
), can also be represented as the Unicode escape sequence, \u0009
. Of course, if the user agent disregards the special character, as browsers do with the horizontal tab, the use is moot.
One of the most common uses of escape sequences is to include double or single quotes within strings delimited by the same character:
var
newString
=
'You can\'t use single quotes '
+
'in a string surrounded by single quotes.'
+
'Oh, wait a sec...yes you can.'
;
Replacing Patterns with New Strings
Solution
Use the String’s replace()
method, with a regular expression:
var
searchString
=
"Now is the time, this is the tame"
;
var
re
=
/t\w{2}e/g
;
var
replacement
=
searchString
.
replace
(
re
,
"place"
);
console
.
log
(
replacement
);
// Now is the place, this is the place
Discussion
The solution also makes use of a global search. Using the global flag (g
) with the regular expression in combination with the String replace()
method will replace all instances of the matched text with the replacement string. If we didn’t use the global flag, only the first match would trigger a replacement.
The literal regular expression begins and ends with a slash (/
). As an alternative, I could have used the built-in RegExp object:
var
re
=
new
RegExp
(
't\\w{2}e'
,
"g"
);
var
replacement
=
searchString
.
replace
(
re
,
"place"
);
console
.
log
(
p
);
The difference is the surrounding slashes aren’t necessary when using RegExp, but the use of the backslash in the pattern has to be escaped. In addition, the global indicator is a second, optional argument to the RegExp constructor.
You can use a regular expression literal or a RegExp object instance interchangeably. The primary difference is that the RegExp constructor allows you to create the regular expression dynamically.
Extra: Regular Expression Quick Primer
Regular expressions are made up of characters that are used alone or in combination with special characters. For instance, the following is a regular expression for a pattern that matches against a string that contains the word technology and the word book, in that order, and separated by one or more whitespace characters:
var
re
=
/technology\s+book/
;
The backslash character (\
) serves two purposes: either it’s used with a regular character, to designate that it’s a special character, or it’s used with a special character, such as the plus sign (+), to designate that the character should be treated literally. In this case, the backslash is used with s, which transforms the letter s to a special character designating a whitespace character (space, tab, line feed, or form feed). The \s
special character is followed by the plus sign, \s+
, which is a signal to match the preceding character (in this example, a whitespace character) one or more times. This regular expression would work with the following:
technology
book
It would also work with the following:
technology
book
It would not work with the following, because there is no white space between the words:
technologybook
It doesn’t matter how much whitespace is between technology and book, because of the use of \s+
. However, using the plus sign does require at least one whitespace character.
Table 1-2 shows the most commonly used special characters in JavaScript applications.
Character | Matches | Example |
| Matches beginning of input |
|
| Matches end of input |
|
| Matches zero or more times |
|
| Matches zero or one time |
|
| Matches one or more times |
|
| Matches exactly n times |
|
| Matches n or more times |
|
| Matches at least n, at most m times |
|
| Any character except newline |
|
| Any character within brackets |
|
| Any character but those within brackets |
|
| Matches on word boundary |
|
| Matches on nonword boundary |
|
| Digits from 0 to 9 |
|
| Any nondigit character |
|
| Matches word character (letters, digits, underscores) |
|
| Matches any nonword character (not letters, digits, or underscores) |
|
| Matches a line feed | |
| A single whitespace character | |
| A single character that is not whitespace | |
| A tab | |
| Capturing parentheses | Remembers the matched characters |
Note
Regular expressions are powerful but can be tricky. I’m only covering them lightly in this book. If you want more in-depth coverage of regular expressions, I recommend the excellent Regular Expressions Cookbook by Jan Goyvaerts and Steven Levithan (O’Reilly).
See Also
Swapping Words in a String Using Capturing Parentheses shows variations of using regular expressions with the String replace
method, including the use of capturing parenthesis.
Finding and Highlighting All Instances of a Pattern
Solution
Use the RegExp exec
method and the global flag (g
) in a loop to locate all instances of a pattern, such as any word that begins with t and ends with e, with any number of characters in between:
var
searchString
=
"Now is the time and this is the time and that is the time"
;
var
pattern
=
/t\w*e/g
;
var
matchArray
;
var
str
=
""
;
// check for pattern with regexp exec, if not null, process
while
((
matchArray
=
pattern
.
exec
(
searchString
))
!=
null
)
{
str
+=
"at "
+
matchArray
.
index
+
" we found "
+
matchArray
[
0
]
+
"\n"
;
}
console
.
log
(
str
);
The results are:
at
7
we
found
the
at
11
we
found
time
at
28
we
found
the
at
32
we
found
time
at
49
we
found
the
at
53
we
found
time
Discussion
The RegExp exec()
method executes the regular expression, returning null
if a match is not found, or an object with information about the match, if found. Included in the returned array is the actual matched value, the index in the string where the match is found, any parenthetical substring matches, and the original string:
-
index
: The index of the located match -
input
: The original input string - [0]: The matched value
- [1],…,[n]+: Parenthesized substring matches, if any
The parentheses capture the matched values. Given a regular expression like that in the following code snippet:
var
re
=
/a(p+).*(pie)/ig
;
var
result
=
re
.
exec
(
"The apples in the apple pie are tart"
);
console
.
log
(
result
);
console
.
log
(
result
.
index
);
console
.
log
(
result
.
input
);
the resulting output is:
[
"apples in the apple pie"
,
"pp"
,
"pie"
]
4
"The apples in the apple pie are tart"
The array results contain the complete matched value at index zero (0), and the rest of the array entries are the parenthetical matches. The index
is the index of the match, and the input
is just a repeat of the string being matched. In the solution, the index where the match was found is printed out in addition to the matched value.
The solution also uses the global flag (g
). This triggers the RegExp object to preserve the location of each match, and to begin the search after the previously discovered match. When used in a loop, we can find all instances where the pattern matches the string. In the solution, the following are printed out:
at
7
we
found
the
at
11
we
found
time
at
28
we
found
the
at
32
we
found
time
at
49
we
found
the
at
53
we
found
time
Both time and the match the pattern.
Let’s look at the nature of global searching in action. In Example 1-1, a web page is created with a textarea
and an input text box for accessing both a search string and a pattern. The pattern is used to create a RegExp object, which is then applied against the string. A result string is built, consisting of both the unmatched text and the matched text, except the matched text is surrounded by a span
element (with a CSS class used to highlight the text). The resulting string is then inserted into the page, using the innerHTML
for a div
element.
<!DOCTYPE html>
<html>
<head>
<title>
Searching for strings</title>
<style>
.found
{
background-color
:
#ff0
;
}
</style>
</head>
<body>
<form
id=
"textsearch"
>
<textarea
id=
"incoming"
cols=
"150"
rows=
"10"
>
</textarea>
<p>
Search pattern:<input
id=
"pattern"
type=
"text"
/>
</p>
</form>
<button
id=
"searchSubmit"
>
Search for pattern</button>
<div
id=
"searchResult"
></div>
<script>
document
.
getElementById
(
"searchSubmit"
).
onclick
=
function
()
{
// get pattern
var
pattern
=
document
.
getElementById
(
"pattern"
).
value
;
var
re
=
new
RegExp
(
pattern
,
"g"
);
// get string
var
searchString
=
document
.
getElementById
(
"incoming"
).
value
;
var
matchArray
;
var
resultString
=
"<pre>"
;
var
first
=
0
;
var
last
=
0
;
// find each match
while
((
matchArray
=
re
.
exec
(
searchString
))
!=
null
)
{
last
=
matchArray
.
index
;
// get all of string up to match, concatenate
resultString
+=
searchString
.
substring
(
first
,
last
);
// add matched, with class
resultString
+=
"<span class='found'>"
+
matchArray
[
0
]
+
"</span>"
;
first
=
re
.
lastIndex
;
}
// finish off string
resultString
+=
searchString
.
substring
(
first
,
searchString
.
length
);
resultString
+=
"</pre>"
;
// insert into page
document
.
getElementById
(
"searchResult"
).
innerHTML
=
resultString
;
}
</script>
</body>
</html>
Figure 1-2 shows the application in action on William Wordsworth’s poem, “The Kitten and the Falling Leaves” after a search for the following pattern:
lea
(
f
|
ves
)
The bar (|
) is a conditional test, and will match a word based on the value on either side of the bar. So leaf matches, as well as leaves, but not leap.
You can access the last index found through the RegExp’s lastIndex
property. The lastIndex
property is handy if you want to track both the first and last matches.
See Also
Replacing Patterns with New Strings describes another way to do a standard find-and-replace behavior, and Swapping Words in a String Using Capturing Parentheses provides a simpler approach to finding and highlighting text in a string.
Swapping Words in a String Using Capturing Parentheses
Problem
You want to accept an input string with first and last name, and swap the names so the last name is first.
Solution
Use capturing parentheses and a regular expression to find and remember the two names in the string, and reverse them:
var
name
=
"Abe Lincoln"
;
var
re
=
/^(\w+)\s(\w+)$/
;
var
newname
=
name
.
replace
(
re
,
"$2, $1"
);
Discussion
Capturing parentheses allow us to not only match specific patterns in a string, but to reference the matched substrings at a later time. The matched substrings are referenced numerically, from left to right, as represented by $1
and $2
in the replace()
method.
In the solution, the regular expression matches two words separated by a space. Capturing parentheses were used with both words, so the first name is accessible using $1
, the last name with $2
.
The capturing parentheses aren’t the only special characters available with replace()
. Table 1-3 shows the other special characters that can be used with regular expressions and String replace()
.
Pattern | Purpose |
| Allows a literal dollar sign ($) in replacement |
| Inserts matched substring |
| Inserts portion of string before match |
| Inserts portion of string after match |
| Inserts n th captured parenthetical value when using RegExp |
The second pattern, which reinserts the matched substring, can be used to provide a simplified version of the Example 1-1 application shown in Finding and Highlighting All Instances of a Pattern. That example found and provided markup and CSS to highlight the matched substring. It used a loop to find and replace all entries, but in Example 1-2 we’ll use replace()
with the matched substring special pattern ($&
).
<!DOCTYPE html>
<html>
<head>
<title>
Searching for strings</title>
<style>
.found
{
background-color
:
#ff0
;
}
</style>
</head>
<body>
<form
id=
"textsearch"
>
<textarea
id=
"incoming"
cols=
"100"
rows=
"10"
>
</textarea>
<p>
Search pattern:<input
id=
"pattern"
type=
"text"
/>
</p>
</form>
<button
id=
"searchSubmit"
>
Search for pattern</button>
<div
id=
"searchResult"
></div>
<script>
document
.
getElementById
(
"searchSubmit"
).
onclick
=
function
()
{
// get pattern
var
pattern
=
document
.
getElementById
(
"pattern"
).
value
;
var
re
=
new
RegExp
(
pattern
,
"g"
);
// get string
var
searchString
=
document
.
getElementById
(
"incoming"
).
value
;
// replace
var
resultString
=
searchString
.
replace
(
re
,
"<span class='found'>$&</span>"
);
// insert into page
document
.
getElementById
(
"searchResult"
).
innerHTML
=
resultString
;
}
</script>
</body>
</html>
This is a simpler alternative, but the result isn’t quite the same: the line feeds aren’t preserved with Example 1-2, but they are with Example 1-1.
The captured text can also be accessed via the RegExp object using the exec()
method. Let’s return to the Swapping Words in a String Using Capturing Parentheses solution code, this time using exec()
:
var
name
=
"Abe Lincoln"
;
var
re
=
/^(\w+)\s(\w+)$/
;
var
result
=
re
.
exec
(
name
);
var
newname
=
result
[
2
]
+
", "
+
result
[
1
];
This approach is handy if you want to access the capturing parentheses values, but without having to use them within a string replacement.
Replacing HTML Tags with Named Entities
Problem
You want to paste example markup into a web page, and escape the markup (i.e., have the angle brackets print out rather than have the contents parsed).
Solution
Use regular expressions to convert angle brackets (<>
) into the named entities <
and >
:
var
pieceOfHtml
=
"<p>This is a <span>paragraph</span></p>"
;
pieceOfHtml
=
pieceOfHtml
.
replace
(
/</g
,
"<"
);
pieceOfHtml
=
pieceOfHtml
.
replace
(
/>/g
,
">"
);
console
.
log
(
pieceOfHtml
);
Discussion
It’s not unusual to want to paste samples of markup into another web page. The only way to have the text printed out, as is, without having the browser parse it, is to convert all angle brackets into their equivalent named entities.
The process is simple with the use of regular expressions and the String replace
method, as demonstrated in the solution. The key is to remember to use the global flag with the regular expression, to match all instances of the angle brackets.
Caution
Of course, if the regular expression finds the use of > or < in a mathematical or conditional expression, it will replace these, too.
Converting an ISO 8601 Formatted Date to a Date Object Acceptable Format
Problem
You need to convert an ISO 8601 formatted date string into values that can be used to create a new Date object instance.
Solution
Parse the ISO 8601 string into the individual date values, and use it to create a new JavaScript Date object instance:
var
dtstr
=
"2014-3-04T19:35:32Z"
;
dtstr
=
dtstr
.
replace
(
/\D/g
,
" "
);
var
dtcomps
=
dtstr
.
split
(
" "
);
// modify month between 1 based ISO 8601 and zero based Date
dtcomps
[
1
]
--
;
var
convdt
=
new
Date
(
Date
.
UTC
.
apply
(
null
,
dtcomps
));
console
.
log
(
convdt
.
toString
());
// Tue, 04 Mar 2014 19:35:32 GMT
Discussion
The ISO 8601 is an international standard that defines a representation for both dates and times. It’s not unusual for applications that provide APIs to require ISO 8601 formatting. It’s also not unusual for most dates to and from APIs to be in UTC, rather than local time.
The solution shows one variation of ISO 8601 formatting. The following demonstrate some others:
- 2009
- 2009-10
- 2009-10-15
- 2009-10-15T19:20
- 2009-10-15T19:20:20
- 2009-10-15T19:20:20.50
The values are year, month, date, then T to represent time, and hours, minutes, seconds, and fractions of sections. The time zone also needs to be indicated. If the date is in UTC, the time zone is represented by the letter Z, as shown in the solution:
2014
-
3
-
04
T19
:
35
:
32
Z
Otherwise, the time zone is represented as +hh:mm
to represent a time zone ahead of UTC, and -hh:mm
to represent a time zone behind UTC.
If you attempt to create a JavaScript Date with an ISO 8601 formatted string, you’ll get an invalid date error. Instead, you have to convert the string into values that can be used with the JavaScript Date.
The simplest way to parse an ISO 8601 formatted string is to use the String split()
method. To facilitate using split()
, all non-numeric characters are converted to one specific character. In the solution, the non-numeric characters are converted to a space:
dtstr
=
dtstr
.
replace
(
/\D/g
,
" "
);
The ISO-formatted string would be converted to:
2014
03
04
19
35
32
ISO months are one-based values of 1 through 12. To use the month value in JavaScript Date
s, the month needs to be adjusted by subtracting 1:
dtcomps
[
1
]
--
;
Finally, the new Date
is created. To maintain the UTC setting, the Date’s UTC()
method is used to create the date in universal time, which is then passed to the Date
constructor. Rather than listing out each and every single date value, the apply()
method is used, with null
as the first value, and all of the arguments as an array as the second:
var
convdt
=
new
Date
(
Date
.
UTC
.
apply
(
null
,
dtcomps
));
The task gets more challenging when you have to account for the different ISO 8601 formats. Example 1-3 shows a JavaScript application that contains a more complex JavaScript function that converts from ISO 8601 to allowable Date
values. The first test in the function ensures that the ISO 8601 format can be converted to a JavaScript Date. This means that, at a minimum, the formatted string must have a month, day, and year.
<!DOCTYPE html>
<html>
<head>
<title>
Converting ISO 8601 date</title>
</head>
<body>
<form>
<p>
Datestring in ISO 8601 format:<input
type=
"text"
id=
"datestring"
/>
</p>
</form>
<button
id=
"dateSubmit"
>
Convert Date</button>
<div
id=
"result"
></div>
<script
type=
"text/javascript"
>
document
.
getElementById
(
"dateSubmit"
).
onclick
=
function
()
{
var
dtstr
=
document
.
getElementById
(
"datestring"
).
value
;
var
convdate
=
convertISO8601toDate
(
dtstr
);
document
.
getElementById
(
"result"
).
innerHTML
=
convdate
;
}
function
convertISO8601toDate
(
dtstr
)
{
// replace anything but numbers by spaces
dtstr
=
dtstr
.
replace
(
/\D/g
,
" "
);
// trim any hanging white space
dtstr
=
dtstr
.
replace
(
/\s+$/
,
""
);
// split on space
var
dtcomps
=
dtstr
.
split
(
" "
);
// not all ISO 8601 dates can convert, as is
// unless month and date specified, invalid
if
(
dtcomps
.
length
<
3
)
return
"invalid date"
;
// if time not provided, set to zero
if
(
dtcomps
.
length
<
4
)
{
dtcomps
[
3
]
=
0
;
dtcomps
[
4
]
=
0
;
dtcomps
[
5
]
=
0
;
}
// modify month between 1 based ISO 8601 and zero based Date
dtcomps
[
1
]
--
;
var
convdt
=
new
Date
(
Date
.
UTC
.
apply
(
null
,
dtcomps
));
return
convdt
.
toUTCString
();
}
</script>
</body>
</html>
Another test incorporated into Example 1-3 is whether a time is given. If there aren’t enough array elements to cover a time, then the hours, minutes, and seconds are set to zero when the UTC date is created.
There are other issues related to dates not covered in the application. For instance, if the ISO 8601 formatted string isn’t in UTC time, converting it to UTC can require additional code, both to parse the time zone and to adjust the date to incorporate the time zone.
Note
Eventually, you won’t need this special processing, because ECMAScript 5 includes support for ISO 8601 dates in methods such as Date parse()
. However, implementation is still inconsistent across all major browsers—nonexistent in older browsers—so you’ll need these workarounds, or a shim, for now. See Using ES6 String Extras Without Leaving Users in the Dirt for more on using a shim.
Using Function Closures with Timers
Problem
You want to provide a function with a timer, but you want to add the function directly into the timer method call.
Solution
Use an anonymous function as first parameter to the setInterval()
or setTimeout()
method call:
intervalId
=
setInterval
(
function
()
{
x
+=
5
;
var
left
=
x
+
"px"
;
document
.
getElementById
(
"redbox"
).
style
.
left
=
left
;
},
100
);
Discussion
Unlike the other material covered in this chapter, JavaScript timers don’t belong to any of the basic built-in objects. Instead, they’re part of the basic Web API (previously known as the Browser Object Model, or BOM). In the browser, they’re properties of the Window object, the browser’s global object, though we don’t need to specify window
when accessing them. In Node.js, the two timer functions are part of the global object.
When you’re creating timers using setTimeout()
and setInterval()
, you can pass in a function variable as the first parameter:
function
bing
()
{
alert
(
'Bing!'
);
}
setTimeout
(
bing
,
3000
);
However, you can also use an anonymous function, as demonstrated in the solution. This approach is more useful, because rather than have to clutter up the global space with a function just to use with the timer, you can embed the function directly. In addition, you can use a variable local to the scope of the enclosing function when you use an anonymous function.
Example 1-4 demonstrates an anonymous function within a setInterval()
method call. The approach also demonstrates how the use of this function closure allows access to the parent function’s local variables within the timer method. In the example, clicking the red box starts the timer, and the box moves. Clicking the box again clears the timer, and the box stops. The position of the box is tracked in the x
variable, which is within scope for the timer function, as it operates within the scope of the parent function.
<!DOCTYPE html>
<head>
<title>
interval and anonymous function</title>
<style>
#redbox
{
position
:
absolute
;
left
:
100px
;
top
:
100px
;
width
:
200px
;
height
:
200px
;
background-color
:
red
;
}
</style>
</head>
<body>
<div
id=
"redbox"
></div>
<script>
var
intervalId
=
null
;
document
.
getElementById
(
'redbox'
).
addEventListener
(
'click'
,
startBox
,
false
);
function
startBox
()
{
if
(
intervalId
==
null
)
{
var
x
=
100
;
intervalId
=
setInterval
(
function
()
{
x
+=
5
;
var
left
=
x
+
"px"
;
document
.
getElementById
(
"redbox"
).
style
.
left
=
left
;
},
100
);
}
else
{
clearInterval
(
intervalId
);
intervalId
=
null
;
}
}
</script>
</body>
There’s no guarantee that the timer event fires when it is supposed to fire. Timers run on the same execution thread as all other user interface (UI) events, such as mouse-clicks. All events are queued and blocked, including the timer event, until its turn. So, if you have several events in the queue ahead of the timer, the actual time could differ—probably not enough to be noticeable to your application users, but a delay can happen.
See Also
John Resig offers an excellent discussion on how timers work, and especially the issues associated with event queues and single threads of execution.
Function closures are covered in more detail in Creating a Function That Remembers Its State. See function closures in timers in action in Tracking Elapsed Time.
Tracking Elapsed Time
Solution
Create a Date object when the first event occurs, a new Date object when the second event occurs, and subtract the first from the second. The difference is in milliseconds; to convert to seconds, divide by 1,000:
var
firstDate
=
new
Date
();
setTimeout
(
function
()
{
doEvent
(
firstDate
);
},
25000
);
function
doEvent
()
{
var
secondDate
=
new
Date
();
var
diff
=
secondDate
-
firstDate
;
console
.
log
(
diff
);
// approx. 25000
}
Discussion
Some arithmetic operators can be used with Date, but with interesting results. In the example, one Date instance can be subtracted from another, and the difference between the two is returned as milliseconds. However, if you add two dates together, the result is a string with the second Date instance concatenated to the first:
Thu
Oct
08
2009
20
:
20
:
34
GMT
-
0500
(
CST
)
Thu
Oct
08
2009
20
:
20
:
31
GMT
-
0500
(
CST
)
If you divide the Date instances, the dates are converted to their millisecond value, and the result of dividing one by the other is returned. Multiplying two dates will return a very large millisecond result.
Note
Only the Date instance subtraction operator really makes sense, but it’s interesting to see what happens with arithmetic operators and the Date object.
Converting a Decimal to a Hexadecimal Value
Solution
Use the Number toString()
method:
var
num
=
255
;
// displays ff, which is hexadecimal equivalent for 255
console
.
log
(
num
.
toString
(
16
));
Discussion
By default, numbers in JavaScript are base 10, or decimal. However, they can also be converted to a different radix, including hexadecimal (16) and octal (8). Hexadecimal numbers begin with 0x
(a zero followed by lowercase x), and octal numbers always begin with zero:
var
octoNumber
=
0255
;
// equivalent to 173 decimal
var
hexaNumber
=
0xad
;
// equivalent to 173 decimal
A decimal number can be converted to another radix, in a range from 2 to 36:
var
decNum
=
55
;
var
octNum
=
decNum
.
toString
(
8
);
// value of 67 octal
var
hexNum
=
decNum
.
toString
(
16
);
// value of 37 hexadecimal
var
binNum
=
decNum
.
toString
(
2
);
// value of 110111 binary
To complete the octal and hexadecimal presentation, you’ll need to concatenate the zero to the octal, and the 0x
to the hexadecimal value.
Although decimals can be converted to any base number (between a range of 2 to 36), only the octal, hexadecimal, and decimal numbers can be manipulated, directly as numbers. In addition, when using JavaScript strict mode, only decimal and hexadecimal literals are supported, as octal integers are no longer supported in JavaScript.
Extra: Speaking of Strict Mode
Strict mode is an ECMAScript 5 addition that signals the use of a more restricted version of the JavaScript language. Strict mode can be implemented for an entire script or only for a function. Triggering is simple:
'use strict'
;
or:
"use strict"
;
This code should be the first line in your script block or function.
When strict mode is engaged, a mistake that would normally be ignored now generates an error. What kind of mistake?
- Typos in variable names in assignment throw an error.
- Assignments that would normally fail quietly now throw an error.
- Attempting to delete an undeletable property fails.
- Using nonunique property names.
- Using nonunique function parameter names.
Strict mode also triggers other requirements:
- Octals aren’t supported in strict mode.
-
The
eval()
statement is limited, andwith
is not supported. -
When constructing a new object,
new
is required forthis
to function correctly.
Bottom line: strict mode helps eliminate unexpected and unexplainable results.
Summing All Numbers in a Table Column
Solution
Traverse the table column containing numeric string values, convert to numbers, and sum the numbers:
var
sum
=
0
;
// use querySelector to find all second table cells
var
cells
=
document
.
querySelectorAll
(
"td:nth-of-type(2)"
);
for
(
var
i
=
0
;
i
<
cells
.
length
;
i
++
)
{
sum
+=
parseFloat
(
cells
[
i
].
firstChild
.
data
);
}
Discussion
The global functions parseInt()
and parseFloat()
convert strings to numbers, but parseFloat()
is more adaptable when it comes to handling numbers in an HTML table. Unless you’re absolutely certain all of the numbers will be integers, parseFloat()
can work with both integers and floating-point numbers.
As you traverse the HTML table and convert the table entries to numbers, sum the results. Once you have the sum, you can use it in a database update, print it to the page, or pop up a message box, as the solution demonstrates.
You can also add a sum row to the HTML table. Example 1-5 demonstrates how to convert and sum up numeric values in an HTML table, and then how to insert a table row with this sum, at the end. The code uses document.querySelectorAll()
, which uses a different variation on the CSS selector, td + td
, to access the data this time. This selector finds all table cells that are preceded by another table cell.
<!DOCTYPE html>
<html>
<head>
<title>
Accessing numbers in table</title>
</head>
<body>
<table
id=
"table1"
>
<tr>
<td>
Washington</td><td>
145</td>
</tr>
<tr>
<td>
Oregon</td><td>
233</td>
</tr>
<tr>
<td>
Missouri</td><td>
833</td>
</tr>
</table>
<script
type=
"text/javascript"
>
var
sum
=
0
;
// use querySelector to find all second table cells
var
cells
=
document
.
querySelectorAll
(
"td + td"
);
for
(
var
i
=
0
;
i
<
cells
.
length
;
i
++
)
sum
+=
parseFloat
(
cells
[
i
].
firstChild
.
data
);
// now add sum to end of table
var
newRow
=
document
.
createElement
(
"tr"
);
// first cell
var
firstCell
=
document
.
createElement
(
"td"
);
var
firstCellText
=
document
.
createTextNode
(
"Sum:"
);
firstCell
.
appendChild
(
firstCellText
);
newRow
.
appendChild
(
firstCell
);
// second cell with sum
var
secondCell
=
document
.
createElement
(
"td"
);
var
secondCellText
=
document
.
createTextNode
(
sum
);
secondCell
.
appendChild
(
secondCellText
);
newRow
.
appendChild
(
secondCell
);
// add row to table
document
.
getElementById
(
"table1"
).
appendChild
(
newRow
);
</script>
</body>
</html>
Being able to provide a sum or other operation on table data is helpful if you’re working with dynamic updates via an Ajax operation, such as accessing rows of data from a database. The Ajax operation may not be able to provide summary data, or you may not want to provide summary data until a web page reader chooses to do so. The users may want to manipulate the table results, and then push a button to perform the summing operation.
Adding rows to a table is simple, as long as you remember the steps:
-
Create a new table row using
document.createElement("tr")
. -
Create each table row cell using
document.createElement("td")
. -
Create each table row cell’s data using
document.createTextNode()
, passing in the text of the node (including numbers, which are automatically converted to a string). - Append the text node to the table cell.
- Append the table cell to the table row.
- Append the table row to the table. Rinse, repeat.
If you perform this operation frequently, you can create functions for these operations, and package them into JavaScript libraries that you can reuse. Also, many of the available JavaScript libraries can do much of this work for you.
See Also
Wonder why I’m not using forEach()
with the results of the query? That’s because the querySelectorAll()
returns a NodeList, not an array, and forEach()
is an Array method. But there is a workaround, covered in Traversing the Results from querySelectorAll() with forEach() and call().
Extra: Modularization of Globals
The parseFloat()
and parseInt()
methods are global methods. As part of a growing effort to modularize JavaScript, both methods are now attached to the Number object, as new static methods, in ECMAScript 6:
var
num
=
Number
.
parseInt
(
'123'
);
The motive is good, but at the time this book was written, only Firefox supported the Number methods.
Converting Between Degrees and Radians
Problem
You have an angle in degrees. To use the value in the Math object’s trigonometric functions, you need to convert the degrees to radians.
Solution
To convert degrees to radians, multiply the value by (Math.PI
/ 180):
var
radians
=
degrees
*
(
Math
.
PI
/
180
);
To convert radians to degrees, multiply the value by (180 / Math.PI
):
var
degrees
=
radians
*
(
180
/
Math
.
PI
);
Discussion
All Math trigonometric methods (sin()
, cos()
, tin()
, asin()
, acos()
, atan()
, and atan2()
), take values in radians, and return radians as a result. Yet it’s not unusual for people to provide values in degrees rather than radians, as degrees are the more familiar unit of measure. The functionality covered in the solution provides the conversion between the two units.
Find the Radius and Center of a Circle to Fit Within a Page Element
Problem
Given the width and height of a page element, you need to find the center and radius of the largest circle that fits within that page element.
Solution
Find the smaller of the width and height; divide this by 2 to find the radius:
var
circleRadius
=
Math
.
min
(
elementWidth
,
elementHeight
)
/
2
;
Given the page element’s width and height, find the center by dividing both by 2:
var
x
=
elementWidth
/
2
;
var
y
=
elementHeight
/
2
;
Discussion
Working with graphics requires us to do things such as finding the center of an element, or finding the radius of the largest circle that will fit into a rectangle (or largest rectangle that can fit in a circle).
Example 1-6 demonstrates both of the solution calculations, modifying an SVG circle contained within an HTML document so that the circle fits within the div
element that surrounds it.
<!DOCTYPE html>
<html>
<head>
<title>
Using Math method to fit a circle</title>
<style
type=
"text/css"
>
#elem
{
width
:
600px
;
height
:
400px
;
border
:
1px
solid
black
;
}
</style>
<script
type=
"text/javascript"
>
window
.
onload
=
window
.
onresize
=
function
()
{
var
box
=
document
.
getElementById
(
"elem"
);
var
style
=
window
.
getComputedStyle
(
box
,
null
);
var
height
=
parseInt
(
style
.
getPropertyValue
(
"height"
));
var
width
=
parseInt
(
style
.
getPropertyValue
(
"width"
));
var
x
=
width
/
2
;
var
y
=
height
/
2
;
var
circleRadius
=
Math
.
min
(
width
,
height
)
/
2
;
var
circ
=
document
.
getElementById
(
"circ"
);
circ
.
setAttribute
(
"r"
,
circleRadius
);
circ
.
setAttribute
(
"cx"
,
x
);
circ
.
setAttribute
(
"cy"
,
y
);
}
</script>
</head>
<body>
<div
id=
"elem"
>
<svg
width=
"100%"
height=
"100%"
>
<circle
id=
"circ"
width=
"10"
height=
"10"
r=
"10"
fill=
"red"
/>
</svg>
</div>
</body>
Figure 1-3 shows the page once it’s loaded. There are techniques in SVG that can accomplish the same procedure using the SVG element’s viewPort
setting, but even with these, at some point in time you’ll need to dust off your basic geometry skills if you want to work with graphics. However, as the example demonstrates, most of the math you’ll need is basic.
Calculating the Length of a Circular Arc
Problem
Given the radius of a circle, and the angle of an arc in degrees, find the length of the arc.
Solution
Use Math.PI
to convert degrees to radians, and use the result in a formula to find the length of the arc:
// angle of arc is 120 degrees, radius of circle is 2
var
radians
=
degrees
*
(
Math
.
PI
/
180
);
var
arclength
=
radians
*
radius
;
// value is 4.18879020478...
Discussion
The length of a circular arc is found by multiplying the circle’s radius times the angle of the arc, in radians.
If the angle is given in degrees, you’ll need to convert the degree to radians first, before multiplying the angle by the radius.
See Also
Converting Between Degrees and Radians covers how to convert between degrees and radians.
Using ES6 String Extras Without Leaving Users in the Dirt
Problem
You want to use new ECMAScript 6 features, such as the string extras like startsWith()
and endsWith()
, but you don’t want your applications to break for people using browsers that don’t support this newer functionality.
Solution
Use an ECMAScript 6 (or ES 6) shim to provide support for the functionality in browsers not currently implementing it. Example 1-7 demonstrates how a shim enables support for the new ES 6 String functionality.
<!DOCTYPE html>
<html>
<head>
<meta
charset=
"utf-8"
>
<title>
ES 6 String</title>
<script
type=
"text/javascript"
src=
"es6-shim.js"
></script>
</head>
<body>
<script
type=
"text/javascript"
>
// quote from "To Kill a Mockingbird"
var
str
=
"Mockingbirds don't do one thing except make music "
+
"for us to enjoy. They don't eat up people's gardens, "
+
"don't nest in corn cribs, "
+
"they don’t do one thing but sing their hearts out for us. "
+
"That's why it’s a sin to kill a mockingbird."
console
.
log
(
str
.
startsWith
(
"Mockingbirds"
));
// true
console
.
log
(
str
.
startsWith
(
"autos"
,
20
));
// false
console
.
log
(
str
.
endsWith
(
"mockingbird."
));
// true
console
.
log
(
str
.
endsWith
(
"kill"
,
str
.
length
-
15
));
// true
var
cp
=
str
.
codePointAt
(
50
);
// 102 for 'f'
var
cp2
=
str
.
codePointAt
(
51
);
// 111 for 'o'
var
cp3
=
str
.
codePointAt
(
52
);
// 114 for 'r'
var
str2
=
String
.
fromCodePoint
(
cp
,
cp2
,
cp3
);
console
.
log
(
str2
);
// for
</script>
</body>
</html>
Discussion
JavaScript (or ECMAScript, the more proper name) is advancing much more rapidly now than in the past, but uneven implementation is still an issue. We do live in better times, as the major browser companies are more ready to embrace new features more quickly, and automated browser upgrades help eliminate some of the bogging down we had with a browser such as IE 6. In addition, until we see complete cross-browser support for a new feature, we can still make use of enhancements in Node.js applications on the server, and via the use of shims in the client. I’ll cover Node.js in a later chapter, but for now, let’s look at shims, JavaScript compatibility, and what they mean for something like the new String object enhancements.
Note
The shim used in the example is the ES6-shim created by Paul Miller. There are other shims and libraries known as polyfills, which you’ll see used elsewhere in this book.
The latest formal release of ECMAScript (ES) is ECMAScript 5, and I make use of several ES 5 features throughout the book. Work is underway, though, on the next generation of ES, appropriately named ES.Next (ECMA-262 Edition 6), but commonly referred to as ES 6.
As consensus is reached on new ES features, they’re added to the existing draft specification. They’re also listed in ES compatibility tables, such as the ones Mozilla incorporates in much of its documentation, and the exceedingly helpful ECMAScript 6 Compatibility Table.
Among the ES 6 additions are the following new String.prototype
methods:
-
startsWith
: Returnstrue
if string begins with characters from another string -
endsWith
: Returnstrue
if string ends with characters from another string -
contains
: Returnstrue
if string contains another string -
repeat
: Repeats the string a given number of times and returns the result -
codePointAt
: Returns the Unicode code point (unicode number) that starts at the given index
Both startsWith()
and endsWith()
require a string to examine as first parameter, and an optional integer as second parameter. For startsWith()
, the integer marks the position in the string to begin the search; for endsWith()
, the integer represents the position in the string where the search should terminate.
The contains()
method also takes two parameters—search string and optional starting position for search—but it returns true
or false
depending on whether it found the search string anywhere in the string:
console
.
log
(
str
.
contains
(
"gardens"
));
// true
The repeat()
method takes a given string and repeats it however many times is given in the only parameter, returning the result:
var
str2
=
'abc'
;
console
.
log
(
str2
.
repeat
(
2
));
// abcabc
The codePointAt()
method returns the UTF-16 encoded code point value for the character found at the position in the string. In addition, there’s also a new static method, fromCodePoint
, which returns a string created by a sequence of code points:
var
cp
=
str
.
codePointAt
(
50
);
// 102 for 'f'
var
cp2
=
str
.
codePointAt
(
51
);
// 111 for 'o'
var
cp3
=
str
.
codePointAt
(
52
);
// 114 for 'r'
var
str2
=
String
.
fromCodePoint
(
cp
,
cp2
,
cp3
);
console
.
log
(
str2
);
// for
At the time of this writing, if I were to access the web page in Example 1-7 without the use of the ES 6 shim, the JavaScript would fail for all but a developer release of Firefox. With the use of the shim, the JavaScript works for all modern browsers.
See Also
Another alternative to a shim is a transpiler that compiles tomorrow’s code into today’s environment. Google’s version, Traceur, is introduced in Using a Destructuring Assignment to Simplify Code, and demonstrated more fully in Creating a True Class and Extending It (with a Little Help from Traceur).
Get JavaScript Cookbook, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.