About

Tuesday, September 29, 2020

Some New Icon Sets

I’ve bookmarked some icon sets lately, partly because I can never find a nice set when I need to. I figured I’d even go the extra mile here and blog them so I can definitely find them later. Aside from being nice, cohesive, and practical sets of icons, I find it interesting that literally all of them:

  • are SVG, and thus easily resizeable
  • are built with rather efficient <path> elements
  • are stroked instead of filled (at least optionally)
  • have a click-to-copy SVG feature on their site
  • are free and open source

Good job, all! Seems like people are coming around to the idea of an SVG icon system where you just… put the SVG in the HTML.

Tabler Icons

Teenyicons

Heroicons

hola svg

This one is from Mariana Beldi who recently shared how hand-drawing SVG can be so much more efficient than using an illustration tool.


The post Some New Icon Sets appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.



source https://css-tricks.com/some-new-icon-sets/

Make Your Own Dev Tool

Amber Wilson on making bookmarklets to help yo-self. She shows off one that injects an accessibility script — I like this approach, as it means you don’t have to maintain the bookmarklet, just the script it links to). Another example runs some code contained right in the link. The result is literally a bookmark in your browser you can click to do something that is useful to you on any site.

Well, I say “any” site, but what I mean is “sites that don’t have a Content Security Policy (CSP)” which is capable of totally blocking inline scripts (and that’s probably the best thing a CSP can do). That’s wonderful for security, but completely stops bookmarklets. The answer is browser extensions. The story with those is getting better as browsers converge on a standard format.

Browser extensions are much harder to write. Someone should make a browser extension that allows you to create arbitrary bookmarklet-ish snippets to run. I found a few attempts at this in a quick search, but nothing that looks particularly nice. Another thought: DevTools has snippets.

Direct Link to ArticlePermalink


The post Make Your Own Dev Tool appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.



source https://amberwilson.co.uk/blog/make-your-own-dev-tool/

The Flavors of Object-Oriented Programming (in JavaScript)

In my research, I’ve found there are four approaches to Object-Oriented Programming in JavaScript:

Which methods should I use? Which one is “the best” way? Here I’ll present my findings along with information that may help you decide which is right for you.

To make that decision, we’re not just going to look at the different flavors but compare conceptual aspects between them:

What is Object-Oriented Programming?

Object-Oriented Programming is a way of writing code that allows you to create different objects from a common object. The common object is usually called a blueprint while the created objects are called instances.

Each instance has properties that are not shared with other instances. For example, if you have a Human blueprint, you can create human instances with different names.

The second aspect of Object-Oriented Programming is about structuring code when you have multiple levels of blueprints. This is commonly called Inheritance or subclassing.

The third aspect of Object Oriented Programming is about encapsulation where you hide certain pieces of information within the object so they’re not accessible.

If you need more than this brief intro, here’s an article that introduces this aspect of Object-Oriented Programming if you need help with it.

Let’s begin with the basics — an introduction to the four flavors of Object-Oriented Programming.

The four flavors of Object-Oriented Programming

There are four ways to write Object-Oriented Programming in JavaScript. They are:

Using Constructor functions

Constructors are functions that contain a this keyword.

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastName = lastName
}

this lets you store (and access) unique values created for each instance. You can create an instance with the new keyword.

const chris = new Human('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

const zell = new Human('Zell', 'Liew')
console.log(zell.firstName) // Zell
console.log(zell.lastName) // Liew

Class syntax

Classes are said to be the “syntactic sugar” of Constructor functions. As in, Classes are an easier way of writing Constructor functions.

There’s serious contention about whether Classes are bad (like this and this). We’re not going to dive into those arguments here. Instead, we’re just going to look at how to write code with Classes and decide whether Classes are better than constructors based on the code we write.

Classes can be written with the following syntax:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Notice the constructor function contains the same code as the Constructor syntax above? We need to do this since we want to initialize values into this. (We can skip constructor if we don’t need to initialize values. More on this later under Inheritance).

At first glance, classes seem to be inferior to constructors — there’s more code to write! Hold your horses and don’t form a conclusion at this point. We have a lot more to cover. Classes begin to shine later.

As before, you can create an instance with the new keyword.

const chris = new Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Objects Linking to Other Objects (OLOO)

OLOO was coined and popularized by Kyle Simpson. In OLOO, you define the blueprint as a normal object. You then use a method (often named init, but that isn’t required in the way constructor is to a Class) to initialize the instance.

const Human = {
  init () {
    this.firstName = firstName
    this.lastName = lastName
  }
}

You use Object.create to create an instance. After creating the instance, you need to run your init function.

const chris = Object.create(Human)
chris.init('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

You can chain init after Object.create if you returned this inside init.

const Human = {
  init () {
    // ...
    return this 
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Factory functions

Factory functions are functions that return an object. You can return any object. You can even return a Class instance or OLOO instance — and it’ll still be a valid Factory function.

Here’s the simplest way to create Factory functions:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName
  }
}

You don’t need new to create instances with Factory functions. You simply call the function.

const chris = Human('Chris', 'Coyier')

console.log(chris.firstName) // Chris
console.log(chris.lastName) // Coyier

Now that we’ve seen these four OOP setup possibilities, let’s look at how you declare properties and methods on each of them so we can get a little better understanding of working with them before getting to the bigger comparisons we’re trying to make.


Declaring properties and methods

Methods are functions declared as an object’s property.

const someObject = {
  someMethod () { /* ... */ }
}

In Object-Oriented Programming, there are two ways to declare properties and methods:

  1. Directly on the instance
  2. In the Prototype

Let’s learn to do both.

Declaring properties and methods with Constructors

If you want to declare a property directly on an instance, you can write the property inside the constructor function. Make sure to set it as the property for this.

function Human (firstName, lastName) {
  // Declares properties
  this.firstName = firstName
  this.lastname = lastName

  // Declares methods
  this.sayHello = function () {
    console.log(`Hello, I'm ${firstName}`)
  }
}

const chris = new Human('Chris', 'Coyier')
console.log(chris)

Methods are commonly declared on the Prototype because Prototype allows instances to use the same method. It’s a smaller “code footprint.”

To declare properties on the Prototype, you need to use the prototype property.

function Human (firstName, lastName) {
  this.firstName = firstName
  this.lastname = lastName
}

// Declaring method on a prototype
Human.prototype.sayHello = function () {
  console.log(`Hello, I'm ${this.firstName}`)
}

It can be clunky if you want to declare multiple methods in a Prototype.

// Declaring methods on a prototype
Human.prototype.method1 = function () { /*...*/ }
Human.prototype.method2 = function () { /*...*/ }
Human.prototype.method3 = function () { /*...*/ }

You can make things easier by using merging functions like Object.assign.

Object.assign(Human.prototype, {
  method1 () { /*...*/ },
  method2 () { /*...*/ },
  method3 () { /*...*/ }
})

Object.assign does not support the merging of Getter and Setter functions. You need another tool. Here’s why. And here’s a tool I created to merge objects with Getters and Setters.

Declaring properties and methods with Classes

You can declare properties for each instance inside the constructor function.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
      this.lastname = lastName

      this.sayHello = function () {
        console.log(`Hello, I'm ${firstName}`)
      }
  }
}

It’s easier to declare methods on the prototype. You write the method after constructor like a normal function.

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

It’s easier to declare multiple methods on Classes compared to Constructors. You don’t need the Object.assign syntax. You just write more functions.

