Understanding production patterns
Front End Engineer
HubSpot
alecortega
slides.com/alecortega/javscript-architecture/live
Being a successful programmer is about understanding that the uncomfortable feeling means that there's a potential to learn more and grow as a developer.
The best thing you can do is understand your basics really well.
No matter what framework, library, or application you're working with the best thing you can ever learn is...
Focus on getting really really good at your core JavaScript skills.
If you understand how JavaScript works at a fundamental level and common ways to organize your code. You'll know 70% of any framework right off the bat.
Experience and context is not knowing how to code every single task that's given to you right off the bat.
Experience and context is just knowing what the good questions to Google for are and recognizing how to solve a problem because you've solved it before.
How to organize your code on a production level
How to use the "this" keyword
When and why frameworks are needed once your application gets to a certain scale.
What modules are and what a module bundler is
var greeting = 'hello';var greeting = 'hello';
function greet () {
console.log(greeting);
};
greet();=> 'hello'function greet () {
};function greet () {
var greeting = 'hello';
console.log(greeting)
};
greet()function greet () {
var greeting = 'hello';
console.log(greeting)
};
greet()
console.log(greeting);=> 'hello'=> 'hello'
=> undefinedfunction greet () {
var greeting = 'hello';
};
var human = 'hello';
var dog = 'woof';
var cow = 'moo';
function greet () {
console.log(human);
};
function bark () {
console.log(dog);
};
function moo () {
console.log(cow);
};
Variables that aren't contained within any function can be accessed anywhere in your program and are considered within the...
var human = 'hello';
function greet () {
console.log(human);
};
function getFavoriteFood () {
console.log('cheeseburger');
}
getFavoriteFood();
// 'cheeseburger'function greet () {
console.log('hello');
}
function getFavoriteFood () {
console.log('cheeseburger');
}getFavoriteFood
greet
var person = {
firstName: 'Alec',
lastName: 'Ortega',
};var person = {
firstName: 'Alec',
lastName: 'Ortega',
};
console.log(person.firstName);
// 'Alec'function greet () {
console.log('hello');
}
function getFavoriteFood () {
console.log('cheeseburger');
}var person = {
greet: function() {
console.log('hello');
},
getFavoriteFood: function() {
console.log('cheeseburger');
}
};var person = {
greet: function() {
console.log('hello');
},
getFavoriteFood: function() {
console.log('cheeseburger');
}
};
person.greet();
=> 'hello'var person = {
};var currentNumber = 0;
var $counterEl = $('.js-counter');
var incrementCounter = function() {
currentNumber++;
$counterEl.text(currentNumber)
}
$('.js-image').on('click', incrementCounter);Create two different counters, each counter should only increment when the corresponding image is clicked.
var currentNumber = 0;
var $counterEl = $('.js-counter');
var incrementCounter = function() {
currentNumber++;
$counterEl.text(currentNumber)
}
$('.js-image').on('click', incrementCounter);var currentNumber = 0;
var $counterEl = $('.js-counter');
var counter = {
};
var incrementCounter = function() {
currentNumber++;
$counterEl.text(currentNumber)
}
$('.js-image').on('click', incrementCounter);var currentNumber = 0;
var $counterEl = $('.counter');
var counter = {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
};var currentNumber = 0;
var $counterEl = $('.counter');
var counter = {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
};
counter.init();var currentNumber = 0;
var $counterEl = $('.counter');
var counter = {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
};var currentNumber = 0;
var $counterEl = $('.counter');
var counter = function() {
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
}
};var counter = function() {
var currentNumber = 0;
var $counterEl = $('.counter');
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
}
};1) It won't run
2) It's only binding the event to the one class ".js-image"
Call the init method. Open up your browser's inspector and go to the "console" tab. If you see an error you've called the function successfully.
In English, it's telling you that it can't find a function anywhere that's called "incrementCounter"
var counter = function() {
var currentNumber = 0;
var $counterEl = $('.js-counter');
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
}
};var counter = function() {
var currentNumber = 0;
var $counterEl = $('.counter');
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', incrementCounter);
}
}
};var counter = function() {
var currentNumber = 0;
var $counterEl = $('.counter');
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image').on('click', this.incrementCounter);
}
}
};You might be inclined to think that "this" is a way to access other functions or values on the same object, and technically that's correct but...
The best way to think of "this" is that it's a reference to whatever is to the "left" of the "dot"
var person = {
greet: function () {
console.log('hello');
}
getFavoriteFood: function () {
console.log('cheeseburger');
}
};
var person = {
greet: function () {
console.log('hello');
}
getFavoriteFoodAndGreet: function () {
console.log('cheeseburger');
}
};
var person = {
greet: function () {
console.log('hello');
}
getFavoriteFoodAndGreet: function () {
console.log('cheeseburger');
console.log('hello');
}
};
var person = {
greet: function () {
console.log('hello');
}
getFavoriteFoodAndGreet: function () {
console.log('cheeseburger');
this.greet();
}
};
var person = {
greet: function () {
console.log('hello');
}
getFavoriteFoodAndGreet: function () {
console.log('cheeseburger');
this.greet();
}
};
person.getFavoriteFoodAndGreet()var person = {
greet: function () {
console.log('hello');
}
getFavoriteFoodAndGreet: function () {
console.log('cheeseburger');
person.greet();
}
};
If you want to learn more about the "this" keyword and how it refers to objects check this out:
Add the "this" keyword into your code. When you click on either of the photos, both counters should start incrementing.
The selectors
Change your HTML so that each counter and image has a different class, then modify your JS so that your function takes in these classes as arguments, you'll be passing in strings.
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
var counter = function('.js-image-one', '.js-counter-one') {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
var counter = function('.js-image-one', '.js-counter-one') {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
var counter = function('.js-image-one', '.js-counter-one') {
var currentNumber = 0;
var $counterEl = $('.js-counter-one');
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
var counter = function('.js-image-one', '.js-counter-one') {
var currentNumber = 0;
var $counterEl = $('.js-counter-one');
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$('.js-image-one').on('click', this.incrementCounter);
}
}
};
var counterOne = counter.init()
var currentNumber =
0
var counterTwo = counter.init()
var currentNumber =
0
1
new function() / scope
new function() / scope
1
2
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
This function wraps around our variables and creates a new scope.
(very important to know)
Closures are functions that refer the variables that were created when the function was called. When you call a a function with a variable in it, the variable will be stored in a new place in memory for every new call. In other words, these functions 'remember' the environment in which they were created.
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
Any piece of code that is encapsulated in a function that has some interface on how to access it's private data.
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};
counter.init();var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};We're returning two different functions from this module. Does a consumer of this module need both of these functions to use it?
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};We can think of functions that we export from modules like "public" methods on a class in other languages.
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};So how would we make "incrementCounter" private to this module?
var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
return {
incrementCounter: function() {
currentNumber++;
$counterEl.text(currentNumber);
},
init: function () {
$(imageSelector).on('click', this.incrementCounter);
}
}
};var counter = function(imageSelector, counterSelector) {
var currentNumber = 0;
var $counterEl = $(counterSelector);
var _incrementCounter = function () {
currentNumber++;
$counterEl.text(currentNumber);
}
return {
init: function () {
$(imageSelector).on('click', _incrementCounter);
}
}
};So what do frameworks do?
import React from 'react';
class Counter extends React.Component {
constructor: function(props) {
super(props);
this.state = {
currentNumber: 0
}
}
incrementCounter: function() {
var oldNumber = this.state.currentNumber;
this.setState({currentNumber: oldNumber++});
}
render: function() {
return (
<img onClick={this.incrementCouter} src="..." />
<p>
This has been clicked {this.state.currentNumber} times.
</p>
);
}
})
export default Counter; <script src="path/to/javascript/file.js" />
</body> ...
<script src="path/to/javascript/Counter.js" />
<script src="path/to/javascript/Navigation.js" />
<script src="path/to/javascript/Sidebar.js" />
<script src="path/to/javascript/Parallax.js" />
<script src="path/to/javascript/main.js" />
</body>var Counter = function () {
...
return {
init: function () {
// initialize our event handlers
}
}
}var Counter = function () {
...
return {
init: function () {
// initialize our event handlers
}
}
}
export default Counter;import Counter from './path/to/Counter.js';
Counter.init();
Counter.js
main.js
main.js
Counter.js
Navigation.js
increment.js
Entry point into your app
Webpack takes resolves all of these imports and exports and puts them all into one file.
...
<script src="path/to/javascript/Counter.js" />
<script src="path/to/javascript/Navigation.js" />
<script src="path/to/javascript/Sidebar.js" />
<script src="path/to/javascript/Parallax.js" />
<script src="path/to/javascript/main.js" />
</body> <script src="path/to/bundle.js" />
</body>This is why whenever you open some websites and look at the source tab to look at their JavaScript it looks like a garbled mess.
This is actually Webpack minifying the JavaScript and trying to make the file size smaller.
You can even make it do cool stuff to your CSS like automatically add vendor prefixes to your CSS properties so that it will automatically support older browsers.
var modalFactory = (name) {
return {
selector: name
isOpen: false,
hasContent: true,
}
}
var myModalState = modalFactory('modal-one');Use this when you need to create multiple objects where only one or a few properties change.
var modalDecorator = (stateObject) {
var newObject = stateObject
newObject.isClosed = false
newObject.hasContent = true
return newObject;
}
var myModalState = modalDecorator({
selector: 'modal-one'
});Use this when you need to mutate an object with consistent properties.