IIFEs let you take advantage of function scope to create namespaces to organize large pieces of your code. There is another use of functions that makes them act like factories for objects that all have similar properties and methods. In other languages, you might use a class for this kind of organization. Strictly speaking, JavaScript does not have classes, but it does give you a way to create custom types.
You have already started to create the DataStore type. Now you will customize it in two steps. In the first step, you will give it a property that will be used internally for storing data. In the second step, you will give it a set of methods for interacting with that data. You do not need to give other objects direct access to that data, so this type will provide an external interface through a set of methods.
Object factory functions are called constructors in JavaScript.
Add the following code to the body of the DataStore function in datastore.js.
(function (window) { 'use strict'; var App = window.App || {}; function DataStore() { console.log('running the DataStore function'); this.data = {}; } App.DataStore = DataStore; window.App = App; })(window);
The job of a constructor is to create and customize a new object.
Inside the body of the constructor, you can refer to that new
object with the keyword this
. You used the dot
operator to create a property named data
on
your new object and assigned an empty object to
data
.
You may have noticed that you capitalized the first letter of DataStore. This is a convention in JavaScript when naming constructors. It is not necessary, but it is a good practice as a way to tell other developers that the function should be used as a constructor.
To differentiate a constructor from a regular function, you
use the keyword new
when you call it. This
tells JavaScript to create a new object, set up the reference
from this
to that new object, and to implicitly
return
that object. That means it will return
the object without an explicit return
statement in the
constructor.
Save and return to the console. To learn how to use a constructor, you are going to create two DataStore objects (or instances) and add values to them. Begin by creating the instances.
var dsOne = new App.DataStore(); var dsTwo = new App.DataStore();
You created these DataStore
instances by calling the DataStore
constructor. At this point, each has an empty
data
property. Add some values to them:
dsOne.data['email'] = '[email protected]'; dsOne.data['order'] = 'black coffee'; dsTwo.data['email'] = '[email protected]'; dsTwo.data['order'] = 'chai tea';
Then inspect the values:
dsOne.data; dsTwo.data;
The results tell you that each instance holds different information (Figure 8.8).
Using a DataStore instance, you can manually store and retrieve data. But, in its current form, DataStore is just a roundabout way of creating an object literal, and any module that will use a DataStore instance has to be coded to use the data property directly.
This is not good software design. It would be better if DataStore provided a public interface for adding, removing, and retrieving data – all while keeping the details of how it works a secret.
The second part of creating your custom DataStore type is to provide these methods for interacting with the data. The goal is for these methods to serve as the interface that other modules use when they interact with a DataStore instance. To accomplish this, you will make use of a very cool feature of JavaScript functions, the prototype property.
Functions in JavaScript are also objects. This means that they
can have properties. In JavaScript, all instances created
by a constructor have access to a shared storehouse
of properties and methods: the
prototype
property of the constructor.
To create these instances, you used the new
keyword
when you called the constructor. The new
keyword
not only creates your instance and returns it but also creates a
special link from the instance to the constructor’s prototype
property.
This link exists for any instance created when the constructor
is created with the new
keyword.
When you add a property to the prototype
and
assign it a function, every instance you create with the
constructor will have access to that function.
You can use the keyword this
inside of the
function body, and it will refer to the instance.
To see this in action, create the add function in datastore.js as a property
of the prototype. You can also delete the call to console.log
.
(function (window) { 'use strict'; var App = window.App || {}; function DataStore() {console.log('running the DataStore function');this.data = {}; } DataStore.prototype.add = function (key, val) { this.data[key] = val; }; App.DataStore = DataStore; window.App = App; })(window);
You gave DataStore.prototype
the property
add and you assigned a function to it. That
function takes two arguments, key
and
val
. Inside the function body, you used
those arguments to make changes to the instance’s
data
property.
In terms of how DataStore works with coffee
orders, it will store the order information (the val
),
using the customer’s email address (the key
).
You are not setting up a true database, but DataStore works well enough
for CoffeeRun. It is able to save some information, val
,
under the unique identifier specified by key
.
Because you are using a JavaScript object for storage, each
key
is guaranteed to be a unique entry in the
database.
(In a JavaScript object, a property name is always unique, like function names within a namespace.
If you tried to store different values using the
same key, you would just overwrite any previous values for that key.)
This aspect of JavaScript objects fulfills the one major requirement of any database: keeping the individual pieces of data separate.
Save your code and switch back to the browser. Create an instance of DataStore in the console and use its add method to store some information.
var ds = new App.DataStore(); ds.add('email', '[email protected]'); ds.add('order', 'triple espresso'); ds.data;
Inspect the data
property to confirm that it works
(Figure 8.9).
The next thing to do is to create methods for accessing the data. In datastore.js, add a method to look up a value based on a given key and one to look up all keys and values.
... DataStore.prototype.add = function (key, val) { this.data[key] = val; }; DataStore.prototype.get = function (key) { return this.data[key]; }; DataStore.prototype.getAll = function () { return this.data; }; App.DataStore = DataStore; window.App = App; })(window);
You created a get method that accepts a
key
, looks up the value for it in the
instance’s data
property, and
return
s it. You also created a getAll method.
It is almost the same, but instead of looking up the value for a
single key, it returns a reference to the
data
property.
You can now add and retrieve information from a DataStore instance. To complete the cycle, you need to add a method for removing information. Add that to datastore.js now.
... DataStore.prototype.getAll = function () { return this.data; }; DataStore.prototype.remove = function (key) { delete this.data[key]; }; App.DataStore = DataStore; window.App = App; })(window);
The delete
operator removes a key/value pair
from an object when your new remove method is called.
With that, you have completed the DataStore module, which provides the most important part of the CoffeeRun application. It can store data, provide stored data in response to queries, and delete unnecessary data on command.
To see all of your methods in action, save your code and go to the console after
browser-sync
has reloaded your browser. Enter the following
code, which exercises all of the methods of DataStore:
var ds = new App.DataStore(); ds.add('[email protected]', 'tea'); ds.add('[email protected]', 'eshpressho'); ds.getAll(); ds.remove('[email protected]'); ds.getAll(); ds.get('[email protected]'); ds.get('[email protected]');
As shown in Figure 8.10, DataStore’s instance methods should now work as expected. These methods are exactly the way that other modules will interact with your application’s database.
Your next module will use the same structure: an IIFE with a parameter for the namespace to modify. But it will provide completely different functionality from DataStore.
3.144.244.250