You might not need JavaScript classes

@soyguijarro

👨‍💻  Web engineer from Madrid

💼  Dev tools at Undefined Labs

🤩  JavaScript, React, Web APIs

🗣️  International tech speaker

🎓  Adalab bootcamp volunteer

💖  Community and inclusion

Ramón
Guijarro

@soyguijarro

Why we got classes

Issues and pitfalls

Alternative patterns

Recent case studies

Why we
got classes

ECMAScript 2015

Object oriented programming

Prototype vs Class

class Counter {
  constructor(initValue) {
    this.initValue = initValue;
    this.value = initValue;
  }

  inc() {
    this.value++;
  }

  reset() {
    this.value = this.initValue;
  }
}
function Counter(initValue) {
  this.initValue = initValue;
  this.value = initValue;
}

Counter.prototype.inc = function() {
  this.value++;
};

Counter.prototype.reset = function() {
  this.value = this.initValue;
};
class StepCounter extends Counter {
  constructor(initValue, step) {
    super(initValue);
    this.step = step;
  }

  inc() {
    this.value += this.step;
  }
}
function StepCounter(initValue, step) {
  Counter.call(this, initValue);
  this.step = step;
}

StepCounter.prototype =
  Object.create(Counter.prototype);

StepCounter.prototype.inc = function() {
  this.value += this.step;
};

StepCounter.prototype.constructor =
  StepCounter;

Improvement over prototype

Familiar for new people

Issues and
pitfalls

False expectations

class Counter {
  constructor(initValue) {
    this.initValue = initValue;
    this.value = initValue;
  }

  inc() {
    this.value++;
  }

  reset() {
    this.value = this.initValue;
  }
}
const counter = new Counter(0);
counter.inc();
console.log(counter.value); // 1

Counter.prototype.inc = function() {
  this.value--;
};
counter.inc();
console.log(counter.value); // 0

Class defines an instantiable type

Prototype is a living instance

living

const counter = new Counter(0);
counter.inc();
console.log(counter.value); // 1

Counter.prototype.inc = function() {
  this.value--;
};
counter.inc();
console.log(counter.value); // 0

Lack of features

Context binding

class Button {
  constructor(clickHandler) {
    this.click = clickHandler;
  }
}

const counter = new Counter(0);
const button = new Button(counter.inc);

button.click();

console.log(counter.value); // 0 (not 1)
console.log(button.value); // NaN (not undefined)
const button = new Button(counter.inc.bind(counter));

const button = new Button(() => {
  counter.inc()
});
class Counter {
  constructor(initValue) {
    this.initValue = initValue;
    this.value = initValue;
    this.inc = this.inc.bind(this);
  }

  inc() {
    this.value++;
  }

  reset() {
    this.value = this.initValue;
  }
}
class Counter {
  constructor(initValue) {
    this.initValue = initValue;
    this.value = initValue;
  }

  inc = () => {
    this.value++;
  }

  reset() {
    this.value = this.initValue;
  }
}
class Counter {
  constructor(initValue) {
    this.initValue = initValue;
    this.value = initValue;
  }

  inc = () => {
    this.value++;
  }

  reset() {
    this.value = this.initValue;
  }
}

console.log(Counter.prototype);

Harder to optimize

Alternative
patterns

Plain functions

Closures

function createCounter(initValue) {
  let value = initValue;

  return {
    inc() {
      value++;
    },
    reset() {
      value = initValue;
    },
    getValue() {
      return value;
    }
  };
}
function createButton(click) {
  return {
    click
  };
}

const counter = createCounter();
const button = createButton(counter.inc);

button.click();

console.log(counter.getValue()); // 1
console.log(button.value); // undefined
class MovieApi {
  constructor(token) {
    this.token = token;
  }
  
  addFavorite(user, movie) {
    fetch(`/users/${user}/favorites/${movie}`, {
      method: 'POST',
      headers: { 'X-Auth-Token': this.token },
    });
  }
}

const api = new MovieApi('abc123');
api.addFavorite('johndoe', '7321');
function createMovieApi(token) {
  return {
    addFavorite(user, movie) {
      fetch(`/users/${user}/favorites/${movie}`, {
        method: 'POST',
        headers: { 'X-Auth-Token': token },
      });
    }
  };
}

const api = createMovieApi('abc123');
api.addFavorite('johndoe', '7321');

Object composition

function createBaseCounter(initValue) {
  let value = initValue;

  return {
    reset() {
      value = initValue;
    },
    setValue(newValue) {
      value = newValue;
    },
    getValue() {
      return value;
    }
  };
}
function createStepCounter(initValue, step) {
  const {
    setValue, ...rest
  } = createBaseCounter(initValue);

  return {
    ...rest,
    inc() {
      setValue(rest.getValue() + step);
    }
  };
}
function createCounter(initValue) {
  const {
    setValue, ...rest
  } = createBaseCounter(initValue);
  return {
    ...rest,
    inc() {
      setValue(rest.getValue() + 1)
    }
  };
}

function createCounter(initValue) {
  return createStepCounter(initValue, 1);
}

Function composition

function withLogging(method) {
  return function(...args) {
    console.log(`Called with ${args.join(', ')}`)
    method(...args)
  }
}

const myDecoratedFn = withLogging(myFn)

Modules

let instance;

function createSingletonCounter(initValue) {
  if (!instance) {
    instance = createCounter(initValue);
  }

  return instance;
}

export default createSingletonCounter;

Recent
case studies

React hooks

function Counter({ initValue }) {
  const [counter, setCounter] = React.useState(initValue);
  
  function inc() {
    setCounter(counter + 1);
  }
  
  return (
    <div>
      <h1>{counter}</h1>
      <button onClick={inc}>Increment</button>
    </div>
  );
}

Vue Composition API

<template>
  <div>
   <h1>{state.counter}</h1>
   <button @click="inc">Increment</button>
  </div>
</template>

<script>
const Counter = {
  setup({ initValue }) {
    const state = Vue.reactive({ counter: initValue });
    const inc = () => {
      state.count++
    };
    return { state, increment };
  }
}
</script>

Better logic organization

Less convoluted patterns

More type friendly

Important
takeaways

Adjust your expectations

Watch out for context issues

Consider using functions

@soyguijarro

Thank you!

You might not need JavaScript classes

By Ramón Guijarro

You might not need JavaScript classes

  • 1,894