How to not use jQuery

Callum Macrae | @callumacrae

@callumacrae

JavaScript Developer at Lost My Name

Author of "Learning from jQuery"

What even is jQuery?

What even is jQuery?

var $uncle = $('.foo').parent().next();
$uncle.css('background-color', 'red');

What even is jQuery?

$('.foo').on('click', function (e) {
    e.preventDefault();

    $(this).html('This has been clicked!');
});

What even is jQuery?

$.get('/user.json', { name: 'Callum' })
    .then(function (data) {
        $('.callum').html('Callum\'s data: ' + data);
    })
    .catch(function (err) {
        $('.callum').text('AJAX request failed :-(');
        console.error(err);
    });

Why not jQuery?

Performance

Kitchen sink

It does a lot you probably don't need.

What about ES2015?

What about ES2015?

let + const

if (true) {
    var first = 'inside';
    let second = 'inside';

    console.log(first, second); // inside, inside
}

console.log(first, second); // inside, undefined

let + const

// Doesn't work as expected
for (var i = 0; i < 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 100);
}

// Works as expected
for (var i = 0; i < 10; i++) {
    let j = i;
    setTimeout(function () {
        console.log(j);
    }, 100);
}

Destructuring

let [a, b, c] = [1, 2, 3];
let [match, name] = /name: (\S+)/.exec('name: Callum');
let [, name] = /name: (\S+)/.exec('name: Callum');

Destructuring

let { one, two, three } = { one: 'Un', two: 'Deux', three: 'Trois' };
console.log(three); // Trois

// Destructuring with function arguments
function logUser({ name, colour }) {
    console.log(name + ' likes ' + colour);
}
logUser({ name: 'Callum', colour: 'orange' });

Destructuring

let { one, two, three } = { one: 'Un', two: 'Deux', three: 'Trois' };
console.log(three); // Trois

// Destructuring with function arguments
function logUser({ name, colour }) {
    console.log(name + ' likes ' + colour);
}
logUser({ name: 'Callum', colour: 'orange' });
// { name } == { name: name }

Default arguments

function log(msg, level = 'log') {
    console[level](msg);
}

log('console.log()');
log('console.error()', 'error');

Default arguments + destructuring

function greet({ greeting = 'hello', greetee = 'world' }) {
  console.log(greeting + ', ' + greetee + '!');
}

greet({ greetee: 'ProgSCon London' });

Replacing jQuery

DOM: Selecting

// jQuery
$('.user .user__name')

// JavaScript
document.querySelectorAll('.user .user__name');

DOM: Iterating with forEach

// jQuery
$('.user').each(function () {
    console.log(this);
});

// JavaScript
let users = document.querySelectorAll('.user');
Array.from(users).forEach(function (user) {
    console.log(user);
});

DOM: Iterating with an iterator

// jQuery
$('.user').each(function () {
    console.log(this);
});

// JavaScript
let users = document.querySelectorAll('.user');
for (let user of users) {
    console.log(user);
}

DOM: Selecting children

// jQuery
$('.user').find('.user__tag');

// JavaScript
let users = document.querySelectorAll('.user');
let tags = [];

for (let user of users) {
    tags.push(...user.querySelectorAll('.user__tag'));
}

DOM: Selecting children

// jQuery
$('.user').find('.user__tag');

// JavaScript
let users = document.querySelectorAll('.user');
let tags = [];

for (let user of users) {
    let tags = user.querySelectorAll('.user__tag');
    tags.push(tags[0], tags[1], ...);
}

DOM: .next() / .previous() / .parent()

let $user = $('.user');
let user = document.querySelector('.user');

$user.next();
user.nextElementSibling;

$user.previous();
user.previousElementSibling;

$user.parent();
user.parentNode;

DOM: Attributes

$user.attr('aria-live');
user.getAttribute('aria-live');

$user.attr('aria-live', 'Your username is invalid');
user.setAttribute('aria-live', 'Your username is invalid');

// Data attributes have a special API
user.dataset.userName = 'Callum';
user.dataset.userName; // Callum

DOM: .html() / .text() / .val()

let $user = $('.user');
let user = document.querySelector('.user');

$user.html();
user.innerHTML;

$user.text();
user.textContent; // Not innerText!

$('.some-input').val();
document.querySelector('.some-input').value;

DOM: Classes

let $user = $('.user');
let user = document.querySelector('.user');

$user.addClass('user--admin');
user.classList.add('user--admin');

$user.removeClass('user--admin');
user.classList.remove('user--admin');

$user.toggleClass('user--admin');
user.classList.toggle('user--admin');

$user.hasClass('user--admin');
user.classList.contains('user--admin');

DOM: Styling

let $user = $('.user');
let user = document.querySelector('user');

$user.css('background-color', 'orange');
user.style.backgroundColor = 'orange';

$user.css('background-color');
getComputedStyle(user).backgroundColor;

Event handling

$('.user').on('click', function () {
    $(this).addClass('clicked');
});

let user = document.querySelector('user');
user.addEventListener('click', function () {
    user.classList.add('clicked');
});

Event handling

$(document).on('click', '.user', function () {
    $(this).addClass('clicked');
});

document.addEventListener('click', function (e) {
    if (e.target.matches('.user')) {
        e.target.classList.add('clicked');
    }
});

Event handling

$(document).on('click', '.user', function () {
    $(this).addClass('clicked');
});

function live(selector, event, cb) {
    document.addEventListener(event, function (e) {
        if (e.target.matches(selector)) {
            cb(e);
        }
    });
}