Note: there’s no , between method declarations in a Class.

class Human (firstName, lastName) {
  constructor (firstName, lastName) { /* ... */ }

  method1 () { /*...*/ }
  method2 () { /*...*/ }
  method3 () { /*...*/ }
}

Declaring properties and methods with OLOO

You use the same process for declaring properties and methods on an instance. You assign them as a property of this.

const Human = {
  init (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    this.sayHello = function () {
      console.log(`Hello, I'm ${firstName}`)
    }

    return this
  }
}

const chris = Object.create(Human).init('Chris', 'Coyier')
console.log(chris)

To declare methods in the prototype, you write the method like a normal object.

const Human = {
  init () { /*...*/ },
  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

Declaring properties and methods with Factory functions

You can declare properties and methods directly by including them in the returned object.

function Human (firstName, lastName) {
  return {
    firstName,
    lastName, 
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

You cannot declare methods on the Prototype when you use Factory functions. If you really want methods on the prototype, you need to return a Constructor, Class, or OLOO instance. (Don’t do this since it doesn’t make any sense.)

// Do not do this
function createHuman (...args) {
  return new Human(...args)
}

Where to declare properties and methods

Should you declare properties and methods directly on the instance? Or should you use prototype as much as you can?

Many people take pride that JavaScript is a “Prototypal Language” (which means it uses prototypes). From this statement, you may make the assumption that using “Prototypes” is better.

The real answer is: It doesn’t matter.

If you declare properties and methods on instances, each instance will take up slightly more memory. If you declare methods on Prototypes, the memory used by each instance will decrease, but not much. This difference is insignificant with computer processing power what it is today. Instead, you want to look at how easy it is to write code — and whether it is possible to use Prototypes in the first place.

For example, if you use Classes or OLOO, you’ll be better off using Prototypes since the code is easier to write. If you use Factory functions, you cannot use Prototypes. You can only create properties and methods directly on the instance.

I wrote a separate article on understanding JavaScript Prototypes if you’re interested in finding out more.

Preliminary verdict

We can make a few notes from the code we wrote above. These opinions are my own!

  1. Classes are better than Constructors because its easier to write multiple methods on Classes.
  2. OLOO is weird because of the Object.create part. I gave OLOO a run for a while, but I always forget to write Object.create. It’s weird enough for me not to use it.
  3. Classes and Factry Fufnctions are easiest to use. The problem is that Factory functions don’t support Prototypes. But like I said, this doesn’t really matter in production.

We’re down to two. Should we choose Classes or Factory functions then? Let’s compare them!


Classes vs. Factory functions — Inheritance

To continue the discussion on Classes and Factory functions, we need to understand three more concepts that are tied closely to Object-Oriented Programming.

  1. Inheritance
  2. Encapsulation
  3. this

Let’s start with Inheritance.

What is Inheritance?

Inheritance is a loaded word. Many people in the industry use Inheritance incorrectly, in my opinion. The word “inheritance” is used when you receive things from somewhere. For example:

  • If you get an inheritance from your parents, it means you get money and assets from them.
  • If you inherit genes from your parents, it means you get your genes from them.
  • If you inherit a process from your teacher, it means you get that process from them.

Fairly straightforward.

In JavaScript, Inheritance can mean the same thing: where you get properties and methods from the parent blueprint.

This means all instances actually inherit from their blueprints. They inherit properties and methods in two ways:

  1. by creating a property or method directly upon creating the instance
  2. via the Prototype chain

We discussed how to do both methods in the previous article so refer back to it if you need help seeing these processes in code.

There’s a second meaning for Inheritance in JavaScript — where you create a derivative blueprint from the parent blueprint. This process is more accurately called Subclassing, but people sometimes will call this Inheritance as well.

Understanding Subclassing

Subclassing is about creating a derivative blueprint from a common blueprint. You can use any Object-Oriented Programming flavor to create the Subclass.

We’ll talk about this with the Class syntax first because it’s easier to understand.

Subclassing with Class

When you create a Subclass, you use the extends keyword.

class Child extends Parent {
  // ... Stuff goes here
}

For example, let’s say we want to create a Developer class from a Human class.

// Human Class
class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

The Developer class will extend Human like this:

class Developer extends Human {
  constructor(firstName, lastName) {
    super(firstName, lastName)
  }

    // Add other methods
}

Note: super calls the Human (also called the “parent”) Class. It initiates the constructor from Human. If you don’t need extra initiation code, you can omit constructor entirely.

class Developer extends Human {
  // Add other methods
}

Let’s say a Developer can code. We can add the code method directly to Developer.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Here’s an example of an instance of Developer:

const chris = new Developer('Chris', 'Coyier')
console.log(chris)
Instance of a Developer class.

Subclassing with Factory functions

There are four steps to creating Subclasses with Factory functions:

  1. Create a new Factory function
  2. Create an instance of the Parent blueprint
  3. Create a new copy of this instance
  4. Add properties and methods to this new copy

The process looks like this:

function Subclass (...args) {
  const instance = ParentClass(...args)
  return Object.assign({}, instance, {
    // Properties and methods go here
  })
}

We’ll use the same example — creating a Developer Subclass — to illustrate this process. Here’s the Human factory function:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

We can create Developer like this:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    // Properties and methods go here
  })
}

Then we add the code method like this:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Here’s an example of a Developer instance :

const chris = Developer('Chris', 'Coyier')
console.log(chris)
Example of a Developer instance with Factory functions.

Note: You cannot use Object.assign if you use Getters and Setters. You’ll need another tool, like mix. I explain why in this article.

Overwriting the Parent’s method

Sometimes you need to overwrite the Parent’s method inside the Subclass. You can do this by:

  1. Creating a method with the same name
  2. Calling the Parent’s method (optional)
  3. Changing whatever you need in the Subclass’s method

The process looks like this with Classes:

class Developer extends Human {
  sayHello () {
    // Calls the parent method
    super.sayHello() 

    // Additional stuff to run
    console.log(`I'm a developer.`)
  }
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()
Overwriting a parent's method.

The process looks like this with Factory functions:

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)

  return Object.assign({}, human, {
      sayHello () {
        // Calls the parent method
        human.sayHello() 

        // Additional stuff to run
        console.log(`I'm a developer.`)
      }
  })
}

const chris = new Developer('Chris', 'Coyier')
chris.sayHello()
Overwriting a parent's method.

Inheritance vs. Composition

No talk about Inheritance ever concludes without the mention of Composition. Experts like Eric Elliot often suggests we should favor Composition over Inheritance.

“Favor object composition over class inheritance” the Gang of Four, “Design Patterns: Elements of Reusable Object Oriented Software”

“In computer science, a composite data type or compound data type is any data type which can be constructed in a program using the programming language’s primitive data types and other composite types. […] The act of constructing a composite type is known as composition.” ~ Wikipedia

So let’s give Composition a deeper look and understand what it is.

Understanding Composition

Composition is the act of combining two things into one. It’s about merging things together. The most common (and simplest) way of merging objects is with Object.assign.

const one = { one: 'one' }
const two = { two: 'two' }
const combined = Object.assign({}, one, two)

The use of Composition can be better explained with an example. Let’s say we already have two Subclasses, a Designer and Developer. Designers can design, while developers can code. Both designers and developers inherit from the Human class.

Here’s the code so far:

