visit
JavaScript is a light language, interpreted, object oriented with functions as first class citizens, that is mostly known as the language for web pages
If you have ever programmed on JavaScript, for sure you are familiar with the term callback. For those that are not, the callback is a function passed as a parameter that will be executed when an event is fired. They are usually used to listen for events like a mouse click or button press.
while (queue.waitForMessage()) {
queue.processNextMessage();
}
The queue.processNextMessage()
waits for new messages in a synchronous manner. Each one of the messages being processed has its own stack and is processed until the stack is empty. Once all the processes are finished, a new message is read from the queue, this goes on while there are messages on the queue.
A good example of an anti-pattern in JavaScript is to change the Object
prototype. Practically all objects in JavaScript inherit from Object
(remember, JavaScript uses a prototype-based inheritance) so imagine that you have changed this prototype. The changes to Object
would be seen in every object that inherits from it — which would be almost all objects from JavaScript. A disaster waiting to happen!
Creational Design Patterns
These patterns deal with object creation in a more optimized way than the basic object creation. When the regular object creation manner would cause more complexity or bring problems to the code, creational patterns can solve the problem.
Structural Design Patterns
These patterns work with relations between objects. They guarantee that if a system’s part change, nothing else has to change with it.
Behavioral Design Patterns
This kind of pattern acknowledges, implement and improve communication between objects of the system. They help to guarantee that unrelated parts of the application have synchronized information.
Concurrency Design Patterns
When you are dealing with multi-threading programming these are the patterns that you will want to use.
Architectural Design Patterns
Design patterns that are used on the system’s architecture, like MVC or MVVM. In the next section, we will take a look at some of these patterns, with examples for better understanding.
// All of these forms can be used to create
// an object in JavaScript with the same result
let instance = {};
// or
let instance = Object.create(Object.prototype);
// or
let instance = new Object();
// The . notation
instance.key = 'value';
// brackets notation
instance['key'] = 'value';
// defining a property with Object.defineProperty
Object.defineProperty(instance, 'key', {
value: 'value',
writable: true,
enumerable: true,
configurable: true
});
// defining multiple properties with Object.defineProperties
Object.defineProperties(instance, {
'firstKey': {
value: 'first key value',
writable: true
},
'secondKey': {
value: 'second key value',
writable: false
}
});
The most usual way for object creation is using {}
and to add properties the dot notation or []
. That is why I recommend that you use these methods, it will make a lot easier for other programmers to understand your code and even your future self.
We mentioned earlier that JavaScript does not support native classes, but it supports constructors through the keyword new
prefixed to a function call. This way we can use a function as a constructor and initialize properties the same we would on a more traditional language.
// We define a constructor for objects of type Person
function Person (name, age, isDeveloper) {
this.name = name;
this.age = age;
this.isDeveloper = isDeveloper || false;
this.writesCode = () => {
console.log(this.isDeveloper ? 'this person writes code' : 'this person does not writes code')
}
}
// Create a person with: name = Ana, age = 32,
// isDeveloper = true and a method writesCode
let person1 = new Person('Ana', 32, true);
// Create a person with: name = Bob, age = 36,
// isDeveloper = false and a method writesCode
let person2 = new Person('Bob', 36);
// prints: this person writes code
person1.writesCode();
// prints: this person does not writes code
person2.writesCode();
We can still improve this code. The problem is that the method writesCode is redefined for every instance of Person. Because JavaScript is prototype-based we can avoid that by adding the method to the prototype.
// We define a constructor for Person
function Person(name, age, isDeveloper) {
this.name = name;
this.age = age;
this.isDeveloper = isDeveloper || false;
}
// Then we extend the prototype, this way we make JavaScript
// point to this function when we call it on a Person
Person.protype.writesCode = function() {
console.log(this.isDeveloper ? 'this person writes code' : 'this person does not writes code')
}
}
// Create a person with: name = Ana, age = 32,
// isDeveloper = true and a method writesCode
let person1 = new Person('Ana', 32, true);
// Create a person with: name = Bob, age = 36,
// isDeveloper = false and a method writesCode
let person2 = new Person('Bob', 36);
// prints this person writes code
person1.writesCode();
// prints this person does not writes code
person2.writesCode();
Now, both instances of Person can access the same shared instance of writesCode()
.
It is as if on the first example we had a different method for each object of type Person
, each Person
points to their own function definition. In the second example, all Person
s will point to the same function, the same piece of code.
Before we get into the details about module patterns, let’s take a better look at closures. A closure is a function with access to its parent’s scope, even after the parent has finished. They will help us emulate the behavior of access limiters. Let’s see an example:
// We use a immediately invoked function to create
// a private variable counter
var counterIncrementer = (() => {
let counter = 0;
return function() {
return ++counter;
};
})(); // these () in the end make this a immediately invoked function
// prints: 1
console.log(counterIncrementer());
// prints: 2
console.log(counterIncrementer());
// prints: 3
console.log(counterIncrementer());
As you can see, we tied the variable counter to a function that was called and closed, but we still can access it through the child function that increments the counter. Note that the inner function is returned from counterIncrementer()
. We can not access the counter from outside the function, essentially, we created a private variable in vanilla JavaScript through scope manipulation.
// Using a closure we will expose an object
// as part of a public API that manages its
// private parts
let fruitsCollection = (() => {
// private
let objects = [];
// public
return {
addObject: (object) => {
objects.push(object);
},
removeObject: (object) => {
let index = objects.indexOf(object);
if (index >= 0) {
objects.splice(index, 1);
}
},
getObjects: () => JSON.parse(JSON.stringify(objects))
};
})(); // notice the execution
fruitsCollection.addObject("apple");
fruitsCollection.addObject("orange");
fruitsCollection.addObject("banana");
// prints: ["apple", "orange", "banana"]
console.log(fruitsCollection.getObjects());
fruitsCollection.removeObject("apple");
// prints: ["orange", "banana"]
console.log(fruitsCollection.getObjects());
// We write the whole logic as private members
// and expose an anonymous object that maps the
// methods we want as their public counterparts
let fruitsCollection = (() => {
// private
let objects = [];
const addObject = (object) => {
object.push(object);
}
const removeObject = (object) => {
let index = objects.indexOf(object);
if (index >= 0) {
objects.splice(index, 1);
}
}
const getObjects = () => JSON.parse(JSON.stringify(objects))
// public
return {
addName: addObject,
removeName: removeObject,
getNames: getObjects
};
})();
fruitsCollection.addName("Bob");
fruitsCollection.addName("Alice");
fruitsCollection.addName("Frank");
// prints: ["Bob", "Alice", "Frank"]
console.log(namesCollection.getNames());
namesCollection.removeName("Alice");
// prints: ["Bob", "Frank"]
console.log(namesCollection.getNames());
let configurationSingleton = (() => {
// private value of the singleton initialized only once
let config;
const initializeConfiguration = (values) => {
this.randomNumber = Math.random();
values = values || {};
this.number = values.number || 5;
this.size = values.size || 10;
}
// We export the centralized method to return
// the singleton's value
return {
getConfig: (values) => {
// initialize the singleton only once
if (config === undefined) {
config = new initializeConfiguration(values);
}
// and always return the same value
return config;
}
};
})();
const configObject = configurationSingleton.getConfig({ "size": 8 });
// prints number: 5, size: 8, randomNumber: someRandomDecimalValue
console.log(configObject);
const configObject1 = configurationSingleton.getConfig({ "number": 8 });
// prints number: 5, size: 8, randomNumber: same randomDecimalValue // como no primeiro config
console.log(configObject1);
One variant to this pattern is the publisher/subscriber pattern, which is the one we will see in this text.
let publisherSubscriber = {};
// We pass an object to the container to manage subscriptions
((container) => {
// the id represents a subscription to the topic
let id = 0;
// the objects will subscribe to a topic by
// sending a callback to be executed when
// the event is fired
container.subscribe = (topic, f) => {
if (!(topic in container)) {
container[topic] = [];
}
container[topic].push({
'id': ++id,
'callback': f
});
return id;
}
// Every subscription has it's own id, we will
// use it to remove the subscription
container.unsubscribe = (topic, id) => {
let subscribers = [];
for (car subscriber of container[topic]) {
if (subscriber.id !== id) {
subscribers.push(subscriber);
}
}
container[topic] = subscribers;
}
container.publish = (topic, data) => {
for (var subscriber of container[topic]) {
// when we execute a callback it is always
// good to read the documentation to know which
// arguments are passed by the object firing
// the event
subscriber.callback(data);
}
}
})(publisherSubscriber);
let subscriptionID1 = publisherSubscriber.subscribe("mouseClicked", (data) => {
console.log("mouseClicked, data: " + JSON.stringify(data));
});
let subscriptionID2 = publisherSubscriber.subscribe("mouseHovered", function(data) {
console.log("mouseHovered, data: " + JSON.stringify(data));
});
let subscriptionID1 = publisherSubscriber.subscribe("mouseClicked", function(data) {
console.log("second mouseClicked, data: " + JSON.stringify(data));
});
// When we publish an event, all callbacks should
// be called and you will see three logs
publisherSubscriber.publish("mouseClicked", {"data": "data1"});
publisherSubscriber.publish("mouseHovered", {"data": "data2"});
// We unsubscribe an event
publisherSubscriber.unsubscribe("mouseClicked", subscriptionID3);
// now we have 2 logs
publisherSubscriber.publish("mouseClicked", {"data": "data1"});
publisherSubscriber.publish("mouseHovered", {"data": "data2"});
let personPrototype = {
sayHi: () => {
console.log("Hi, I am " + this.name + ", and I have " + this.age);
},
sayBye: () => {
console.log("Bye!");
}
};
const Person = (name, age) => {
let name = name || "John Doe";
let age = age || 26;
const constructorFunction = (name, age) => {
this.name = name;
this.age = age;
};
constructorFunction.prototype = personPrototype;
let instance = new constructorFunction(name, age);
return instance;
}
const person1 = Person();
const person2 = Person("Jane Doe", 38);
// prints Hi, I am John Doe, and I have 26
person1.sayHi();
// prints Hi, I am Jane Doe, and I have 38
person2.sayHi();
That is where an abstraction layer separates the objects that call the API from the objects that determine when to call it comes in. This way we don’t need to do a big change on the application, because we just have to change the call to the API in a single place.
// The object that knows how to execute the command
const invoker = {
add: (x, y) => {
return x + y;
},
subtract: (x, y) => {
return x - y;
}
}
// the object to be used as abstraction layer when
// we execute commands; it represents a interface
// to the caller object
let manager = {
execute: (name, args) => {
if (name in invoker) {
return invoker[name].apply(invoker, [].slice.call(arguments, 1));
}
return false;
}
}
// prints 8
console.log(manager.execute("add", 3, 5));
// prints 2
console.log(manager.execute("subtract", 5, 3));
This pattern is used, for example, on the DOM selectors of libraries as JQuery, Dojo, and D3. These frameworks have powerful selectors that allow us to write complex queries in a very simple way. Something like jQuery(".parent .child div.span")
seems simple, but it hides a complex query logic underneath.