Now that you've mastered JavaScript's primitive data types, arrays, and functions, it's time to stay true to the promise of the book title and talk about objects.
JavaScript has an eccentric take on the classical Object-oriented programming. Object-oriented programming is one of the most popular programming paradigms and has been a mainstay in most of programming languages like Java and C++. There are well defined ideas proposed by classical OOP that most of these languages adopt. JavaScript, however, has a different take on it. We will look JavaScript's way of supporting OOP.
In this chapter, you will learn the following topics:
As you already know from Chapter 2, Primitive Data Types, Arrays, Loops, and Conditions, an array is just a list of values. Each value has an index (a numeric key) that starts from zero and increments by one for each value. Consider the following example:
> 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 shown as follows:
Key |
Value |
0 |
red |
1 |
blue |
2 |
yellow |
3 |
purple |
An object is similar to an array, but the difference is that you define the keys yourself. You're not limited to using only numeric indexes, and you 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:
hero
[
and ]
, which you use to define an array, you use {
and }
for objectskey:value
The keys (names of the properties) can optionally be placed in quotation marks. For example, these keys are all the same:
var hero = {occupation: 1}; var hero = {"occupation": 1}; var hero = {'occupation': 1};
It's recommended that you don't quote the names of the properties (it's less typing), but there are cases when you must use quotes. Some of the cases are stated here:
_
and $
characters)In other words, if the name you have chosen for a property is not a valid name for a variable in JavaScript, then you need to wrap it in quotes.
Have a look at this bizarre-looking object:
var o = { $omething: 1, 'yes or no': 'yes', '!@#$%^&*': true };
This is a valid object. The quotes are required for the second and the third properties; otherwise, you'll get an error.
Later in this chapter, you'll see other ways to define objects and arrays, in addition to []
and {}
. However, first, let's introduce this bit of terminology - defining an array with []
is called array literal notation, and defining an object using 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's just the terminology that people are used to, probably from other programming languages.
A property of an object can point to a function, because functions are just data. Properties that point to functions are also called methods. In the following example, talk
is a method:
var dog = { name: 'Benji', talk: function () { alert('Woof, woof!'), } };
As you have seen in the previous chapter, it's also possible to store functions as array elements and invoke them, but you'll not see much code like this in practice:
> var a = []; > a[0] = function (what) { alert(what); }; > a[0]('Boo!'),
You can also see people using the word members to refer to the properties of an object, most often when it doesn't matter if the property is a function or not.
In some programming languages, there is a distinction between:
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 the property of an object:
hero['occupation']
hero.occupation
The dot notation is easier to read and write, but it cannot always be used. The same rules apply 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 the hero
object again:
var hero = { breed: 'Turtle', occupation: 'Ninja' };
Following is an example for accessing a property with the dot notation:
> hero.breed; "Turtle"
Let's see an example for accessing a property with the bracket notation:
> hero['occupation']; "Ninja"
Consider the following example for 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 can use the following lines of code:
> book.author.firstname; "Joseph"
Let see an example using the square brackets notation:
> book['author']['lastname']; "Heller"
It works even if you mix both:
> book.author['lastname']; "Heller" > book['author'].lastname; "Heller"
Another case where you need square brackets is when the name of the property you need to access is not known beforehand. During runtime, it's dynamically stored in a variable:
> var key = 'firstname'; > book.author[key]; "Joseph"
You know a method is just a property that happens to be a function, so you access methods in the same way in which you would access properties-using the dot notation or using square brackets. Calling (invoking) a method is the same as calling any other function - you just add parentheses after the method name, which effectively says 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 would proceed as you would with normal functions:
> hero.say('a', 'b', 'c'),
As you can use the array-like square brackets to access a property, it means you can also use brackets to access and invoke methods:
> hero['say']();
This is not a common practice, unless the method name is not known at the time of writing code, but it is instead defined at runtime:
var method = 'say'; hero[method]();
JavaScript allows you to alter the 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 object without properties is shown as follows:
> var hero = {};
A "blank" object
In this section, you started with a "blank" object, var hero = {}
. Blank is in quotes because this object is not really empty and useless. Although at this stage it has no properties of its own, it has already inherited some.
You'll learn more about own versus inherited properties later. So, an object in ES3 is never really blank or empty. In ES5 though, there is a way to create a completely blank object that doesn't inherit anything, but let's not get ahead too much.
> typeof hero.breed; "undefined"
> hero.breed = 'turtle'; > hero.name = 'Leonardo'; > hero.sayName = function () { return hero.name; };
> hero.sayName(); "Leonardo"
> delete hero.name; true
name
property:> hero.sayName(); "undefined"
Malleable objects
You can always change any object at any time, such as adding and removing properties and changing their values. However, there are exceptions to this rule. A few properties of some built-in objects are not changeable (for example, Math.PI
, as you'll see later). Also, ES5 allows you to prevent changes to objects. You'll learn more about it in Appendix C, Built-in Objects.
In the previous example, the sayName()
method 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 the method belongs to. This method is using the special value this
:
> var hero = { name: 'Rafaelo', sayName: function () { return this.name; } }; > hero.sayName(); "Rafaelo"
So, when you say this
, you're actually saying-this object or the current object.
There is another way to create objects-using constructor functions. Let's look at an example:
function Hero() { this.occupation = 'Ninja'; }
In order to create an object using this function, you can use the new
operator as follows:
> var hero = new Hero(); > hero.occupation; "Ninja"
A 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"
If you call a function that is designed to be a constructor but you omit the new
operator, it is not an error. However, it doesn't give you the expected result:
> var h = Hero('Leonardo'), > typeof h; "undefined"
What happened here? There is no new
operator, so a new object was not created. The function was called like any other function, so the variable h
contains the value that the function returns. The function does not return anything (there's no return
function), so it actually returns undefined
, which gets assigned to the variable h
.
In this case, what does this
refer to? It refers to the global object.
You have already learned a bit about global variables (and how you should avoid them). You also know that JavaScript programs run inside a host environment (the browser, for example). Now that you know about objects, it's time for the whole truth, the host environment provides a global object, and all global variables are accessible as properties of the global object.
If your host environment is the web browser, the global object is called window. Another way to access the global object (and this is also true in most other environments) is to use this
keyword outside a constructor function, for example in the global program code outside any function.
As an illustration, you can declare a global variable outside any function as follows:
> var a = 1;
Then, you can access this global variable in various ways:
a
window['a']
or window.a
this
:> var a = 1; > window.a; 1 > this.a; 1
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 the properties set to 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; TypeError: Cannot read property 'name' of undefined
As you had this
keyword inside the function 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 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 built-in global functions you have seen in Chapter 3, Functions, can also be invoked as methods of the window
object. So, the following two calls have the same result:
> parseInt('101 dalmatians'), 101 > window.parseInt('101 dalmatians') 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; function Hero(name) { this.name = name; }
As 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 (there is more about this later in this chapter):
> var o = {}; > o.constructor; function Object() { [native code] } > typeof o.constructor; "function"
With the instanceof
operator, you can test whether an object was created with a specific constructor
function:
> function Hero() {} > var h = new Hero(); > var o = {}; > h instanceof Hero; true > h instanceof Object; true > o instanceof Object; true
Note that you don't put parentheses after the function name (you don't use h instanceof Hero()
). This is because you're not invoking this function, but just referring to it by name, as with any other variable.
In addition to using constructor
functions and the new
operator to create objects, you can also use a normal function to create objects without the new
operator. You can have a function that does a bit of 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 }; }
Consider the following example using the factory()
function:
> var o = factory('one'), > o.name; "one" > o.constructor; function Object() { [native code] }
In fact, you can also use constructor
functions and return
objects different from this
keyword. 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
However, 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 this
object, 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
.
If you think about how objects are created inside constructor functions, you can imagine that a variable called this
is defined at the top of the function and then returned at the end. Consider the following code:
function C() { // var this = {}; // pseudo code, you can't do this this.a = 1; // return this; }
When you assign an object to a different variable or pass it to a function, you only pass a reference to that object. Consequently, if you make a change to the reference, you're 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 mycopy = original; > mycopy.howmany; 1 > mycopy.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. If you compare two distinct objects that happen to have the exact same methods and properties, the result would be false
.
Let's create two objects that look the same:
> var fido = {breed: 'dog'}; > var benji = {breed: 'dog'};
Comparing them returns false
:
> benji === fido; false > benji == fido; false
You can create a new variable, mydog
, and assign one of the objects to it. This way, the variable mydog
actually points to the same object:
> var mydog = benji;
In this case, benji
is mydog
because they are the same object (changing the mydog
variable's properties will change the benji
variable's properties). The comparison returns true
:
> mydog === benji; true
As fido
is a different object, it does not compare to mydog
:
> mydog === fido; false
Before diving into the built-in objects in JavaScript, let's quickly say a few words about working with objects in the WebKit 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 an arrow pointing to the word object.
The object is clickable and expands to show you a list of all of the properties of the object. If a property is also an object, there is an arrow next to it too, so you can expand this as well. This is handy as it gives you an insight into exactly what this object contains. Consider the following example:
The console also offers you an object called console
and a few methods, such as console.log()
and console.error()
, which you can use to display any value you want in the console.
The console.log()
method is convenient when you want to quickly test something, as well as when you want to dump some intermediate debugging information in your real scripts. Here's how you can experiment with loops, for example:
> for (var i = 0; i < 5; i++) { console.log(i); } 0 1 2 3 4
ES6 introduces a much succinct syntax while using object literals. ES6 offers several shorthands for property initialization and function definitions. ES6 shorthands closely resemble a familiar JSON syntax. Consider the following code fragment:
let a = 1 let b = 2 let val = {a: a, b: b} console.log(val) //{"a":1,"b":2}
This is a typical way to assign property values. If the name of the variable and the property key is the same, ES6 allows you to use shorthand syntax. The preceding code can be written as follows:
let a = 1 let b = 2 let val = {a, b} console.log(val) //{"a":1,"b":2}
Similar syntax is available for method definitions as well. As we have discussed, methods are simply properties of an object whose values are functions. Consider the following example:
var obj = { prop: 1, modifier: function() { console.log(this.prop); } }
There is a compact way to define methods in ES6. You simply drop the function
keyword and :
. The equivalent code in ES6 would look like the following:
var obj = { prop: 1, modifier () { console.log(this.prop); } }
ES6 allows you to compute the key of a property. Until ES6, you could only use fixed property names. Here is an example:
var obj = { prop: 1, modifier: function () { console.log(this.prop); } } obj.prop = 2; obj.modifier(); //2
As you can see, we are limited to using fixed key names: prop
and modifier
in this case. However, ES6 allows you to use computed property keys. It is possible to create property keys dynamically using values returned by a function as well:
let vehicle = "car" function vehicleType(){ return "truck" } let car = { [vehicle+"_model"]: "Ford" } let truck= { [vehicleType() + "_model"]: "Mercedez" } console.log(car) //{"car_model":"Ford"} console.log(truck) //{"truck_model":"Mercedez"}
We are using the value of variable vehicle
to concatenate with a fixed string to derive the property key while creating the car
object. In the second snippet, we are creating a property by concatenating a fixed string with the value returned by a function. This way of computing property keys provides great flexibility while creating objects, and a lot of boilerplate and repetitive code can be eliminated.
This syntax is applicable to method definition as well:
let object_type = "Vehicle" let obj = { ["get"+object_type]() { return "Ford" } }
3.147.78.174