class Human {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class Designer extends Human {
  design (thing) {
    console.log(`${this.firstName} designed ${thing}`)
  }
}

class Developer extends Designer {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Now let’s say you want to create a third Subclass. This Subclass is a mix of a Designer and a Developer they — can design and code. Let’s call it DesignerDeveloper (or DeveloperDesigner, whichever you fancy).

How would you create the third Subclass?

We cannot extend Designer and Developer classes at the same time. This is impossible because we cannot decide which properties come first. This is often called The Diamond Problem.

Diamond problem.

The Diamond Problem can be easily solved if we do something like Object.assign – where we prioritize one object over the other. If we use the Object.assign approach, we may be able to extend classes like this. But this is not supported in JavaScript.

// Doesn't work
class DesignerDeveloper extends Developer, Designer {
  // ...
}

So we need to rely on Composition.

Composition says: Instead of trying to create DesignerDeveloper via Subclassing, let’s create a new object that stores common features. We can then include these features whenever necessary.

In practice, it can look like this:

const skills = {
  code (thing) { /* ... */ },
  design (thing) { /* ... */ },
  sayHello () { /* ... */ }
}

We can then skip Human altogether and create three different classes based on their skills.

Here’s the code for DesignerDeveloper:

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName

    Object.assign(this, {
      code: skills.code,
      design: skills.design,
      sayHello: skills.sayHello
    })
  }
}

const chris = new DesignerDeveloper('Chris', 'Coyier')
console.log(chris)
Composing methods into a class

You can do the same with Developer and Designer.

class Designer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName 

    Object.assign(this, {
      design: skills.design,
      sayHello: skills.sayHello
    }) 
  }
}

class Developer {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName 

    Object.assign(this, {
      code: skills.code,
      sayHello: skills.sayHello
    }) 
  }
}

Did you notice we’re creating methods directly on the instance? This is just one option. We can still put methods into the Prototype, but I think the code looks clunky. (It’s as if we’re writing Constructor functions all over again.)

class DesignerDeveloper {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design,
  sayHello: skills.sayHello
})
Composition via Classes by putting methods into the Prototype.

Feel free to use whatever code structure you’re attracted to. The results are kinda the same anyway.

Composition with Factory Functions

Composition with Factory functions is essentially adding the shared methods into the returned object.

function DesignerDeveloper (firstName, lastName) {
  return {
    firstName,
    lastName,    
    code: skills.code,
    design: skills.design,
    sayHello: skills.sayHello
  }
}
Composing methods into a factory function

Inheritance and Composition at the same time

Nobody says we can’t use Inheritance and Composition at the same time. We can!

Using the example we’ve ironed out so far, Designer, Developer, and DesignerDeveloper Humans are still humans. They can extend the Human object.

Here’s an example where we use both inheritance and composition with the class syntax.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

class DesignerDeveloper extends Human {}
Object.assign(DesignerDeveloper.prototype, {
  code: skills.code,
  design: skills.design
})
Subclassing and Composition at the same time.

And here’s the same thing with Factory functions:

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () { 
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function DesignerDeveloper (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code: skills.code,
    design: skills.design
  }
}
Subclassing and Composition in Factory functions

Subclassing in the real world

One final point about Subclassing vs. Composition. Even though experts have pointed out that Composition is more flexible (and hence more useful), Subclassing still has its merits. Many things we use today are built with the Subclassing strategy.

For example: The click event we know and love is a MouseEvent. MouseEvent is a Subclass of a UIEvent, which in turn is a Subclass of Event.

MouseEvent is a subclass of UIEvent.

Another example: HTML Elements are Subclasses of Nodes. That’s why they can use all properties and methods of Nodes.

HTMLElement is a subclass of Node.

Preliminary verdict

Classes and Factory functions can both use Inheritance and Composition. Composition seems to be cleaner in Factory functions though, but that’s not a big win over Classes.

We’ll examine Classes and Factory Functions more in detail next.


Classes vs. Factory functions — Encapsulation

We’v looked at the four different Object-Oriented Programming flavors so far. Two of them — Classes and Factory functions — are easier to use compared to the rest.

But the questions remain: Which should you use? And why?

To continue the discussion on Classes and Factory functions, we need to understand three concepts that are tied closely to Object-Oriented Programming:

  1. Inheritance
  2. Encapsulation
  3. this

We just talked about Inheritance. Now let’s talk about Encapsulation.

Encapsulation

Encapsulation is a big word, but it has a simple meaning. Encapsulation is the act of enclosing one thing inside another thing so the thing inside doesn’t leak out. Think about storing water inside a bottle. The bottle prevents water from leaking out.

In JavaScript, we’re interested in enclosing variables (which can include functions) so these variables don’t leak out into the external scope. This means you need to understand scope to understand encapsulation. We’ll go through an explanation, but you can also use this article to beef up your knowledge regarding scopes.

Simple Encapsulation

The simplest form of Encapsulation is a block scope.

{
  // Variables declared here won't leak out
}

When you’re in the block, you can access variables that are declared outside the block.

const food = 'Hamburger'

{
  console.log(food)
}
Logs food from inside the blog. Result: Hamburger.

But when you’re outside the block, you cannot access variables that are declared inside the block.

{
  const food = 'Hamburger'
}

console.log(food)
Logs food from outside the blog. Results: Error.

Note: Variables declared with var don’t respect block scope. This is why I recommend you use let or const to declare variables.

Encapsulating with functions

Functions behave like block scopes. When you declare a variable inside a function, they cannot leak out of that function. This works for all variables, even those declared with var.

function sayFood () {
  const food = 'Hamburger'
}

sayFood()
console.log(food)
Logs food from outside the function. Results: Error.

Likewise, when you’re inside the function, you can access variables that are declared outside of that function.

const food = 'Hamburger'

function sayFood () {
  console.log(food)
}


sayFood()
Logs food from inside the function. Result: Hamburger.

Functions can return a value. This returned value can be used later, outside the function.

function sayFood () {
  return 'Hamburger'
}

console.log(sayFood())
Logs return value from function. Result: Hamburger.

Closures

Closures are an advanced form of Encapsulation. They’re simply functions wrapped in functions.

// Here's a closure
function outsideFunction () {
  function insideFunction () { /* ...*/ }
}

Variables declared in outsideFunction can be used in insideFunction.

function outsideFunction () {
  const food = 'Hamburger'
  console.log('Called outside')

  return function insideFunction () {
    console.log('Called inside')
    console.log(food)
  }
}

// Calls `outsideFunction`, which returns `insideFunction`
// Stores `insideFunction` as variable `fn`
const fn = outsideFunction() 

// Calls `insideFunction`
fn()
Closure logs.

Encapsulation and Object-Oriented Programming

When you build objects, you want to make some properties publicly available (so people can use them). But you also want to keep some properties private (so others can’t break your implementation).

Let’s work through this with an example to make things clearer. Let’s say we have a Car blueprint. When we produce new cars, we fill each car up with 50 liters of fuel.

class Car {
  constructor () {
    this.fuel = 50
  }
}

Here we exposed the fuel property. Users can use fuel to get the amount of fuel left in their cars.

const car = new Car()
console.log(car.fuel) // 50

Users can also use the fuel property to set any amount of fuel.

const car = new Car()
car.fuel = 3000
console.log(car.fuel) // 3000

Let’s add a condition and say that each car has a maximum capacity of 100 liters. With this condition, we don’t want to let users set the fuel property freely because they may break the car.