live('.user', 'click', function (e) {
    e.target.classList.add('clicked');
});

$.Deferred()

var deferred = $.Deferred();

$('.yes').on('click', function () {
    deferred.resolve();
});

$('.no').on('click', function () {
    deferred.reject();
});

deferred
    .then(function () {
        console.log('success');
    })
    .fail(function () {
        console.log('fail');
    });

Promises

var promise = new Promise(function (resolve, reject) {
    $('.yes').on('click', resolve);
    $('.no').on('click', reject);
});

promise
    .then(() => console.log('success'))
    .catch(() => console.log('fail'));

$.ajax()

$.get('/api')
    .then(function (data) {
        console.log(data);
    })
    .catch(function (err) {
        console.error(err);
    });

XMLHttpRequest 😔

var request = new XMLHttpRequest();
request.open('GET', '/api');

request.onload = function () {
    var data = this.response;
    if (this.status >= 200 && this.status < 400) {
        console.log(data);
    } else {
        console.error(data);
    }
};

request.onerror = function (err) {
    console.error(err);
};

request.send();

Fetch

fetch('/api')
    .then((res) => res.json());
    .then((data) => console.log(data))
    .catch((err) => console.error(err));

GitHub polyfill for fetch()

Post form data

// jQuery
$.post('/api', $('.my-form').serialize());

// JavaScript
let form = document.querySelector('.my-form');
fetch('/api', {
    method: 'post',
    body: new FormData(form)
});

Array helpers

// Each
$.each(['a', 'b', 'c'], function (i, letter) {
    console.log(letter);
});

['a', 'b', 'c'].forEach(function (letter) {
    console.log(letter);
});

// Map
$.map(['a', 'b', 'c'], function (i, letter) {
    return letter.charCodeAt(0);
});

['a', 'b', 'c'].map(function (letter) {
    return letter.charCodeAt(0);
});

MORE array helpers

// ECMAScript 5
Array.prototype.reduce()
Array.prototype.filter()
Array.prototype.every()
Array.prototype.some()

// ECMAScript 6
Array.from() // second argument!
Array.of()
Array.prototype.entries()
Array.prototype.keys()
Array.prototype.values()
Array.prototype.find()
Array.prototype.findIndex()
Array.prototype.fill()

JSON

$.parseJSON(data)

 

ಠ_ಠ

Merging objects

// jQuery
function myPlugin(options) {
    options = $.extend({
        color: 'red',
        size: 3
    }, options);
}

// JavaScript
function myPlugin(options) {
    options = Object.assign({
        color: 'red',
        size: 3
    }, options);
}

Cloning objects

$.extend({}, objectToClone);

Object.assign({}, objectToClone);

Animation

Babel

Is jQuery necessary?

Old versions of Internet Explorer

Old versions of Internet Explorer

Low barrier to entry

A bajillion libraries

Browser quirks

Rick Waldron's list of 110 quirks fixed by jQuery

More ES2015

Template literals

let name = 'Callum';

let oldGreeting = 'Hello ' + name + '!\n\n'
    + 'Welcome to Earth.';

let greeting = `Hello ${name}!

Welcome to Earth.`;

Spread operator

function logSecondArgument(one, two, three) {
    console.log(two);
}

let numbers = [1, 2, 3];

logSecondArgument(...numbers); // 2

// Equivalent to:
logSecondArgument(1, 2, 3);

// Before ES2015:
logSecondArgument.apply(this, numbers); // 2

Spread operator

let numbers = [1, 2, 3];

[...numbers, 4, 5, 6]; // [1, 2, 3, 4, 5, 6]

Spread operator

let [one, two, ...more] = [1, 2, 3, 4, 5];

console.log(more); // [3, 4, 5]

Generators

function* count() {
    yield 1;
    yield 2;
    yield 3;
}

var counter = count();

counter.next(); // {value: 1, done: false}
counter.next(); // {value: 2, done: false}
counter.next(); // {value: 3, done: false}

counter.next(); // {done: true, value: undefined}

Generators

function* range(start, end) {
    for (let i = start; i <= end; i++) {
        yield i;
    }
}

for (let i of range(5, 8)) {
    console.log(i);
}

Read more on my blog

Classes

class Developer {
    constructor(name, languages) {
        this.name = name;
        this.languages = languages;
    }
    intro() {
        console.log(`I'm ${this.name} and I write ${this.languages.join(',')}.`);
    }
}

class JavaScriptDeveloper extends Developer {
    constructor(name, languages = ['JavaScript']) {
        super(name, languages);
    }
}

let callum = new JavaScriptDeveloper('Callum');
callum.intro(); // I'm Callum and I write JavaScript.

Classes

class Person {
    constructor(name, age) {
        this._age = age;
    }

    get age() { return this._age; }

    set age(newAge) {
        if (typeof newAge !== 'number') {
            throw new Error('Age must be a number');
        }
        this._age = age;
    }
}

let bob = new Person('Bob', 49);
bob.age; // 49
bob.age = 'ten'; // Age must be a number

Classes

class Number {
    constructor(value) {
        this.value = value;
    }

    static max(...nums) {
        return nums.reduce(function (acc, curr) {
            return (curr.value > acc.value) ? curr : acc;
        });
    }
}

let seven = new Number(7);
let eight = new Number(8);
let zero = new Number(0);

seven.max === undefined;
Number.max(seven, eight, zero) === eight;

ES2016?!

How to not use jQuery

Thanks!

Callum Macrae | @callumacrae

@callumacrae