Now that you've mastered JavaScript's primitive data types, arrays, and functions, it is time for the best part—objects. In this chapter, you will learn:
How to create and use objects
What are the constructor functions
What types of built-in JavaScript objects exist and what they can do for you
As you already know from Chapter 2, an array is just a list of values. Each value has an index (a numeric key) starting from zero and incrementing by one for each value.
>>>> var myarr = ['red', 'blue', 'yellow', 'purple']; >>> myarr;
["red", "blue", "yellow", "purple"]
>>> myarr[0]
"red"
>>> myarr[3]
"purple"
If you put the indexes in one column and the values in another, you'll end up with a table of key/value pairs like this:
Key |
Value |
---|---|
0 |
red |
1 |
blue |
2 |
yellow |
3 |
purple |
An object is very similar to an array but with the difference that you define the keys yourself. You're not limited to using only numeric indexes but can use friendlier keys, such as first_name
, age
, and so on.
Let's take a look at a simple object and examine its parts:
var hero = { breed: 'Turtle', occupation: 'Ninja' };
You can see that:
The name of the variable that contains the object is hero
Instead of [
and ]
which you use to define an array, you use {
and }
for objects
You separate the elements (called properties) contained in the object with commas
The key/value pairs are divided by colons, as key:
value
The keys (names of the properties) can optionally be placed in quotation marks. For example these are all the same:
var o = {prop: 1}; var o = {"prop": 1}; var o = {'prop': 1};
It's recommended that you don't quote the names of the properties (it is also less typing!), but there are some cases when you have must use quotes:
If the property name is one of the reserved words in JavaScript (see Appendix A)
If it contains spaces or special characters (anything other than letters, numbers, and the underscore character)
If it starts with a number
Basically, if the name you have chosen for a property is not a valid name for a variable in JavaScript, then you need to in place in quotes.
Have a look at this bizarre-looking object:
var o = { something: 1, 'yes or no': 'yes', '!@#$%^&*': true };
This is a valid object. The quotes are required for the second and the third properties, otherwise you will get an error.
Later in this chapter you will see other ways to define objects and arrays, in addition to []
and {}
. But first, let's introduce this bit of terminology: defining an array with []
is called array literal notation and defining an object using the curly braces {}
is called object literal notation.
When talking about arrays, you say that they contain elements. When talking about objects, you say that they contain properties. There isn't any significant difference in JavaScript; it is just the terminology that people are used to, probably from other programming languages.
A property of an object can contain a function, because functions are just data. In this case, you say that this property is a method.
var dog = { name: 'Benji', talk: function(){ alert('Woof, woof!'), } };
It's also possible to store functions as array elements and invoke them, but you will not see much code like this in practice:
>>> var a = []; >>> a[0] = function(what){alert(what);}; >>> a[0]('Boo!'),
In some programming languages, there is distinction between:
A normal array, also called indexed or enumerated (the keys are numbers) and
An associative array, also called a hash (the keys are strings)
JavaScript uses arrays to represent indexed arrays and objects to represent associative arrays. If you want a hash in JavaScript, you use an object.
There are two ways to access a property of an object:
Using square bracket notation, for example hero['occupation']
Using the dot notation, for example hero.occupation
The dot notation is easier to read and write but it cannot always be used. The same rules apply as for quoting property names: if the name of the property is not a valid variable name, you cannot use the dot notation.
Let's take this object:
var hero = { breed: 'Turtle', occupation: 'Ninja' };
Accessing a property with the dot notation:
>>> hero.breed;
"Turtle"
Accessing a property with the bracket notation:
>>> hero['occupation'];
"Ninja"
Accessing a non-existing property returns undefined
:
>>> 'Hair color is ' + hero.hair_color;
"Hair color is undefined"
Objects can contain any data, including other objects.
var book = { name: 'Catch-22', published: 1961, author: { firstname: 'Joseph', lastname: 'Heller' } };
To get to the firstname
property of the object contained in the author
property of the book
object, you use:
>>> book.author.firstname
"Joseph"
Or using the square braces notation:
>>> book['author']['lastname']
"Heller"
It works even if you mix both:
>>> book.author['lastname']
"Heller"
>>> book['author'].lastname
"Heller"
One other case where you need square brackets is if the name of the property you need to access is not known beforehand. During runtime, it is dynamically stored in a variable:
"Joseph"
Because a method is just a property that happens to be a function, you can access methods in the same way as you would access properties: using the dot notation or using square brackets. Calling (invoking) a method is the same as calling any other function: just add parentheses after the method name, which effectively say "Execute!".
var hero = { breed: 'Turtle', occupation: 'Ninja', say: function() { return 'I am ' + hero.occupation; } } >>> hero.say();
"I am Ninja"
If there are any parameters that you want to pass to a method, you proceed as with normal functions:
>>> hero.say('a', 'b', 'c'),
Because you can use the array-like square brackets to access a property, this means you can also use brackets to access and invoke methods, although this is not at a common practice:
>>> hero['say']();
JavaScript is a dynamic language; it allows you to alter properties and methods of existing objects at any time. This includes adding new properties or deleting them. You can start with a blank object and add properties later. Let's see how you can go about doing this.
An empty object:
>>> var hero = {};
Accessing a non-existing property:
>>> typeof hero.breed
"undefined"
Adding some properties and a method:
>>> hero.breed = 'turtle'; >>> hero.name = 'Leonardo'; >>> hero.sayName = function() {return hero.name;};
Calling the method:
>>> hero.sayName();
"Leonardo"
Deleting a property:
>>> delete hero.name;
true
Calling the method again will no longer work:
>>> hero.sayName();
reference to undefined property hero.name
In the previous example, the method sayName()
used hero.name
to access the name
property of the hero
object. When you're inside a method though, there is another way to access the object this method belongs to: by using the special value this
.
var hero = { name: 'Rafaelo', sayName: function() { return this.name; } } >>> hero.sayName();
"Rafaelo"
So when you say this
, you are actually saying "this object" or "the current object".
There is another way to create objects: by using constructor functions. Let's see an example:
function Hero() { this.occupation = 'Ninja'; }
In order to create an object using this function, you use the new
operator, like this:
>>> var hero = new Hero(); >>> hero.occupation;
"Ninja"
The benefit of using constructor functions is that they accept parameters, which can be used when creating new objects. Let's modify the constructor to accept one parameter and assign it to the name
property.
function Hero(name) { this.name = name; this.occupation = 'Ninja'; this.whoAreYou = function() { return "I'm " + this.name + " and I'm a " + this.occupation; } }
Now you can create different objects using the same constructor:
>>> var h1 = new Hero('Michelangelo'), >>> var h2 = new Hero('Donatello'), >>> h1.whoAreYou();
"I'm Michelangelo and I'm a Ninja"
>>> h2.whoAreYou();
"I'm Donatello and I'm a Ninja"
By convention, you should capitalize the first letter of your constructor functions so that you have a visual clue that this is not a normal function. If you call a function that is designed to be a constructor, but you omit the new
operator, this is not an error, but it may not behave as you could expect.
>>> var h = Hero('Leonardo'), >>> typeof h
"undefined"
What happened here? As there was no new
operator, we didn't create a new object. The function was called like any other function, so h
contains the value that the function returns. The function does not return anything (there's no return
), so it actually returns undefined, which gets assigned to h
.
In this case, what does this
refer to? It refers to the global object.
Previously we discussed global variables (and how you should avoid them) and also the fact that JavaScript programs run inside a host environment (the browser for example). Now that you know about objects, it is time for the whole truth: the host environment provides a global object and all global variables are actually properties of the global object.
If your host environment is the web browser, the global object is called window
.
As an illustration, you can try declaring a global variable, outside of any function, such as:
>>> var a = 1;
Then you can access this global variable in various ways:
As a variable a
As a property of the global object, for example window['a']
or window.a
Let's go back to the case where you define a constructor function and call it without the new
operator. In such cases this
refers to the global object and all properties set with this
become properties of window
.
Declaring a constructor function and calling it without new
, returns "undefined":
>>> function Hero(name) {this.name = name;} >>> var h = Hero('Leonardo'), >>> typeof h
"undefined"
>>> typeof h.name
h has no properties
Because you had this
inside Hero
, a global variable (a property of the global object) called name
was created.
>>> name
"Leonardo"
>>> window.name
"Leonardo"
If you call the same constructor function but this time using new
, then a new object is returned and this
refers to it.
>>> var h2 = new Hero('Michelangelo'), >>> typeof h2
"object"
>>> h2.name
"Michelangelo"
The global functions you saw in Chapter 3 can also be invoked as methods of the window
object. So the following two codes are equivalent:
>>> parseInt('101 dalmatians')
101
101
When an object is created, a special property is assigned to it behind the scenes—the constructor
property. It contains a reference to the constructor function used to create this object.
Continuing from the previous example:
>>> h2.constructor
Hero(name)
Because the constructor
property contains a reference to a function, you might as well call this function to produce a new object. The following code is like saying, "I don't care how object h2
was created, but I want another one just like it".
>>> var h3 = new h2.constructor('Rafaello'), >>> h3.name;
"Rafaello"
If an object was created using the object literal notation, its constructor is the built-in Object()
constructor function (more about this later in this chapter).
>>> var o = {}; >>> o.constructor;
Object()
>>> typeof o.constructor;
"function"
Using the instanceof
operator, you can test if an object was created with a specific constructor function:
>>> function Hero(){} >>> var h = new Hero(); >>> var o = {}; >>> h instanceof Hero;
true
>>> h instanceof Object;
false
>>> o instanceof Object;
true
Note that you don't put parentheses after the function name (don't use h instanceof Hero()
). This is because you're not invoking this function, but just referring to it by name, as for any other variable.
In addition to using constructor functions and the new
operator to create objects, you can also use a normal function and create objects without new
. You can have a function that does some preparatory work and has an object as a return value.
For example, here's a simple factory()
function that produces objects:
function factory(name) { return { name: name }; }
Using the factory()
:
>>> var o = factory('one'), >>> o.name
"one"
>>> o.constructor
Object()
In fact, you can also use constructor functions and return objects, different from this
. This means you can modify the default behavior of the constructor function. Let's see how.
Here's the normal constructor scenario:
>>> function C() {this.a = 1;} >>> var c = new C(); >>> c.a
1
But now look at this scenario:
>>> function C2() {this.a = 1; return {b: 2};} >>> var c2 = new C2(); >>> typeof c2.a
"undefined"
>>> c2.b
2
What happened here? Instead of returning the object this
, which contains the property a
, the constructor returned another object that contains the property b
. This is possible only if the return value is an object. Otherwise, if you try to return anything that is not an object, the constructor will proceed with its usual behavior and return this
.
When you copy an object or pass it to a function, you only pass a reference to that object. Consequently, if you make a change to the reference, you are actually modifying the original object.
Here's an example of how you can assign an object to another variable and then make a change to the copy. As a result, the original object is also changed:
>>> var original = {howmany: 1}; >>> var copy = original; >>> copy.howmany
1
>>> copy.howmany = 100;
100
>>> original.howmany
100
The same thing applies when passing objects to functions:
>>> var original = {howmany: 100}; >>> var nullify = function(o) {o.howmany = 0;} >>> nullify(original); >>> original.howmany
0
When you compare objects, you'll get true
only if you compare two references to the same object. Comparing two distinct objects that happen to have the exact same methods and properties will return false
.
Let's create two objects that look the same:
>>> var fido = {breed: 'dog'}; >>> var benji = {breed: 'dog'};
Comparing them will return false
:
>>> benji === fido
false
>>> benji == fido
false
You can create a new variable mydog
and assign one of the objects to it, this way mydog
actually points to the same object.
>>> var mydog = benji;
In this case benji
is mydog
because they are the same object (changing mydog
's properties will change benji
's). The comparison returns true.
>>> mydog === benji
true
And because fido
is a different object, it does not compare to mydog
:
false
Before diving into the built-in objects in JavaScript, let's quickly say a few words about working with objects in the Firebug console.
After playing around with the examples in this chapter, you might have already noticed how objects are displayed in the console. If you create an object and type its name, you'll get a string representation of the object including the properties (but only the first few properties if there are too many of them).
The object is clickable and takes you to the DOM tab in Firebug, which lists all of the properties of the object. If a property is also an object, there is a plus ( + ) sign to expand it. This is handy as it gives you an insight into exactly what this object contains.
The console also offers you an object called console
and some methods, such as console.log()
, console.error()
, and console.info()
which you can use to display any value you want in the console.
console.log()
is convenient when you want to quickly test something, as well as in your real scripts when you want to dump some intermediate debuging information. Here's how you can experiment with loops, for example:
0
1
2
3
4
Earlier in this chapter we came across the Object()
constructor function. It is returned when you create objects with the object literal notation and access their constructor
property. Object()
is one of the built-in constructors; there are others and in the rest of this chapter you'll see all of them.
The built-in objects can be divided into three groups:
Data wrapper objects—Object
, Array
, Function
, Boolean
, Number
, and String
. These objects correspond to the different data types in JavaScript. Basically, there is a data wrapper object for each different value returned by typeof
(discussed in Chapter 2) with the exception of "undefined" and "null".
Utility objects—These are Math
, Date
, RegExp
and can come in very handy.
Error objects—The generic Error
object as well as other, more specific objects that can help your program recover its working state when something unexpected happens.
Only a handful of methods of the built-in objects will be discussed in this chapter. For a full reference, see Appendix C.
If you're confused about what is a built-in object and what is a built-in constructor, well, they are the same thing. In a moment, you will see how functions, and therefore constructor functions, are also objects.
Obj
ect
is the parent of all JavaScript objects, which means that every object you create inherits from it. To create a new empty object you can use the literal notation or the Object()
constructor function. The following two lines are equivalent:
>>> var o = {}; >>> var o = new Object();
An empty object is not completely useless because it already contains some methods and properties. Let's see a few:
o.constructor
property returns the constructor function
o.toString()
is a method that returns a string representation of the object
o.valueOf()
returns a single-value representation of the object, often this is the object itself
Let's see these methods in action. First, create an object:
>>> var o = new Object();
Calling toString()
returns a string representation of the object.
>>> o.toString()
"[object Object]"
toString()
will be called internally by JavaScript, when an object is used in a string context. For example alert()
works only with strings, so if you call the alert()
function passing an object, the method toString()
will be called behind the scenes. These two lines will produce the same result:
>>> alert(o) >>> alert(o.toString())
Ano
ther type of string context is the string concatenation. If you try to concatenate an object with a string, the object's toString()
will be called first:
>>> "An object: " + o
"An object: [object Object]"
valueOf()
is another method that all objects provide. For the simple objects (whose constructor is Object()
) the valueOf()
method will return the object itself.
>>> o.valueOf() === o
true
To summarize:
You can create objects either with var o = {};
(object literal notation, the preferred method) or with var o = new Object();
Any object, no matter how complex, inherits from the Object
object and therefore offers methods such as toString()
and properties such as constructor
.
Array()
is a built-in function that you can use as a constructor to create arrays:
This is equivalent to the array literal notation:
>>> var a = [];
No matter how the array is created, you can add elements to it as usual:
>>> a[0] = 1; a[1] = 2; a;
[1, 2]
When using the Array()
constructor, you can also pass values which will be assigned to the new array's elements.
>>> var a = new Array(1,2,3,'four'), >>> a;
[1, 2, 3, "four"]
An exception to this is when you pass a single number to the constructor. In this case, the number passed will be considered to be the length of the array.
>>> var a2 = new Array(5); >>> a2;
[undefined, undefined, undefined, undefined, undefined]
Because arrays are created with a constructor, does this mean that arrays are in fact objects? Yes, and you can verify this by using the typeof
operator:
>>> typeof a;
"object"
Because arrays are objects, this means that they inherit the properties and methods of the parent Object
.
>>> a.toString();
"1,2,3,four"
>>> a.valueOf()
[1, 2, 3, "four"]
>>> a.constructor
Array()
Arrays are objects, but of a special type because:
The names of their properties are automatically assigned using numbers starting from 0
They have a length
property which contains the number of elements in the array
They have additional built-in methods in addition to those inherited from the parent object
Let's examine the differences between an array and an object, starting by creating the empty object o
and the empty array a
:
>>> var a = [], o = {};
Array objects have a length
property automatically defined for them, while normal objects do not:
>>> a.length
0
>>> typeof o.length
"undefined"
It's OK to add both numeric and non-numeric properties to both arrays and objects:
>>> a[0] = 1; o[0] = 1; >>> a.prop = 2; o.prop = 2;
The length
property is always up-to-date with the number of numeric properties, ignoring the non-numeric ones.
>>> a.length
1
The length
property can also be set by you. Setting it to a greater value than the current number of items in the array creates empty elements (with a value of undefined
).
>>> a.length = 5
5
>>> a
[1, undefined, undefined, undefined, undefined]
Setting the length
to a lower value removes the trailing elements:
>>> a.length = 2;
2
>>> a
[1, undefined]
In addition to the methods inherited from the parent object, array objects also have some more useful methods, such as sort()
, join()
, and slice()
, among others (see Appendix C for the full list).
Let's take one array and experiment with some of these methods:
>>> var a = [3, 5, 1, 7, 'test'];
The push()
method appends a new element at the end of the array. The pop()
method removes the last element. a.push('new')
works just like a[a.length] = 'new'
and a.pop()
is the same as a.length--
.
push()
returns the length of the changed array, pop()
returns the element that it removed.
>>> a.push('new')
6
>>> a
[3, 5, 1, 7, "test", "new"]
>>> a.pop()
"new"
>>> a
[3, 5, 1, 7, "test"]
The sort()
method sorts the array and returns the modified array. In the next example, after the sort, both a
and b
contain pointers to the same array.
>>> var b = a.sort(); >>> b
[1, 3, 5, 7, "test"]
>>> a
[1, 3, 5, 7, "test"]
join()
returns a string containing the values of all the elements in the array, concatenated together using the string parameter passed to join()
>>> a.join(' is not '),
"1 is not 3 is not 5 is not 7 is not test"
slice()
returns a piece of the array without modifying the source array. The first parameter to slice()
is the start index and the second is the end index (both indices are zero-based).
>>> b = a.slice(1, 3);
[3, 5]
>>> b = a.slice(0, 1);
[1]
>>> b = a.slice(0, 2);
[1, 3]
After all the slicing, the source array is still the same:
>>> a
[1, 3, 5, 7, "test"]
splice()
modifies the source array. It removes a slice, returns it, and optionally fills the gap with new elements. The first two parameters define start and end of the slice to be removed; the other parameters pass the new values.
>>> b = a.splice(1, 2, 100, 101, 102);
[3, 5]
>>> a
[1, 100, 101, 102, 7, "test"]
Filling the gap with new elements is optional and you can skip it:
[100, 101, 102]
>>> a
[1, 7, "test"]
You already know that functions are a special data type. But it turns out that there's more to it than that—functions are actually objects. There is a built-in constructor function called Function()
which allows an alternative (but not recommended) way to create a function.
The following three ways of defining a function are equivalent:
>>> function sum(a, b) {return a + b;}; >>> sum(1, 2)
3
>>> var sum = function(a, b) {return a + b;}; >>> sum(1, 2)
3
>>> var sum = new Function('a', 'b', 'return a + b;'), >>> sum(1, 2)
3
When using the Function()
constructor, you pass the parameter names first (as strings) and then the source code for the body of the function (again as a string). The JavaScript engine then needs to evaluate the source code you pass and create the new function for you. This source code evaluation suffers from the same drawbacks as the eval()
function, so defining functions using the Function()
constructor should be avoided when possible.
If you use the Function
constructor to create functions that have lots of parameters, bear in mind that the parameters can be passed as a single comma-delimited list, so, for example, these are the same:
>>> var first = new Function('a, b, c, d', 'return arguments;'), >>> first(1,2,3,4);
[1, 2, 3, 4]
>>> var second = new Function('a, b, c', 'd', 'return arguments;'), >>> second(1,2,3,4);
[1, 2, 3, 4]
[1, 2, 3, 4]
Best Practice
Do not use the Function()
constructor. As with eval()
and setTimeout()
(discussed further in the book), always try to stay away from cases where you pass JavaScript code as a string.
Like any other object, functions have a constructor
property that contains a reference to the Function()
constructor function.
>>> function myfunc(a){return a;} >>> myfunc.constructor
Function()
Functions also have a length
property, which contains the number of parameters the function accepts.
>>> function myfunc(a, b, c){return true;} >>> myfunc.length
3
There is another interesting property, which doesn't exist in the ECMA standard, but is implemented across the browsers—the caller
property. This returns a reference to the function that called our function. Let's say there is a function A()
that gets called from function B()
. If inside A()
you put A.caller
, it will return the function B()
.
>>> function A(){return A.caller;} >>> function B(){return A();} >>> B()
B()
This could be useful if you want your function to respond differently depending on the function from which it was called. If you call A()
from the global space (outside of any function), A.caller
will be null
.
>>> A()
null
The most important property of a function is the prototype
property. We'll discuss this property in detail in the next chapter, but for now let's just say this:
The prototype
property of a function contains an object
It is only useful when you use this function as a constructor
All objects created with this function keep a reference to the prototype
property and can use its properties as their own
Let's see a quick example to demonstrate the prototype
property. Let's start with a simple object that has a property name
and a method say()
.
var some_obj = { name: 'Ninja', say: function(){ return 'I am a ' + this.name; } }
If you create a hollow function, you can verify that it automatically has a prototype
property that contains an empty object.
>>> function F(){} >>> typeof F.prototype
"object"
It gets interesting when you modify the prototype
property. You can replace the default empty object with any other object. Let's assign our some_obj
to the prototype.
>>> F.prototype = some_obj;
Now, using the function F()
as a constructor function, you can create a new object obj
which will have access to the properties of F.prototype
as if it were its own.
>>> var obj = new F(); >>> obj.name
"Ninja"
>>> obj.say()
"I am a Ninja"
There will be more about the prototype
property in the next chapter.
The function objects, being a descendant of the top parent Object
, get the default methods, such as toString()
. When invoked on a function, the toString()
method returns the source code of the function.
>>> function myfunc(a, b, c) {return a + b + c;} >>> myfunc.toString()
"function myfunc(a, b, c) {
return a + b + c;
}"
If you try to peek into the source code of the built-in functions, you'll get the hardly useful [native code]
string:
>>> eval.toString()
"function eval() {
[native code]
}"
Two useful methods of the function objects are call()
and apply()
. They allow your objects to borrow methods from other objects and invoke them as their own. This is an easy and powerful way to reuse code.
Let's say you have a some_obj
object, which contains the method say()
var some_obj = { name: 'Ninja', say: function(who){ return 'Haya ' + who + ', I am a ' + this.name; } }
You can call the say()
method which internally uses this.name
to gain access to its own name
property.
>>> some_obj.say('Dude'),
"Haya Dude, I am a Ninja"
Now let's create a simple object my_obj
, which only has a name
property:
>>> my_obj = {name: 'Scripting guru'};
my_obj
likes some_obj
's say()
method so much that it wants to invoke it as its own. This is possible using the call()
method of the say()
function object:
>>> some_obj.say.call(my_obj, 'Dude'),
"Haya Dude, I am a Scripting guru"
It worked! But what happened here? We invoked the call()
method of the say()
function object passing two parameters: the object my_obj
and the string 'Dude'. The result is that when say()
was invoked, the references to this
value that it contains, pointed to my_obj
. This way this.name
didn't return Ninja, but Scripting guru instead.
If you have more parameters to pass when invoking the call()
method, you just keep adding them:
some_obj.someMethod.call(my_obj, 'a', 'b', 'c'),
If you don't pass an object as a first parameter to call()
or pass null
, the global object will be assumed.
The method apply()
works the same way as call()
but with the difference that all parameters you want to pass to the method of the other object are passed as an array. The following two lines are equivalent:
some_obj.someMethod.apply(my_obj, ['a', 'b', 'c']); some_obj.someMethod.call(my_obj, 'a', 'b', 'c'),
Continuing the example above, you can use:
"Haya Dude, I am a Scripting guru"
In the previous chapter, you saw how, from inside a function, you have access to something called arguments
, which contains the values of all parameters passed to the function:
>>> function f() {return arguments;} >>> f(1,2,3)
[1, 2, 3]
arguments
looks like an array but is actually an array-like object. It resembles an array because it contains indexed elements and a length
property. However, the similarity ends here, as arguments
doesn't provide any of the array methods, such as sort()
or slice()
.
The arguments
object has another interesting property—the callee
property. This contains a reference to the function being called. If you create a function that returns arguments.callee
and you call this function, it will simply return a reference to itself.
>>> function f(){return arguments.callee;} >>> f()
f()
arguments.callee
allows anonymous functions to call themselves recursively. Here's an example:
( function(count){ if (count < 5) { alert(count); arguments.callee(++count); } } )(1)
Here you have an anonymous function that receives a count
parameter, alerts it, and then calls itself with an incremented count
. The whole function is wrapped in parentheses and followed by another set of parentheses, which invokes the function right away, passing the initial value 1. The result of this code is four alerts showing the numbers 1, 2, 3, and 4.
Our journey through the built-in objects in JavaScript continues, and the next ones are fairly easy; they merely wrap the primitive data types boolean, number, and string.
You already know a lot about booleans from Chapter 2. Now, let's meet the Boolean()
constructor:
>>> var b = new Boolean();
It is important to note that this creates a new object b
, and not a primitive boolean value. To get the primitive value, you can call the valueOf()
method (inherited from Object
).
>>> var b = new Boolean(); >>> typeof b
"object"
>>> typeof b.valueOf()
"boolean"
>>> b.valueOf()
false
Overall, objects created with the Boolean()
constructor are not too useful, as they don't provide any methods or properties, other than the inherited ones.
The Boolean()
function is useful when called as a normal function, without new
. This converts non-booleans to booleans (which is the same as using a double negation !!value
).
>>> Boolean("test")
true
>>> Boolean("")
false
>>> Boolean({})
true
Apart from the six falsy values, everything else is truthy in JavaScript, including the empty objects. This also means that all boolean objects created with new Boolean()
evaluate to true, as they are objects.
Let's create two boolean objects, one truthy and one falsy:
>>> var b1 = new Boolean(true) >>> b1.valueOf()
true
>>> var b2 = new Boolean(false) >>> b2.valueOf()
false
Now let's convert them to primitive boolean values. They both convert to true
because all objects are truthy.
>>> Boolean(b1)
true
>>> Boolean(b2)
true
Similarly to Boolean()
, the Number()
function can be used:
As a normal function in order to try to convert any value to a number. This is similar to the use of parseInt()
or parseFloat().
As a constructor function (with new
) to create objects
>>> var n = Number('12.12'), >>> n
12.12
>>> typeof n
"number"
>>> var n = new Number('12.12'), >>> typeof n
"object"
Because functions are objects, they can have properties. The Number()
function contains some interesting built-in properties (which you cannot modify):
>>> Number.MAX_VALUE
1.7976931348623157e+308
>>> Number.MIN_VALUE
5e-324
>>> Number.POSITIVE_INFINITY
Infinity
>>> Number.NEGATIVE_INFINITY
-Infinity
>>> Number.NaN
NaN
The number objects provide three methods—toFixed()
, toPrecision()
and toExponential()
(see Appendix C for details).
>>> var n = new Number(123.456) >>> n.toFixed(1)
"123.5"
Note that you can use these methods without explicitly creating a number object. In such cases, the number object will be created (and destroyed) for you behind the scenes:
>>> (12345).toExponential()
"1.2345e+4"
As with all objects, number objects also provide the toString()
method. It is interesting to note that this method accept an optional radix parameter (10 is the default).
>>> var n = new Number(255); >>> n.toString();
"255"
>>> n.toString(10);
"255"
>>> n.toString(16);
"ff"
>>> (3).toString(2);
"11"
"3"
Using the String()
constructor function you can create string objects. Objects produced this way provide some useful methods when it comes to text manipulation, but if you don't plan on using these methods, you're probably better off just using primitive strings.
Here's an example that shows the difference between a string object and a primitive string data type.
>>> var primitive = 'Hello'; >>> typeof primitive;
"string"
>>> var obj = new String('world'), >>> typeof obj;
"object"
A string object is very similar to an array of characters. The string objects have an indexed property for each character and they also have a length
property.
>>> obj[0]
"w"
>>> obj[4]
"d"
>>> obj.length
5
To extract the primitive value from the string object, you can use the valueOf()
or toString()
methods inherited from Object
. You'll probably never need to do this, as toString()
is called behind the scenes if you use an object in a string context.
>>> obj.valueOf()
"world"
>>> obj.toString()
"world"
>>> obj + ""
"world"
Primitive strings are not objects, so they don't have any methods or properties. But JavaScript still offers you the syntax to treat primitive strings as objects.
In the following example, string objects are being created (and then destroyed) behind the scenes every time you access a primitive string as if it was an object:
>>> "potato".length
6
>>> "tomato"[0]
"t"
>>> "potato"["potato".length - 1]
"o"
One final example to illustrate the difference between a string primitive and a string object: let's convert them to boolean. The empty string is a falsy value, but any string object is truthy.
>>> Boolean("")
false
>>> Boolean(new String(""))
true
Similarly to Number()
and Boolean()
, if you use the String()
function without new
, it converts the parameter to a primitive string. This means calling toString()
method, if the input is an object.
>>> String(1)
"1"
>>> String({p: 1})
"[object Object]"
>>> String([1,2,3])
"1,2,3"
Let's experiment with some of the methods you can call for string objects (see Appendix C for the full list).
Start off by creating a string object:
>>> var s = new String("Couch potato");
toUpperCase()
and toLowerCase()
are convenient ways to transform the capitalization of the string:
>>> s.toUpperCase()
"COUCH POTATO"
>>> s.toLowerCase()
"couch potato"
charAt()
tells you the character found at the position you specify, which is the same as using square brackets (a string is an array of characters).
>>> s.charAt(0);
"C"
>>> s[0]
"C"
If you pass a non-existing position to charAt()
, you get an empty string:
>>> s.charAt(101)
""
indexOf()
allows you to search within a string. If there is a match, the method returns the position at which the first match is found. The position count starts at 0, so the second character in "Couch" is "o" at position 1.
>>> s.indexOf('o')
1
You can optionally specify where (at what position) to start the search. The following finds the second "o", because indexOf()
is instructed to start the search at position 2:
>>> s.indexOf('o', 2)
7
lastIndexOf()
starts the search from the end of the string (but the position of the match is still counted from the beginning):
>>> s.lastIndexOf('o')
11
You can search for strings, not only characters, and the search is case sensitive:
>>> s.indexOf('Couch')
0
If there is no match, the function returns a position -1:
>>> s.indexOf('couch')
-1
To perform a case-insensitive search you can transform the string to lowercase first and then search it:
>>> s.toLowerCase().indexOf('couch')
0
When you get 0, this means that the matching part of the string starts at position 0. This can cause confusion when you check with if
, because if
will convert the position 0 to a boolean false
. So while this is syntactically correct, it is logically wrong:
if (s.indexOf('Couch')) {...}
The proper way to check if a string contains another string is to compare the result of indexOf()
to the number -1.
if (s.indexOf('Couch') !== -1) {...}
slice()
and substring()
return a piece of the string when you specify start and end positions:
>>> s.slice(1, 5)
"ouch"
>>> s.substring(1, 5)
"ouch"
Note that the second parameter you pass is the end position, not the length of the piece. The difference between these two methods is how they treat negative arguments. substring()
treats them as zeros, while slice()
adds them to the length of the string. So if you pass parameters (1, -1) it's the same as substring(1,
0)
and slice(1,
s.length
-
1)
:
>>> s.slice(1, -1)
"ouch potat"
>>> s.substring(1, -1)
"C"
The split()
method creates an array from the string, using a string that you pass as a separator:
>>> s.split(" ")
["Couch", "potato"]
split()
is the opposite of join()
which creates a string from an array:
>>> s.split(' ').join(' '),
"Couch potato"
concat()
glues strings together, the way the + operator does for primitive strings:
>>> s.concat("es")
"Couch potatoes"
Note that while some of the methods discussed above return new primitive strings, none of them modify the source string. After all the methods we called, our initial string is still the same:
>>> s.valueOf()
"Couch potato"
We looked at indexOf()
and lastIndexOf()
to search within strings, but there are more powerful methods (search()
, match()
, and replace()
) which take regular expressions as parameters. You'll see these later, when we get to the RegExp()
constructor function.
At this point we're done with all of the data wrapper objects, so off we go to the utility objects Math
, Date
, and RegExp.
Math
is a little different from the other built-in global objects you saw above. It's not a normal function and therefore cannot be used with new
to create objects. Math
is a built-in global object, which provides a number of methods and properties that are useful for mathematical operations.
Math
's properties are constants so you can't change their values. Their names are all in upper case to emphasize the difference between them and a normal variable property. Let's see some of these constant properties:
The number π:
>>> Math.PI
3.141592653589793
Square root of 2:
>>> Math.SQRT2
1.4142135623730951
Euler's constant e:
>>> Math.E
2.718281828459045
Natural logarithm of 2:
>>> Math.LN2
0.6931471805599453
Natural logarithm of 10:
>>> Math.LN10
2.302585092994046
Now you know how to impress your friends the next time they (for whatever awkward reason) start wondering, "What was the value of e? I can't remember." Just type Math.E
in the console and you have the answer.
Let's take a look at some of the methods the Math
object provides (the full list is in Appendix C).
>>> Math.random()
0.3649461670235814
random()
returns a number between 0 and 1, so if you want a number between, let's say 0 and 100, you can do:
>>> 100 * Math.random()
For numbers between any two values min
and max
, use the formula ((max
-
min) * Math.random())
+
min
. For example, a random number between 2 and 10 would be:
>>> 8 * Math.random() + 2
9.175650496668485
If you only need an integer, you can use one of rounding methods—floor()
to round down, ceil()
to round up or round()
to round to the nearest. For example to get either 0 or 1:
>>> Math.round(Math.random())
If you need the lowest or the highest among a set of numbers, you have the methods min()
and max()
. So if you have a form on a page that asks for a valid month, you can make sure that you always work with sane data:
>>> Math.min(Math.max(1, input), 12)
The Math
object also provides you with the ability to perform mathematical operations for which you don't have a designated operator. This means that you can raise to a power using pow()
, find the square root using sqrt()
and perform all the trigonometric operations—sin()
, cos()
, atan()
, and so on.
2 to the power of 8:
>>> Math.pow(2, 8)
256
>>> Math.sqrt(9)
3
Date()
is a constructor function that creates date objects. You can create a new object by passing:
Nothing (defaults to today's date)
A date-like string
Separate values for day, month, time, and so on
A timestamp
An object instantiated with today's date/time:
>>> new Date()
Tue Jan 08 2008 01:10:42 GMT-0800 (Pacific Standard Time)
(As with all objects, the Firefox console displays the result of the toString()
method, so this long string "Tue Jan 08...." is what you get when you call toString()
on a date object.)
Here are some examples of using strings to initialize a date object. It's interesting how many different formats you can use to specify the date.
>>> new Date('2009 11 12')
Thu Nov 12 2009 00:00:00 GMT-0800 (Pacific Standard Time)
>>> new Date('1 1 2012')
Sun Jan 01 2012 00:00:00 GMT-0800 (Pacific Standard Time)
>>> new Date('1 mar 2012 5:30')
Thu Mar 01 2012 05:30:00 GMT-0800 (Pacific Standard Time)
It is good that JavaScript can figure out a date from different strings, but this is not really a reliable way of defining a precise date. The better way is to pass numeric values to the Date()
constructor representing:
Year
Month: 0 (January) to 11 (December)
Day: 1 to 31
Hour: 0 to 23
Minutes: 0 to 59
Seconds: 0 to 59
Milliseconds: 0 to 999
Let's see some examples.
Passing all the parameters:
>>> new Date(2008, 0, 1, 17, 05, 03, 120)
Tue Jan 01 2008 17:05:03 GMT-0800 (Pacific Standard Time)
Passing date and hour:
>>> new Date(2008, 0, 1, 17)
Tue Jan 01 2008 17:00:00 GMT-0800 (Pacific Standard Time)
Watch out for the fact that the month starts from 0, so 1 is February:
>>> new Date(2008, 1, 28)
Thu Feb 28 2008 00:00:00 GMT-0800 (Pacific Standard Time)
If you pass a value greater than allowed, your date "overflows" forward. Because there's no February 30 in 2008, this means it has to be March 1st (remember that 2008 was a leap-year).
>>> new Date(2008, 1, 29)
Fri Feb 29 2008 00:00:00 GMT-0800 (Pacific Standard Time)
>>> new Date(2008, 1, 30)
Sat Mar 01 2008 00:00:00 GMT-0800 (Pacific Standard Time)
Similarly, Dec 32nd becomes Jan 01st of the next year:
>>> new Date(2008, 11, 31)
Wed Dec 31 2008 00:00:00 GMT-0800 (Pacific Standard Time)
>>> new Date(2008, 11, 32)
Thu Jan 01 2009 00:00:00 GMT-0800 (Pacific Standard Time)
Finally, a date object can be initialized with a timestamp (the number of milliseconds since the UNIX epoch, where 0 milliseconds is 1st January 1970).
>>> new Date(1199865795109)
Wed Jan 09 2008 00:03:15 GMT-0800 (Pacific Standard Time)
If you call Date()
without new
, you get a string representing the current date, whether or not you pass any parameters. This gives the current time (current when this example was run):
>>> Date()
"Thu Jan 17 2008 23:11:32 GMT-0800 (Pacific Standard Time)"
>>> Date(1, 2, 3, "it doesn't matter");
"Thu Jan 17 2008 23:11:35 GMT-0800 (Pacific Standard Time)"
Once you've created a date object, there are lots of methods you can call on that object. Most of the methods can be divided into set*()
and get*()
methods. For example getMonth()
, setMonth()
, getHours()
, setHours()
, and so on. Let's see some examples.
Creating a date object:
>>> var d = new Date(); >>> d.toString();
"Wed Jan 09 2008 00:26:39 GMT-0800 (Pacific Standard Time)"
Setting the month to March (months start from 0):
>>> d.setMonth(2);
1205051199562
>>> d.toString();
"Sun Mar 09 2008 00:26:39 GMT-0800 (Pacific Standard Time)"
Getting the month:
>>> d.getMonth();
2
In addition to all the methods of the date instances, there are also two methods that are properties of the Date()
function/object. These do not need a date instance; they work just like Math
's methods. In class-based languages, such methods would be called "static" because they don't require an instance.
Date.parse()
takes a string and returns a timestamp:
>>> Date.parse('Jan 1, 2008')
1199174400000
Date.UTC()
takes all parameters for year, month, day, and so on, and produces a timestamp in Universal time.
>>> Date.UTC(2008, 0, 1)
1199145600000
Because the new Date()
constructor can accept timestamps, you can pass the result of Date.UTC()
to it. Using the following example you can see how UTC()
works with universal time, while new Date()
works with local time:
>>> new Date(Date.UTC(2008, 0, 1));
Mon Dec 31 2007 16:00:00 GMT-0800 (Pacific Standard Time)
>>> new Date(2008, 0, 1);
Tue Jan 01 2008 00:00:00 GMT-0800 (Pacific Standard Time)
Let's see one final example of working with the Date
object. I was curious about which day my birthday falls on in 2012:
>>> var d = new Date(2012, 5, 20); >>> d.getDay();
3
Starting the count from 0 (Sunday), 3 means Wednesday. Is that so?
>>> d.toDateString();
"Wed Jun 20 2012"
OK, Wednesday is good but not necessarily the best day for a party. So how about a loop that tells how many times June 20 is a Friday from year 2012 to year 3012. Actually, let's see the distribution of all the days of the week. (After all, with all the medical progress, we're all going to be alive and kicking in 3012.)
First, let's initialize an array with seven elements, one for each day of the week. These will be used as counters. As we loop our way up to 3012, we'll increment the counters.
var stats = [0,0,0,0,0,0,0];
for (var i = 2012; i < 3012; i++) { stats[new Date(i, 5, 20).getDay()]++; }
And the result:
>>> stats;
[139, 145, 139, 146, 143, 143, 145]
143 Fridays and 145 Saturdays. Woo-hoo!
Regular expressions provide a powerful way to search and manipulate text. If you're familiar with SQL, you can think of regular expressions as being somewhat similar to SQL: you use SQL to find and update data inside a database, and you use regular expressions to find and update data inside a piece of text.
Different languages have different implementations (think "dialects") of the regular expressions syntax. JavaScript uses the Perl 5 syntax.
Instead of saying "regular expression", people often shorten it to "regex" or "regexp".
A regular expression consists of:
A pattern you use to match text
Zero or more modifiers (also called flags) that provide more instructions on how the pattern should be applied
The pattern can be as simple as literal text to be matched verbatim, but that is rare and in such cases you're better off using indexOf()
. Most of the times, the pattern is more complex and could be difficult to understand. Mastering regular expressions patterns is a large topic, which won't be discussed in details here; instead, you'll see what JavaScript provides in terms of syntax, objects and methods in order to support the use of regular expressions. You can also refer to Appendix D as a reference when writing patterns.
JavaScript provides the RegExp()
constructor which allows you to create regular expression objects.
>>> var re = new RegExp("j.*t");
There is also the more convenient regexp literal:
>>> var re = /j.*t/;
In the example above, j.*t
is the regular expression pattern. It means, "Match any string that starts with j
, ends with t
and has zero or more characters in between". The asterisk * means "zero or more of the preceding"; the dot (.) means "any character". The pattern needs to be placed in quotation marks when used in a RegExp()
constructor.
The regular expression objects have the following properties:
global
: If this property is false
, which is the default, the search stops when the first match is found. Set this to true
if you want all matches.
ignoreCase
: Case sensitive match or not, defaults to false.
multiline
: Search matches that may span over more than one line, defaults to false.
lastIndex
: The position at which to start the search, defaults to 0.
source
: Contains the regexp pattern.
None of these properties, except for lastIndex
, can be changed once the object has created.
The first three parameters represent the regex modifiers. If you create a regex object using the constructor, you can pass any combination of the following characters as a second parameter:
"g" for global
"i" for ignoreCase
"m" for multiline
These letters can be in any order. If a letter is passed, the corresponding modifier is set to true
. In the following example, all modifiers are set to true
:
>>> var re = new RegExp('j.*t', 'gmi'),
Let's verify:
>>> re.global;
true
Once set, the modifier cannot be changed:
>>> re.global = false; >>> re.global
true
To set any modifiers using the regex literal, you add them after the closing slash.
>>> var re = /j.*t/ig; >>> re.global
true
The regex objects provide two methods you can use to find matches: test()
and exec()
. They both accept a string parameter. test()
returns a boolean (true
when there's a match, false
otherwise), while exec()
returns an array of matched strings. Obviously exec()
is doing more work, so use test()
unless you really need to do something with the matches. People often use regular expressions for validation purposes, in this case test()
would probably be enough.
No match, because of the capital J
:
>>> /j.*t/.test("Javascript")
false
Case insensitive test gives a positive result:
>>> /j.*t/i.test("Javascript")
true
The same test using exec()
returns an array and you can access the first element as shown below:
>>> /j.*t/i.exec("Javascript")[0]
"Javascript"
Previously in this chapter we talked about the String
object and how you can use the methods indexOf()
and lastIndexOf()
to search within text. Using these methods you can only specify literal string patterns to search. A more powerful solution would be to use regular expressions to find text. String objects offer you this ability.
The string objects provide the following methods that accept regular expression objects as parameters:
match()
returns an array of matches
search()
returns the position of the first match
replace()
allows you to substitute matched text with another string
split()
also accepts a regexp when splitting a string into array elements
Let's see some examples of using the methods search()
and match()
. First, you create a string object.
>>> var s = new String('HelloJavaScriptWorld'),
Using match()
you get an array containing only the first match:
>>> s.match(/a/);
["a"]
Using the g
modifier, you perform a global search, so the result array contains two elements:
>>> s.match(/a/g);
["a", "a"]
Case insensitive match:
>>> s.match(/j.*a/i);
["Java"]
The search()
method gives you the position of the matching string:
>>> s.search(/j.*a/i);
5
replace()
allows you to replace the matched text with some other string. The following example removes all capital letters (it replaces them with blank strings):
>>> s.replace(/[A-Z]/g, ''),
"elloavacriptorld"
If you omit the g
modifier, you're only going to replace the first match:
>>> s.replace(/[A-Z]/, ''),
"elloJavaScriptWorld"
When a match is found, if you want to include the matched text in the replacement string, you can access it using $&
. Here's how to add an underscore before the match while keeping the match:
>>> s.replace(/[A-Z]/g, "_$&");
"_Hello_Java_Script_World"
When the regular expression contains groups (denoted by parentheses), the matches of each group are available as $1
is the first group, $2
the second and so on.
>>> s.replace(/([A-Z])/g, "_$1");
"_Hello_Java_Script_World"
Imagine you have a registration form on your web page that asks for email address, username, and password. The user enters their email, and then your JavaScript kicks in and suggests the username, taking it from the email address:
>>> var email = "[email protected]"; >>> var username = email.replace(/(.*)@.*/, "$1"); >>> username;
"stoyan"
When specifying the replacement, you can also pass a function that returns a string. This gives you the ability to implement any special logic you may need before specifying the replacements.
>>> function replaceCallback(match){return "_" +match.toLowerCase();} >>> s.replace(/[A-Z]/g, replaceCallback);
"_hello_java_script_world"
The callback function will receive a number of parameters (we ignored all but the first one in the example above):
The first parameter is the match
The last is the string being searched
The one before last is the position of the match
The rest of the parameters contain any strings matched by any groups in your regex pattern
Let's test this. First, let's create a variable to store the whole arguments array passed to the callback function:
>>> var glob;
Next, we'll define a regular expression that has three groups and matches email addresses in the format [email protected]
:
>>> var re = /(.*)@(.*).(.*)/;
Finally, we’ll define a callback function that stores the arguments in glob
and then returns the replacement:
var callback = function(){ glob = arguments; return arguments[1] + ' at ' + arguments[2] + ' dot ' + arguments[3]; }
We can then call this as follows:
>>> "[email protected]".replace(re, callback);
"stoyan at phpied dot com"
Here's what the callback function received as arguments:
>>> glob
["[email protected]", "stoyan", "phpied", "com", 0, "[email protected]"]
You already know about the method split()
, which creates an array from an input string and a delimiter string. Let's take a string of comma-separated values and split it:
>>> var csv = 'one, two,three ,four'; >>> csv.split(','),
["one", " two", "three ", "four"]
Because the input string has some inconsistent spaces before and after the commas, the array result has spaces too. With a regular expression, we can fix this, using s*
, which means "zero or more spaces":
>>> csv.split(/s*,s*/)
["one", "two", "three", "four"]
One last thing to note is that the four methods you just saw (split()
, match()
, search()
, and replace()
) can also take strings as opposed to regular expressions. In this case the string argument is used to produce a new regex as if it was passed to new RegExp()
.
Example of passing a string to replace:
>>> "test".replace('t', 'r')
"rest"
The above is the same as:
>>> "test".replace(new RegExp('t'), 'r')
"rest"
When you pass a string, you cannot set modifiers as you can with a normal constructor or regex literal.
Errors happen, and it's good to have the mechanisms in place so that your code can realize that there has been an error condition and recover from it in a graceful manner. JavaScript provides the statements try
, catch
, and finally
to help you deal with errors. If an error occurs, an error object is thrown. Error objects are created by using one of these built-in constructors: EvalError
, RangeError
, ReferenceError
, SyntaxError
, TypeError
, and URIError
. All of these constructors inherit from the Error
object.
Let's just cause an error and see what happens. An example of an error will be trying to call a function that doesn't exist. Type this into the Firebug console:
>>> iDontExist();
You'll get something like this:
In the bottom right corner, instead of Firebug's usual icon, you'll see:
If you open Firefox's error console (Tools | Error console), you'll see:
The display of errors can vary greatly between browsers and other host environments. In Internet Explorer you might see something like this in the lower left corner of the window:
If you double-click the message, you can get some more information:
Depending on your browser's configuration, you might not even notice that an error occurred. However, you cannot assume that all of your users have disabled the display of errors and it is your responsibility to ensure an error-free experience for them. The error above propagated to the user because the code didn't try to trap (catch) this error, it didn't expect the error and was unprepared for handling it. Fortunately, it's really easy to trap the error. All you need is the try
statement, followed by a catch
.
This code will not cause any of the error displays from the screenshots above
try { iDontExist(); } catch (e){ // do nothing }
Here you have:
The try
statement, followed by a block of code
catch
statement, followed by a variable name in parentheses and another block of code
There is also an optional finally
statement, not used in this example, which is executed regardless of whether there was an error or not.
In the example above, the code block that follows the catch
didn't do anything, but this is the place where you put the code that will recover from the error, or at least give some feedback to the user that you application is aware that there was a special condition.
The variable e
in the parentheses after the catch
statement contains an error object. Like any other object, it contains some useful properties and methods. Unfortunately, different browsers implement these methods and properties differently, but there are two properties that are consistently implemented—e.name
and e.message
.
Let's try this code now:
try { iDontExist(); } catch (e){ alert(e.name + ': ' + e.message); } finally { alert('Finally!'), }
This
will present an alert()
showing e.name
and e.message
and then another alert()
saying Finally!.
In Firefox, the first alert will say ReferenceError: iDontExist is not defined. In Internet Explorer it will be TypeError: Object expected. This tells us two things:
e.name
contains the name of the constructor that was used to create the error object.
Because the error objects are not consistent across host environments (browsers), it would be somewhat tricky to have your code act differently depending on the type of error (the value of e.name
).
You can also create error objects yourself using new Error()
or any of the other error constructors, and then let the JavaScript engine know that there's an erroneous condition, using the throw
statement.
For example, imagine a scenario where you call maybeExists()
function and after that make some calculations. You want to trap all errors in a consistent way, no matter whether the error is that maybeExists()
doesn't exist or that your calculations found a problem. Consider this code:
try { var total = maybeExists(); if (total === 0) { throw new Error('Division by zero!'), } else { alert(50 / total); } } catch (e){ alert(e.name + ': ' + e.message); } finally { alert('Finally!'), }
This code will alert()
different messages, depending on whether or not maybeExists()
is defined and the values it returns:
If maybeExists()
doesn't exist, you get ReferenceError: maybeExists() is not defined in Firefox and TypeError: Object expected in IE
If maybeExists()
returns 0, you'll get Error: Division by zero!
If maybeExists()
returns 2, you'll get an alert that says 25
In all cases, there will be a second alert that says Finally!
Inste
ad of throwing a generic error throw
new
Error('Division
by
zero!')
, you can be more specific if you choose to, for example, by throwing throw
new
RangeError('Division
by
zero!')
. Alternatively, you don't need any constructor; you can simply throw a normal object:
throw { name: "MyError", message: "OMG! Something terrible has happened" }
In Chapter 2, you saw that there are five primitive data types (number, string, boolean, null, and undefined) and we also said that everything that is not a primitive piece of data is an object. Now you also know that:
Objects are like arrays but you specify the keys.
Objects contain properties.
Some of the properties can be functions (functions are data, var f = function(){};
). Properties that are functions are also called methods.
Arrays are actually objects with predefined numeric properties and a length
property.
Array objects have a number of useful methods (such as sort()
or slice()
).
Functions are also objects and they have properties (such as length
and prototype
) and methods (such as call()
and apply()
).
Regarding the five primitive data types, apart from undefined
(which is essentially nothing) and null
(which is also an object), the other three have corresponding constructor functions: Number()
, String()
, and Boolean()
. Using these, you can create objects, called wrapper objects, which contain some useful methods for working with primitive data elements.
Number()
, String()
, and Boolean()
can be invoked:
With the new
operator—to create new objects
Without new
—to convert any value to a corresponding primitive data type
Other built-in constructor functions you're now familiar with include: Object()
, Array()
, Function()
, Date()
, RegExp()
, and Error()
. You are also familiar with Math
, which is not a constructor.
Now you can see how objects have a central role in the JavaScript programming, as pretty much everything is an object or can be wrapped into an object.
Finally, let's wrap up the literal notations you're now familiar with.
Name |
literal |
constructor |
Example |
---|---|---|---|
Object |
{} |
|
|
Array |
[] |
|
|
regular expression |
/pattern/modifiers |
|
|
Look at this code:
function F() { function C() { return this; } return C(); } var o = new F();
The value of this
refers to the global object or the object o
?
What's the result of executing this piece of code?
function C(){ this.a = 1; return false; } console.log(typeof new C());
What's the result of executing the following piece of code?
>>> c = [1, 2, [1, 2]]; >>> c.sort(); >>> c.join('--'), >>> console.log(c);
Imagine the String()
constructor didn't exist. Create a constructor function MyString()
that acts like String()
as closely as possible. You're not allowed to use any built-in string methods or properties, and remember that String()
doesn't exist. You can use this code to test your constructor:
>>> var s = new MyString('hello'), >>> s.length;
5
>>> s[0];
"h"
>>> s.toString();
"hello"
>>> s.valueOf();
"hello"
>>> s.charAt(1);
"e"
>>> s.charAt('2'),
"l"
>>> s.charAt('e'),
"h"
>>> s.concat(' world!'),
"hello world!"
>>> s.slice(1,3);
"el"
>>> s.slice(0,-1);
"hell"
>>> s.split('e'),
["h", "llo"]
>>> s.split('l'),
["he", "", "o"]
Update your MyString()
constructor to include a reverse()
method.
Imagine Array()
doesn't exist and the array literal notation doesn't exist either. Create a constructor called MyArray()
that behaves as close to Array()
as possible. Test with this code:
>>> var a = new MyArray(1,2,3,"test"); >>> a.toString();
"1,2,3,test"
>>> a.length;
4
>>> a[a.length - 1]
"test"
>>> a.push('boo'),
5
>>> a.toString();
"1,2,3,test,boo"
>>> a.pop();
[1, 2, 3, "test"]
>>> a.toString();
"1,2,3,test"
>>> a.join(',')
"1,2,3,test"
>>> a.join(' isn't ')
"1 isn't 2 isn't 3 isn't test"
If you found this exercise amusing, don't stop with the join()
; go on with as many methods as possible.
Imagine Math
didn't exist. Create a MyMath
object that also provides some additional methods:
MyMath
.rand(min, max, inclusive)
—generates a random number between min
and max
, inclusive if inclusive
is true
(default)
MyMath
.min(array)
—returns the smallest number in a given array
MyMath
.max(array)
—returns the largest number in a given array
18.225.255.134