There are two ways to do prevent users from setting fuel:

  1. Private by convention
  2. Real Private Members

Private by convention

In JavaScript, there’s a practice of prepending underscores to a variable name. This denotes the variable is private and should not be used.

class Car {
  constructor () {
    // Denotes that `_fuel` is private. Don't use it!
    this._fuel = 50
  }
}

We often create methods to get and set this “private” _fuel variable.

class Car {
  constructor () { 
    // Denotes that `_fuel` is private. Don't use it!
    this._fuel = 50
  }

  getFuel () {
    return this._fuel
  }

  setFuel (value) {
    this._fuel = value
    // Caps fuel at 100 liters
    if (value > 100) this._fuel = 100
  }
}

Users should use the getFuel and setFuel methods to get and set fuel.

const car = new Car() 
console.log(car.getFuel()) // 50 

car.setFuel(3000)
console.log(car.getFuel()) // 100 

But _fuel is not actually private. It is still a public variable. You can still access it, you can still use it, and you can still abuse it (even if the abusing part is an accident).

const car = new Car() 
console.log(car.getFuel()) // 50 

car._fuel = 3000
console.log(car.getFuel()) // 3000

We need to use real private variables if we want to completely prevent users from accessing them.

Real Private Members

Members here refer to variables, functions, and methods. It’s a collective term.

Private Members with Classes

Classes let you create private members by prepending # to the variable.

class Car {
  constructor () {
    this.#fuel = 50
  }
}

Unfortunately, you can’t use # directly inside a constructor function.

Error when declaring <code>#</code> directly in constructor function.

You need to declare the private variable outside the constructor first.

class Car {
  // Declares private variable
  #fuel 
  constructor () {
    // Use private variable
    this.#fuel = 50
  }
}

In this case, we can use a shorthand and declare#fuel upfront since we set fuel to 50.

class Car {
  #fuel = 50
}

You cannot access #fuel outside Car. You’ll get an error.

const car = new Car()
console.log(car.#fuel)
Cannot access #fuel.

You need methods (like getFuel or setFuel) to use the #fuel variable.

class Car {
  #fuel = 50

  getFuel () {
    return this.#fuel
  }

  setFuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.getFuel()) // 50

car.setFuel(3000)
console.log(car.getFuel()) // 100

Note: I prefer Getters and Setters instead of getFuel and setFuel. The syntax is easier to read.

class Car {
  #fuel = 50

  get fuel () {
    return this.#fuel
  }

  set fuel (value) {
    this.#fuel = value
    if (value > 100) this.#fuel = 100
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100

Private Members with Factory functions

Factory functions create Private Members automatically. You just need to declare a variable like normal. Users will not be able to get that variable anywhere else. This is because variables are function-scoped and hence encapsulated by default.

function Car () {
  const fuel = 50 
}

const car = new Car() 
console.log(car.fuel) // undefined 
console.log(fuel) // Error: `fuel` is not defined

We can create getter and setter functions to use this private fuel variable.

function Car () {
  const fuel = 50 

  return {
    get fuel () { 
      return fuel 
    },

    set fuel (value) {
      fuel = value 
      if (value > 100) fuel = 100
    }
  }
}

const car = new Car()
console.log(car.fuel) // 50

car.fuel = 3000
console.log(car.fuel) // 100

That’s it! Simple and easy!

Verdict for Encapsulation

Encapsulation with Factory functions are simpler and easier to understand. They rely on the scopes which are a big part of the JavaScript language.

Encapsulation with Classes, on the other hand, requires prepending # to the private variable. This can make things clunky.

We’ll look at the final concept — this to complete the comparison between Classes and Factory functions — in the next section.


Classes vs. Factory Functions — The this variable

this (ha!) is one of the main arguments against using Classes for Object-Oriented Programming. Why? Because this value changes depending on how it is used. It can be confusing for many developers (both new and experienced).

But the concept of this is relatively simple in reality. There are only six contexts in which you can use this. If you master these six contexts, you’ll have no problems using this.

The six contexts are:

  1. In a global context
  2. Inan object construction
  3. In an object property / method
  4. In a simple function
  5. In an arrow function
  6. In an event listener

I covered these six contexts in detail. Give it a read if you need help understanding this.

Note: Don’t shy away from learning to use this. It’s an important concept you need to understand if you intend on mastering JavaScript.

Come back to this article after you’ve solidified your knowledge on this. We’ll have a deeper discussion about using this in Classes and Factory functions.

Back yet? Good. Let’s go!

Using this in Classes

this refers to the instance when used in a Class. (It uses the “In an object property / method” context.) This is why you can set properties and methods on the instance inside the constructor function.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
    console.log(this)
  }
}

const chris = new Human('Chris', 'Coyier')
<code>this</code> points to the instance

Using this in Constructor functions

If you use this inside a function and new to create an instance, this will refer to the instance. This is how a Constructor function is created.

function Human (firstName, lastName) {
  this.firstName = firstName 
  this.lastName = lastName
  console.log(this)  
}

const chris = new Human('Chris', 'Coyier')
<code>this</code> points to the instance.

I mentioned Constructor functions because you can use this inside Factory functions. But this points to Window (or undefined if you use ES6 Modules, or a bundler like webpack).

// NOT a Constructor function because we did not create instances with the `new` keyword
function Human (firstName, lastName) {
  this.firstName = firstName 
  this.lastName = lastName
  console.log(this)  
}

const chris = Human('Chris', 'Coyier')
<code>this</code> points to Window.

Essentially, when you create a Factory function, you should not use this as if it’s a Constructor function. This is one small hiccup people experience with this. I wanted to highlight the problem and make it clear.

Using this in a Factory function

The correct way to use this in a Factory function is to use it “in an object property / method” context.

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayThis () {
      console.log(this)
    }
  }
}

const chris = Human('Chris', 'Coyier')
chris.sayThis()
<code>this</code> points to the instance.

Even though you can use this in Factory functions, you don’t need to use them. You can create a variable that points to the instance. Once you do this, you can use the variable instead of this. Here’s an example at work.

function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${human.firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()

human.firstName is clearer than this.firstName because human definitely points back to the instance. You know when you see the code.

If you’re used to JavaScript, you may also notice there’s no need to even write human.firstName in the first place! Just firstName is enough because firstName is in the lexical scope. (Read this article if you need help with scopes.)

function Human (firstName, lastName) {
  const human = {
    firstName,
    lastName,
    sayHello() {
      console.log(`Hi, I'm ${firstName}`)
    }
  }

  return human
}

const chris = Human('Chris', 'Coyier')
chris.sayHello()
Runs <code>chris.sayHello</code>

What we covered so far is simple. It’s not easy to decide whether this is actually needed until we create a sufficiently complicated example. So let’s do that.

Detailed example

Here’s the setup. Let’s say we have a Human blueprint. This Human ha firstName and lastName properties, and a sayHello method.

We have a Developer blueprint that’s derived from Human. Developers can code, so they’ll have a code method. Developers also want to proclaim they’re developers, so we need to overwrite sayHello and add I'm a Developer to the console.

We’ll create this example with Classes and Factory functions. (We’ll make an example with this and an example without this for Factory functions).

The example with Classes

First, we have a Human blueprint. This Human has a firstName and lastName properties, as well as a sayHello method.

class Human {
  constructor (firstName, lastName) {
    this.firstName = firstName
    this.lastname = lastName 
  }

  sayHello () {
    console.log(`Hello, I'm ${this.firstName}`)
  }
}

We have a Developer blueprint that’s derived from Human. Developers can code, so they’ll have a code method.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }
}

Developers also want to proclaim that they’re developers. We need to overwrite sayHello and add I'm a Developer to the console. We do this by calling Human‘s sayHello method. We can do this using super.

class Developer extends Human {
  code (thing) {
    console.log(`${this.firstName} coded ${thing}`)
  }

  sayHello () {
    super.sayHello()
    console.log(`I'm a developer`)
  }
}

The example with Factory functions (with this)

Again, first, we have a Human blueprint. This Human has firstName and lastName properties, as well as a sayHello method.

function Human () {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

Next, we have a Developer blueprint that’s derived from Human. Developers can code, so they’ll have a code method.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    }
  })
}

Developers also want to proclaim they’re developers. We need to overwrite sayHello and add I'm a Developer to the console.
We do this by calling Human‘s sayHello method. We can do this using the human instance.

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I\'m a developer')
    }
  })
}

The example with Factory functions (without this)

Here’s the full code using Factory functions (with this):

function Human (firstName, lastName) {
  return {
    firstName,
    lastName,
    sayHello () {
      console.log(`Hello, I'm ${this.firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  const human = Human(firstName, lastName)
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${this.firstName} coded ${thing}`)
    },

    sayHello () {
      human.sayHello()
      console.log('I\'m a developer')
    }
  })
}

Did you notice firstName is available within the lexical scope in both Human and Developer? This means we can omit this and use firstName directly in both blueprints.

function Human (firstName, lastName) {
  return {
    // ...
    sayHello () {
      console.log(`Hello, I'm ${firstName}`)
    }
  }
}

function Developer (firstName, lastName) {
  // ...
  return Object.assign({}, human, {
    code (thing) {
      console.log(`${firstName} coded ${thing}`)
    },

    sayHello () { /* ... */ }
  })
}

See that? This means you can safely omit this from your code when you use Factory functions.

Verdict for this

In simple terms, Classes require this while Factory functions don’t. I prefer Factory functions here because:

  1. The context of this can change (which can be confusing)
  2. The code written with factory functions is shorter and cleaner (since we can use encapsulated variables without writing this.#variable).

Next up is the last section where we build a simple component together with both Classes and Factory functions. You get to see how they differ and how to use event listeners with each flavolr.

Classes vs Factory functions — Event listeners

Most Object-Oriented Programming articles show you examples without event listeners. Those examples can be easier to understand, but they don’t reflect the work we do as frontend developers. The work we do requires event listeners — for a simple reason — because we need to build things that rely on user input.

Since event listeners change the context of this, they can make Classes troublesome to deal with. At the same time, they make Factory functions more appealing.

But that’s not really the case.

The change in this doesn’t matter if you know how to handle this in both Classes and Factory functions. Few articles cover this topic so I thought it would be good to complete this article with a simple component using Object-Oriented Programming flavors.

Building a counter

We’re going to build a simple counter in this article. We’ll use everything you learned in this article — including private variables.

Let’s say the counter contains two things:

  1. The count itself
  2. A button to increase the count

Here’s the simplest possible HTML for the counter:

<div class="counter">
  <p>Count: <span>0</span>
  <button>Increase Count</button>
</div>

Building the Counter with Classes

To make things simple, we’ll ask users to find and pass the counter’s HTML into a Counter class.

class Counter () {
  constructor (counter) {
    // Do stuff 
  } 
}

// Usage 
const counter = new Counter(document.querySelector('.counter'))

We need to get two elements in the Counter class:

  1. The <span> that contains the count – we need to update this element when the count increases
  2. The <button> – we need to add an event listener to this element class
Counter () {
  constructor (counter) {
    this.countElement = counter.querySelector('span')
    this.buttonElement = counter.querySelector('button')
  }
}

We’ll initialize a count variable and set it to what the countElement shows. We’ll use a private #count variable since the count shouldn’t be exposed elsewhere.

class Counter () {
  #count
  constructor (counter) {
    // ...

    this.#count = parseInt(countElement.textContent)
  } 
}

When a user clicks the <button>, we want to increase #count. We can do this with another method. We’ll name this method increaseCount.

class Counter () {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
  }
}

Next, we need to update the DOM with the new #count. Let’s create a method called updateCount to do this. We will call updateCount from increaseCount:

class Counter () {
  #count
  constructor (counter) { /* ... */ }

  increaseCount () {
    this.#count = this.#count + 1
    this.updateCount()
  }

  updateCount () {
    this.countElement.textContent = this.#count
  }
}

We’re ready to add the event listener now.

Adding the event listener

We will add the event listener to the this.buttonElement. Unfortunately, we cannot use increaseCount as the callback straightaway. You’ll get an error if you try it.

class Counter () {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  // Methods
}
Error accessing #count because this doesn't point to the instance

You get an error because this points to buttonElement. (This is the event listener context.) You’ll see the buttonElement if you logged this into the console.

this points to the button element

We need to change the value of this back to the instance for increaseCount in order for things to work. There are two ways to do it:

  1. Use bind
  2. Use arrow functions

Most people use the first method (but the second one is easier).

Adding the event listener with bind

bind returns a new function. It lets you change this to the first argument that’s passed. People normally create event listeners by calling bind(this).

class Counter () {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount.bind(this))
  }

  // ...
}

This works, but it’s not very nice to read. It’s also not beginner-friendly because bind is seen as an advanced JavaScript function.

Arrow functions

The second way is to use arrow functions. Arrow functions work because it preserves the this value to the lexical context.

Most people write methods inside the arrow function callback, like this:

class Counter () {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', _ => {
      this.increaseCount()
    })
  }

  // Methods
}

This works, but it is a long way around. There’s actually a shortcut.

You can create increaseCount with arrow functions. If you do this, the this value for increaseCount will be bound to the instance’s value straightaway.

So here’s the code you need:

class Counter () {
  // ...

  constructor (counter) {
    // ...
    this.buttonElement.addEventListener('click', this.increaseCount)
  }

  increaseCount = () => {
    this.#count = this.#count + 1
    this.updateCounter()
  }

  // ...
}

The code

Here’s a complete version of the Class-based code (using arrow functions).

Creating the Counter with Factory functions

We’ll do the same thing here. We’ll get users to pass the Counter’s HTML into the Counter factory.

function Counter (counter) {
  // ...
}

const counter = Counter(document.querySelector('.counter'))

We need to get two elements from counter — the <span> and the <button>. We can use normal variables (without this) here because they are private variables already. We won’t expose them.

function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')
}

We will initialize a count variable to the value that’s present in the HTML.

function Counter (counter) {
  const countElement = counter.querySelector('span')
  const buttonElement = counter.querySelector('button')

  let count = parseInt(countElement.textContext)
}

We will increase this count variable with an increaseCount method. You can choose to use a normal function here, but I like to create a method to keep things neat and tidy.

function Counter (counter) {
  // ... 
  const counter = {
    increaseCount () {
      count = count + 1
    }
  }
}

Finally, we will update the count with an updateCount method. We will also call updateCount from increaseCount.

function Counter (counter) {
  // ... 
  const counter = {
    increaseCount () {
      count = count + 1
      counter.updateCount()
    }

    updateCount () {
      increaseCount()
    }
  }
}

Notice I used counter.updateCount instead of this.updateCount? I like this because counter is clearer compared to this.I also do this because beginners can also make a mistake with this inside Factory functions (which I’ll cover later).

Adding event listeners

We can add event listeners to the buttonElement. When we do this, we can use counter.increaseCount as the callback straight away.

We can do this because we didn’t use this, so it doesn’t matter even if event listeners change the this value.

function Counter (counterElement) {
  // Variables 

  // Methods
  const counter = { /* ... */ }

  // Event Listeners
  buttonElement.addEventListener('click', counter.increaseCount)
}

The this gotcha

You can use this in Factory functions. But you need to use this in a method context.

In the following example, if you call counter.increaseCount, JavaScript will also call counter.updateCount. This works because this points to the counter variable.

function Counter (counterElement) {
  // Variables 

  // Methods
  const counter = {
    increaseCount() {
      count = count + 1
      this.updateCount()
    }
  }

  // Event Listeners
  buttonElement.addEventListener('click', counter.increaseCount)
}

Unfortunately, the event listener wouldn’t work because the this value was changed. You’ll need the same treatment as Classes — with bind or arrow functions to — get the event listener working again.

And this leads me to the second gotcha.

Second this gotcha

If you use the Factory function syntax, you cannot create methods with arrow functions. This is because the methods are created in a simple function context.

function Counter (counterElement) {
  // ...
  const counter = {
    // Do not do this. 
    // Doesn't work because `this` is `Window`
    increaseCount: () => {
      count = count + 1
      this.updateCount()
    }
  }
  // ...
}

So, I highly suggest skipping this entirely if you use Factory functions. It’s much easier that way.

The code

Verdict for event listeners

Event listeners change the value of this, so we must be very careful about using the this value. If you use Classes, I recommend creating event listeners callbacks with arrow functions so you don’t have to use bind.

If you use Factory functions, I recommend skipping this entirely because it may confuse you. That’s it!


Conclusion

We talked about the four flavors of Object-Oriented Programming. They are:

  1. Constructor functions
  2. Classes
  3. OLOO
  4. Factory functions

First, we concluded that Classes and Factory functions are easier to use from a code-related point of view.

Second, we compared how to use Subclasses with Classes and Factory functions. Here, we see creating Subclasses is easier with Classes, but Composition is easier with Factory functions.

Third, we compared Encapsulation with Classes and Factory functions. Here, we see Encapsulation with Factory functions is natural — like JavaScript — while encapsulation with Classes requires you to add a # before variables.

Fourth, we compared the usage of this in Classes and Factory functions. I feel Factory functions win here because this can be ambiguous. Writing this.#privateVariable also creates longer code compared to using privateVariable itself.

Finally, in this article, we built a simple Counter with both Classes and Factory functions. You learned how to add event listeners to both Object-Oriented Programming programming flavors. Here, both flavors work. You just need to be careful whether you use this or not.

That’s it!

I hope this shines some light on Object-Oriented Programming in JavaScript for you. If you liked this article, you may like my JavaScript course, Learn JavaScript, where I explain (almost) everything you need to know about JavaScript in a format as clear and succinct as this.

If you have any questions on JavaScript or front-end development in general, feel free to reach out to me. I’ll see how I can help!


The post The Flavors of Object-Oriented Programming (in JavaScript) appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.



source https://css-tricks.com/the-flavors-of-object-oriented-programming-in-javascript/

Monday, September 28, 2020

ooooops I guess we’re* full-stack developers now

*by “we’re” i mean front-end developers

This is a written version of a talk from Jamstack Conf London in 2019 that I put together for attendees. Because, the day before I was to board a flight for that trip, I flew off my mountain bike and broke both my arms. I felt bad that I couldn’t deliver the talk, so I both recorded it and made this. I’m moving it here because I like to keep a lot of my writing under one roof and to have a little fun with WordPress Gutenberg.

👋 Hey! I’ve spent my whole career self-identifying as a front-end developer and trying to help others become better at it.

and boy are my arms tired

I run the site CSS-Tricks (which recently had its 12th birthday), a resource site all about building websites.

I also co-founded the site CodePen, a social coding platform all about building things with front-end technology.

I also co-host a podcast called ShopTalk Show which is going on 400 episodes.

I’m sure I’ve talked to thousands of other developers over the years, which has helped me understand the industry and the spectrum of work it takes to make websites happen.

What is a front-end developer?

✅ It’s a job and a common job title.

This is important because it’s not just semantics. It’s a job that people are hired to do, and it is their job title at work. Nothing more personal than money.

How we define what this job is and what the expectations are is personal.

Look at any job board that has technology jobs on it and you’ll see postings for front-end developers.

What is a front-end developer?

✅ It deals very directly with the browser, devices, and users.

Everybody that works on websites probably has their browser open all day, but front-end developers live in there. They have DevTools open. They have multiple browsers open and test across versions and platforms.

Crucially, they care about the users that interact with those browsers and assistive technology.

Mina Markham explains what a front-end developers is at a high level:

There is a distinction from back-end developers. Its not that back-end developers don’t care about users, it’s just that responsibility is delegated.

Monica Dinculescu puts it well:

The browser is at the heart of the job of front-end development. Whatever you are doing, if your concern is how things ultimately look and work in a browser, that’s front-end development.

It’s harder than it gets credit for.

What is a front-end developer?

✅ There are loads of tools involved, but ultimately it comes down to HTML, CSS, and JavaScript.

Those are the languages that browsers speak. Yes yes, there is SVG and PNG and whatever else also, but you know what I mean.

Whatever other tooling you use, it all comes down to what ships to the browser, and front-end developers are responsible for that.

They are gonna come up at work.

Not all front-end developers know all the languages equally well. In fact, there are lots of developers out there who hardly write any JavaScript at all, but who are otherwise very successful front-end developers. And there are also a lot of front-end developers who write almost nothing but JavaScript. 🤯

My article The Great Divide digs into the split between front-end developers who are deeply into JavaScript and those who are not.

It’s not just my thoughts, but a collection of quotes from many others who feel the divide.

Brad Frost coins the terms “Front of the Front” and “Back of the Front” as a way to describe another sort of divide. He points out that it’s not a weakness, but a strength in putting those different people together.

At Google, the divide is recognized and split by job title. Plus, those two career ladders get paid the same.

Front-End Developer is a real and meaningful term.

There is no doubt about it. Particularly since about 2015, JavaScript has exploded as a language.

(If you need to look at these bigger, use DevTools or whatever. C’mon you’re front-end developers right?)

Our jobs are already facinating! So we all deal with browsers and users and devices. That’s core. But we all know different stuff, and actually put that knowledge to work.

Some of us are designers. Some of us are photographers. Some of us know the law really well. Some of us are way into performance. Some of us specialize in accessibility. Some of us embrace social media.

Metaphorically, you could map us like this tree.

This metaphor probably doesn’t hold up quite perfectly, but the divide looks a little something like this. We still share some fundamentals, and we still branch out and know lots of different stuff, but one side is heavily focused on JavaScript and “Back of the Front” type work and the other is not.

Since this talk is about the slow morphing of front-end developers starting to do more full-stack work and having that role grow wider and wider, let’s just assume we’re talking about front-end developers who go down the heavier JavaScript road.

A bunch of stuff you need to do to build websites has sort of moved stacks.

Back End → JavaScript

That is, from more of a back-end-and-friends skillset to a front-end-and-friends skillset.

Component-Driven Design & Development

Thanks, Obama JavaScript.

It seems to me non-JavaScript server-side rendered projects never really embraced components. (There are examples, don’t @ me, but I mean across the gigantic PHP CMS landscape and Ruby on Rails and huge players like that.) You had templates and includes, but they are a pale comparison to real component-driven development.

It’s facinating to see that while there is a wide variety of JavaScript-based frameworks that all disagree on plenty of things, one thing they all agree on is the idea of components. Even native web components… it’s right in the name.

Let’s take a quick look at CodePen, which is a React powered site (mostly) these days.

Even this tiny little SVG icon? It’s a component. We call it an <SVGIcon /> component because it nicely abstracts away a few things that are useful to us.

Pairing an icon with a number is another component, because it is another repeated pattern and might have additional responsibility, like being clicked.

That whole row of MetaItem components might become a component, along with other aspects of an Item’s display

So of course the whole Item itself becomes a component.

These are visual and functional abstractions of what we need to build the UIs. There is semantic HTML underneath, but the abstractions are building blocks of our own devise.

Larger and larger areas become components. In this case, it makes sense that a grid of items becomes a component, so that it can handle that layout and things like pagination.

It’s true! Not only are components capable of being intelligent building block abstractions for UIs for front-end developers, but designers are also largely already working this way. Tools like Figma, Sketch, and Adobe XD tout things like “symbols” which are spiritually connected.

I find other developers are like “cool, components, I get it”.

Site-Level Architecture / Routing

Back End → JavaScript

OK I guess this is my job now. It makes sense and I like the control, but it’s a big serious job.

Dealing with URLs and overall site structure used to feel primarily like a back end concern. These days, “routing” is becoming more and more a front-end concern.

<Suspense fallback={<Spinner />}>
  <Route
    exact
    path={['/', '/picked-pens']}
    component={anon ? AnonHomepage : Homepage}
  />
  <Route path={['/topics', '/topic']} component={Topics} />
  <Route path={['/challenges']} component={Challenges} />
  <Route path="/instagram" component={Instagram} />
  <Route path="/dashboard" component={Dashboard} />
  <Route
    path={['/profile_new/:username', '/profile_new/team/:teamname']}
    component={Profile}
  />
</Suspense>

Looking back at the CodePen UI, the components don’t stop at the grid. Literally everything becomes a component. The tabs, the titles, the buttons…

… the forms, the menus, the sidebar…

Ultimately the whole gosh-darned page becomes a component.

Once the whole page is a component, what you’ve really done is turned the the URL into a component.

And now that the URL is a component, all the URLs are components, and you’re controlling the whole site.

You’ve become an architect of the entire site, in a sense.

That’s… a lot. Think of all the work you already have to do as a front-end developer. None of that goes away. You’re just responsible for a lot more now. It’s no wonder that front-end developers are feeling more and more full-stack-y.

State Management + Getting & Mutating Data

Back End → JavaScript

Another thing that has fallen into the hands of front-end developers is state management. Now that’s kind of at the core of most JavaScript frameworks and it’s a pretty great concept that wipes away a lot of front-end spaghetti problems of the past.

But state is often filled from getting data, and that’s now often on our shoulders as well.

Even more complicated is changing that data when necessary and sending data back to servers when required.

GraphQL is a pretty great answer to some of this. GraphQL is a lot of things and meaningful to different people in different ways. But to me, it’s about empowerment.

With a strong GraphQL endpoint in place, and tools like Apollo giving me tools to use in my JavaScript framework, I can, as a front-end developer, get my hands on any data I need to build UI.

import gql from "graphql-tag";
import { Query } from "react-apollo";

const GET_DOGS = gql`
  {
    dogs {
      id
      breed
    }
  }
`;

const Dogs = () => (
  <Query query={GET_DOGS}>
    {({ loading, error, data }) => {
      if (loading) return `Loading...`;
      if (error) return `Error`;

      return (
        {data.dogs.map(dog => (
          <div key={dog.id}>
            {dog.breed}
          </div>
        ))}
      );
    }}
  </Query>
);

Note that not only am I getting all my own data, but I’m also managing the asynchronous nature of the component. Should I show a skeleton right away? A spinner? Should I delay rendering until the data is ready? What happens if it times out or there is another error?

Not only can I get data, but it’s on me update that data and send it back through GraphQL in the form of mutations.

mport gql from "graphql-tag";
import { Mutation } from "react-apollo";

const ADD_TODO = gql`
  mutation AddTodo($type: String!) {
    addTodo(type: $type) {
      id
      type
    }
  }
`;

const AddTodo = () => {
  let input;

  return (
    <Mutation mutation={ADD_TODO}>
      {(addTodo, { data }) => (
        <div>
          <form
            onSubmit={e => {
              e.preventDefault();
              addTodo({ variables: { type: input.value } });
              input.value = "";
            }}
          >
            <input
              ref={node => {
                input = node;
              }}
            />
            <button type="submit">Add Todo</button>
          </form>
        </div>
      )}
    </Mutation>
  );
};

Mutations aren’t terribly more complicated than queries, but it’s all the more work that is on my plate as a front-end developer. Work that was almost surely in the realm of back-end development before.

Note that the above examples were illustrative of GraphQL, but done through Apollo Client implemented in React.

While we’re talking about components, queries, and mutations, let’s throw one more thing on the pile: styling.

Front-end developers have always been in charge of styling, but in a land of components that are self-contained in so many other ways, it starts to make sense to co-locate the styling information as well.

Here we’re using CSS modules to scope the styles to a specific component. We can and do still have global styles, and we even continue to use Sass for useful global abstractions.

.root {
  display: grid;
}
import styles from './styles.scss';

<NewsItems className={styles.root} />

The result of this componentization and co-locating is nice little folders that have everything from logic, to view templates, to queries and mutations, to styling all together.

It’s convenient, but it has cool interesting side effects. For example, JavaScript bundles may contain what they need (code splitting). Styling doesn’t get bloated because when components stop being used because their styles leave with them. Not to mention naming things is a lot less stressful since the naming is file-scoped.

The GraphQL documentary is kinda fun. I like what Kyle Mathews says (about 20:24) about React wiping away a whole class of front-end development problems, and how GraphQL does a similar sort of thing.

For every project? Of course not. But for the somewhat large and complex apps that we’re so often expected to build and maintain: yes.

All the very huge responsibilities front-end developers already have:

  • Pulling of the design
  • Making the design part of a system
  • Making sure it is accessible
  • Worrying about the performance
  • Testing things across browsers
  • Testing things across devices
  • Sweating the UX

Oh hello, big pile of new responsibilities

  • Component-driven design, designing our own abstractions
  • Site-level architecture
  • Routing
  • Fetching our own data
  • Talking to APIs
  • Mutating data
  • State management

The haystack of responsibilities grows and grows and grows.

That’s not to say that all of us need to know every single part of this and do it perfectly well, but it is to say that these are tasks that fall within the realm of front-end web development.

Peggy Rayzis talks about how wide the term front-end developer has gotten and that it’s likely we’re specializing.

Again, a bunch of tasks have sort of moved stacks. From what used to be rather back-end jobs to being in the JavaScript realm.

Let’s draw a spectrum and see how that has morphed through time.

LAMP is Linux, Apache, MySQL,and PHP. It’s a rather old stack, but it’s still very huge. It’s what an awful lot of CMS’s run. LAMP is how the stack is talked about.

If I’m a front-end developer working in that stack, I’m way over on the other side of the spectrum, not touching much of the technology the stack is referring to.

MEAN is another stack, referring to MongoDB, Express, Angular, and Node. Notice the OS isn’t mentioned anymore.

Notably, Angular is a front-end framework, so the stack is starting to include that. If I’m a front-end developer, what I’m working on overlaps with the stack.

Serverless moves the stack further to the right. We don’t even care what servers the code runs on anymore, it’s just server-side code using and creating APIs.

If I’m a front-end developer working in this world, I overlap in that I might even be using my JavaScript abilities to be writing these serverless functions and digesting the APIs.

Shawn Wang called Design Systems, TypeScript, Apollo GraphQL, and React a STAR app.

That’s… like… all front end stuff.

It seems to me the way we talk about the important technology that powers websites shifts more and more toward the front-end developer spectrum.

Let’s take a quick look at how serverless is expanding our front-end powers.

I made a website about serverless stuff because I think it’s cool and a big deal.


I consider JAMstack to essentially be a part of the serverless movement. Natch.

Javascript, APIs, and Markup. Although I would say, half tongue-in-cheek, that SHAMstack makes a bit more sense.

Here’s a perfect example of a JAMstack site that leverages serverless tech to do the things it needs to do.

It’s a site that lists upcoming conferences related to front-end development.

---
title: JAMstack_conf_ldn
url: 'https://jamstackconf.com/london/'
cocUrl: 'https://jamstackconf.com/london/code-of-conduct'
date: 2019-07-09T08:00:00.000Z
endDate: 2019-07-10T16:00:00.000Z
location: 'London, England'
byline: 'Learn how to design, develop, and deploy fast, modern web projects that run without web servers.'
---

Following the inaugural [JAMstack_conf in San Francisco](https://2018.jamstackconf.com/) in 2018, we're now also bringing an edition to London where we'll have talks about how to design, develop, and deploy fast, modern web projects that run without web servers.

Each conference is a Markdown file with Front Matter to describe meta data associated with the conference, like the city and dates.

I wasn’t deliberately trying to avoid a database, but this just seemed like the perfect kind of site to use a static site generator for and the data needs are so basic that flat Markdown files are a natural fit.

So each conference is a Markdown file, and there are some basic templates in Nunjucks, and Eleventy is wonderful for processing that kind of setup.

The site is a public repo on GitHub. That seems obvious, perhaps, but I think it’s hugely significant here.

It means that:

  1. The entire site is in the repo. To run it, you pull it down and run a single command.
  2. There is no fiddling with log ins, permissions, or credentials.
  3. It opens up the content of the site for public contributions, as well as the design and functionality. This has been huge.

The site being on GitHub means I could just leave it on GitHub Pages, but it’s like a 2-second process to put in on Netlify, which is a massive improvement. Here’s a few reasons:

  • Deploy previews. When I get a Pull Request, I can take a look at a live URL of what the site will be like with that Pull Request merged. Amazing.
  • I can activate Analytics on the site and get the most accurate possible numbers.
  • I can fiddle with my images programmatically.

There are a couple other big ones though…

With Netlify CMS, I get a UI to edit content right on the site itself. No code editing or Git involved at all once it’s set up.

I don’t even need Netlify to use Netlify CMS, but Netlify Identity makes the auth part a million times easier.

Check out this feature of the conference site. For each conference, you can click a button that reveals an email input form. Enter an email and submit, and the site fires off an email with details about the conference.

That’s a back-end thing. You can’t really send email with client-side technology alone. You can communicate with APIs that will send email, but even that requires API keys that need to be kept secret via back-end code.

const SparkPost = require('sparkpost');
const client = new SparkPost(process.env.SPARKPOST);

exports.handler = function(event, context, callback) {
  client.transmissions
    .send({
      content: {
        from: 'chris@css-tricks.com',
        subject: `Conference!`,
        html: `
          <html>
            <body>
              <h1>Hello, World!</h1>
            </body>
          </html>`
      },
      recipients: [{ address: email }]
    })
    .then(data => {
      callback(null, {
        statusCode: 200,
        body: `Message sent!`
      });
    });
};

I’ll use Sparkpost to send the email. They offer APIs to send email (that’s the entire point). They also offer a Node.js library to make it very easy. Ultimately just a few lines of code.

And that code? It’s JavaScript. I’m a JavaScript developer. This is not a stretch.

How do I run this?

That’s the meat and potatoes of serverless: cloud functions. I’m talking about AWS LambdaAzure FunctionsGoogle Cloud Functions, etc.

The Netlify version, which is AWS Lamba under the hood, is Netlify Functions. It’s stupid easy. You just toss your functions in a `/functions/` folder and run them by hitting a relative URL.

It feels very powerful to be able to build the entire site from front to back like that.

Let’s revisit the spectrum of technology again with this modern stuff in mind.

I don’t really have to care about operating systems and servers. Entire products can be built without having to care about these.

I probably don’t need to care much about databases either. It’s not that databases aren’t important, it’s just that my dealing with data is likely to happen through APIs. Perhaps a Headless CMS (e.g. Contentful). Perhaps a data storage tool that is designed to work through APIs (e.g. FaunaDB) or on-page libraries (e.g. Firestore).

So now I’m left with a spectrum of technology where I can work with all parts of it.

So I’m feeling pretty full-stacky already. But you take all that and add in:

  • I know Git
  • I can write tests
  • I design
  • I know about build processes
  • I care about performance
  • I can make sites accessible
  • I can set up a basic deploy pipeline

🎉
You’re Gosh
Danged Right
I’m a Full-Stack
Developer!

butttttt

The haystack of skills here is getting extremely large.

You can totally specialize. You probably will anyway. That’s good.

“Actual” unicorns, those people that are very good at every task across the entire spectrum of building websites: as rare as actual unicorns.

I’m also not trying to insinuate that back-end developers are becoming obsolete. In fact, the fact that building websites has gotten so damn complex, they are more important than ever.

Right now, I can open up the CodePen issue tracker and see 89 issues that I can’t solve alone. I need back-end developer help to dig into them and fix them. I can think of myself as full-stacky if I want, but I know that back-end stuff is where my rough edges are.

Things tend to end up a bit more like this.

Or in my case, more like this:

That’s not to make fun of anybody. I mean, maybe a little, but it draws a nice metaphor that is kinda perfect for this talk. We are the whole horse! The whole dragon! But we’ve got rough edges. So what.

It’s cool to see the tech around our job evolve to the point that we can reach our arms around the whole thing. It’s worthy of some concern when we feel like complication of web technology feels like it’s raising the barrier to entry. That happens sometimes and it’s not great. But it’s also worthy of cheer when web technology becomes simple enough that people can build things from start to finish all by themselves. That’s pretty cool.

While we’re being all productive and amazing, let’s just remember that doing a good job is everybodies job.

  • Good UX is everyone’s job
  • Good performance is everyone’s job
  • Good security is everyone’s job
  • Good accessibility is everyone’s job
  • Doing right by the people that use your website is everyone’s job

Even if you aren’t writing the code that directly affects any of those things, you care about them and fight for them to be handled well.

CodePen PRO (support your local artisanal craft software products with money)


The post ooooops I guess we’re* full-stack developers now appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.



source https://css-tricks.com/ooooops-i-guess-were-full-stack-developers-now/