Pasha Craydon

Recent Posts

  • How to write good javaScript fast

    Do you write most of your javaScript by making sure it runs first in the browser, adding debugger and console.log statements along the way. If so, you may be hindering the speed at which you write javaScript and the quality of your code.

    Tunnel code

    When you focus first on only whether or not your code runs without errors, the vision of your code is narrow.

    It can be easy to look at writing code as just a series of solving problems where each solution is a patchwork of unrelated pieces farmed from jumping from one stackoverflow search to the next.

    You may be tempted to believe this is the fastest way to write javaScript.

    How much time do you lose tracking down bugs you do not entirely understand, just to get the thing working.

    How often do you get frustrated because your code is broken and if you could just figure out why then you could move on to the next problem and also why is the browser taking so long to load.

    Greater vision

    I challenge you to set your gaze higher. Forget about whether or not your code actually works in the browser, that can come later. In fact, forget about the browser altogether at first.

    Step back and forget about the code at first too. Think about what you are trying to build, the whole system.

    What are the pieces that will make up the whole system? How do they relate? What would be the best way to organize them? Write it down.

    Now start writing your code.

    Small, incremental tests

    As you start writing your code, write tests to make sure it works. While you will write more code then you would if you had just started writing code using the browser, this approach will actually save you more time.

    The reason is that there are a lot of small, inconvenient things that even a genius could be forgiven for missing.

    That value you require from a function that needs to be a float, well turns out it returns a string.

    The closure that is supposed to return a new value, well turns out it just returns the same value over and over.

    These problems are much easier to discover and fix with tests then in the browser.

    I recommend many, short tests over a few, large tests. Nothing is too dumb to test. It is often the obvious, simple things which have bugs.

    How

    The test-driven development process has three steps;

    1. Write a test that fails.
    2. Write enough code to make the test pass.
    3. Refactor the code to tidy it up, ensure it is readable and maintainable. Run all the tests to ensure there are no regressions.

    The first step is important because you can accidentally write broken tests that always pass. In the second step, you focus primarily on solving the test. In the third step you focus on the quality of your code.

    Relax

    I believe that writing code should be fun or at least stress free. Using a test-driven development approach has a number of benefits that can improve your javaScript workflow and make writing it more enjoyable;

    1. Deeper understanding of your code. Writing tests will force you to think more carefully about what you are trying to achieve. As you become more clear about your goals and the architecture of your code, it will be easier to write code.

    2. Slow and steady wins the race. It sets the goal on the simple task of writing code in small chunks. You are more focused on the process rather then the end result and are more likely to write code that is deliberate and well-considered. You will gradually gain confidence in your code and the process and make fewer mistakes.

    3. Faster debugging. It will be easier to narrow the scope of bugs that show up in your code. You can be sure that you have fixed certain bugs and can look elsewhere. You can re-run your tests and have more confidence that changes you make to your code have not broken anything.

  • Debugging npm issues in webpack

    I setup webpack at my company so I am often asked to debug issues other devs have with npm packages. My debug strategy is pretty straightforward;

    • Ask for a stacktrace of the error
    • Follow it from the bottom to the top

    As an example, lets look at this stacktrace I was given.

      ```ERROR in ./~/language-tags/lib/index.js
      Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/index' in /heron-docker/node_modules/language-tags/lib
      resolve module language-subtag-registry/data/json/index in /heron-docker/node_modules/language-tags/lib
        looking for modules in /heron-docker/static/js
          /heron-docker/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules/sbo-nest/nest/static/js
          /heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules
          resolve 'file' or 'directory' data/json/index in /heron-docker/node_modules/language-subtag-registry
            resolve file
              /heron-docker/node_modules/language-subtag-registry/data/json/index.js doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/index doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/index.jsx doesn't exist
            resolve directory
              /heron-docker/node_modules/language-subtag-registry/data/json/index doesn't exist (directory default file)
              /heron-docker/node_modules/language-subtag-registry/data/json/index/package.json doesn't exist (directory description file)
      [/heron-docker/static/js/language-subtag-registry]
      [/heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/node_modules/language-subtag-registry/data/json/index.js]
      [/heron-docker/node_modules/language-subtag-registry/data/json/index]
      [/heron-docker/node_modules/language-subtag-registry/data/json/index.jsx]
       @ ./~/language-tags/lib/index.js 14:12-63
      ERROR in ./~/language-tags/lib/index.js
      Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/registry' in /heron-docker/node_modules/language-tags/lib
      resolve module language-subtag-registry/data/json/registry in /heron-docker/node_modules/language-tags/lib
        looking for modules in /heron-docker/static/js
          /heron-docker/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules/sbo-nest/nest/static/js
          /heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules
          resolve 'file' or 'directory' data/json/registry in /heron-docker/node_modules/language-subtag-registry
            resolve file
              /heron-docker/node_modules/language-subtag-registry/data/json/registry doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/registry.js doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/registry.jsx doesn't exist
            resolve directory
              /heron-docker/node_modules/language-subtag-registry/data/json/registry doesn't exist (directory default file)
              /heron-docker/node_modules/language-subtag-registry/data/json/registry/package.json doesn't exist (directory description file)
      [/heron-docker/static/js/language-subtag-registry]
      [/heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/node_modules/language-subtag-registry/data/json/registry]
      [/heron-docker/node_modules/language-subtag-registry/data/json/registry.js]
      [/heron-docker/node_modules/language-subtag-registry/data/json/registry.jsx]
       @ ./~/language-tags/lib/index.js 15:15-69
      ERROR in ./~/language-tags/lib/index.js
      Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/macrolanguage' in /heron-docker/node_modules/language-tags/lib
      resolve module language-subtag-registry/data/json/macrolanguage in /heron-docker/node_modules/language-tags/lib
        looking for modules in /heron-docker/static/js
          /heron-docker/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules/sbo-nest/nest/static/js
          /heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules
          resolve 'file' or 'directory' data/json/macrolanguage in /heron-docker/node_modules/language-subtag-registry
            resolve file
              /heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage.js doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage.jsx doesn't exist
            resolve directory
              /heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage doesn't exist (directory default file)
              /heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage/package.json doesn't exist (directory description file)
      [/heron-docker/static/js/language-subtag-registry]
      [/heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage]
      [/heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage.js]
      [/heron-docker/node_modules/language-subtag-registry/data/json/macrolanguage.jsx]
       @ ./~/language-tags/lib/index.js 108:6-65
      ERROR in ./~/language-tags/lib/index.js
      Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/meta' in /heron-docker/node_modules/language-tags/lib
      resolve module language-subtag-registry/data/json/meta in /heron-docker/node_modules/language-tags/lib
        looking for modules in /heron-docker/static/js
          /heron-docker/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules/sbo-nest/nest/static/js
          /heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules
          resolve 'file' or 'directory' data/json/meta in /heron-docker/node_modules/language-subtag-registry
            resolve file
              /heron-docker/node_modules/language-subtag-registry/data/json/meta doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/meta.js doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/meta.jsx doesn't exist
            resolve directory
              /heron-docker/node_modules/language-subtag-registry/data/json/meta doesn't exist (directory default file)
              /heron-docker/node_modules/language-subtag-registry/data/json/meta/package.json doesn't exist (directory description file)
      [/heron-docker/static/js/language-subtag-registry]
      [/heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js/language-subtag-registry]
      [/heron-docker/node_modules/language-subtag-registry/data/json/meta]
      [/heron-docker/node_modules/language-subtag-registry/data/json/meta.js]
      [/heron-docker/node_modules/language-subtag-registry/data/json/meta.jsx]
       @ ./~/language-tags/lib/index.js 141:8-58
      ERROR in ./~/language-tags/lib/Tag.js
      Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/index' in /heron-docker/node_modules/language-tags/lib
      resolve module language-subtag-registry/data/json/index in /heron-docker/node_modules/language-tags/lib
        looking for modules in /heron-docker/static/js
          /heron-docker/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules/sbo-nest/nest/static/js
          /heron-docker/node_modules/sbo-nest/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/node_modules
          resolve 'file' or 'directory' data/json/index in /heron-docker/node_modules/language-subtag-registry
            resolve file
              /heron-docker/node_modules/language-subtag-registry/data/json/index doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/index.js doesn't exist
              /heron-docker/node_modules/language-subtag-registry/data/json/index.jsx doesn't exist
            resolve directory
              /heron-docker/node_modules/language-subtag-registry/data/json/index/package.json doesn't exist (directory description file)
              /heron-docker/node_modules/language-subtag-registry/data/json/index doesn't exist (directory default file)
        looking for modules in /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/selenium/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js
          /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)
        looking for modules in /heron-docker/.tox/py27/lib/python2.7/site-packages/nest/static/js
          /

    This is a problem compiling javaScript with webpack in docker. Looking at the line second to the bottom I can see that it is complaining about a missing file.

      /heron-docker/.tox/qunit/lib/python2.7/site-packages/nest/static/js/language-subtag-registry doesn't exist (module as directory)

    Now looking at the first line in the stacktrace, I can see that the npm module language-tags can’t find the file index in language-subtag-registry.

      Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/index' in /heron-docker/node_modules/language-tags/lib resolve module language-subtag-registry/data/json/index in /heron-docker/node_modules/language-tags/lib

    Looking for dependency

    My first thought is that language-subtag-registry is a dependency in language-tags. I visit the npm page for language-tags and sure enough, it lists language-subtag-registry as a dependency.

    boom

    I ask the dev to install language-subtag-registry via npm.

    No luck. The dev gets the same error after installing the package via npm.

    Reinstall node-modules

    Ok, my next thought is that maybe the devs node-modules folder is out of sync for some reason and a good `ol cache clean + reinstall can solve this. I ask the dev to run this command;

      docker-compose run —rm web rm -rf node_modules && npm cache clear && npm install —production

    No luck. Dev gets the same error again.

    Dig into npm package code on github

    At this point I need to really dig into the code and see whats going on here.

    The very first line in the stacktrace (ERROR in ./~/language-tags/lib/index.js) tells me where the problem occurs so I visit the github page for language-tags and look for the index.js in the language-tags/lib directory.

    I see that the file is importing language-subtag-registry/data/json/index, which exactly matches the error on the second line of the stacktrace, Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/index' in /heron-docker/node_modules/language-tags/lib. There is a problem importing index.

      var index = require('language-subtag-registry/data/json/index');

    language-tags imports language-subtag-registry/data/json/index

    I realize index is a JSON file and webpack needs to look for the .json extension, otherwise, it will think it is a .js file. I ask the dev to include .json in the webpack extensions settings.

    This solves the problem.

  • Polling an AJAX request in React + Redux

    Problem; how to mount a React component that requires data from a webservice on many other parts of a page, many times (that also each make their own network requests) without duplicating requests.

    Polling to the rescue

      function displayMenu(collections) {
        ReactDOM.render(
          <MenuDropdown collectionsState={collections} />,
          document.getElementsByClassName('js-my-react-mount')[0]
        );
      }
    
      if (store.getState().collections.length) {
        displayMenu(store.getState().collections);
      }
      else if (store.getState().collections.isFetching) {
        const pollRequest = setInterval(() => {
          if (!store.getState().collections.isFetching) {
            displayMenu(store.getState().collections);
            clearInterval(pollRequest);
          }
        }, 100);
      }
      else {
        store.dispatch(getAllCollections())
          .then((response) => {
            displayMenu(response);
          });
      }

    This bit of code is embedded inside the ajax promises of other parts of the page. They fetch their own data then need this React component to fetch it’s own data and display it.

    • First it checks if the React component has data in it’s Redux store. If so, it uses that data to render itself.
    • If it finds that it has no data in storage but is in the process of requesting it, it will poll for the request to finish by looking at the ‘isFetching’ state in the Redux store, then use that stored data when the ‘isFetching’ state finishes.
    • Finally, if there is no data in storage and no request is happening, it will make a new request.

    -

    This provided a clean solution to a messy situation where I needed my React code to be “dummy proof”. That is, any other engineer could just embed it in any other parts of the site (often multiple times and at multiple points, inside the promises of other network requests) without knowing a thing about it and it would just work without making duplicate requests.

  • Testing axios with axios-mock-adapter

    I have been using axios to make network requests to a webservice from a React + Redux app. For testing the requests from the React app, I have been using axios-mock-adapter. The following is the action which makes the network request that I want to test.

      export function retrieveSingleCollection(collectionId) {
        return function (dispatch) {
          dispatch(retrieveCollectionRequest())
          return axios.get(`${c.WEBSERVICE_ENDPOINT}/collections/${collectionId}/`)
            .then(response => dispatch(retrieveSingleCollectionSuccess(response)))
            .catch(error => dispatch(collectionsRequestDidError(error)))
        }
      }

    It uses a Redux thunk to dispatch the request action retrieveCollectionRequest() before the network request happens.

    The next action, retrieveSingleCollectionSuccess(response)) happens after the request is successfully resolved. This action just passes the response from the request to my reducer which stores the response in my Redux store.

    I dispatch the action elsewhere in one of my components.

      store.dispatch(retrieveSingleCollection(collectionId))

    I want to test that when I dispatch this action it makes a network request to my webservice then stores the response in my Redux store if it is successful.

      import axios from 'axios'
      import MockAdapter from 'axios-mock-adapter'
      import expect from 'expect'
      import * as c from 'collections/utils/constants'
      import collectionsJSON from 'collections/tests/collections.json'
    
      const collections = collectionsJSON
      const collection = collections[0]
    
      describe('retrieveSingleCollection()', () => {
        it('should store the response from a successful GET request.', function () {
          const mock = new MockAdapter(axios)
          const collectionId = '123456789'
    
          mock.onGet(`${c.WEBSERVICE_ENDPOINT}/collections/${collectionId}/`).reply(200, collections[0])
    
          return store.dispatch(retrieveSingleCollection(collectionId))
            .then(() => {
              const { collection } = store.getState().collectionsState
              expect(collection).toEqual(collections[0])
            })
          })
        })
      })

    The MockAdapter replys to my GET request with a 200 status code so that the request resolves successfully and passes in a collection as the response. collectionsState is the object in my Redux store where I store the response. I test that the collection which shows up in the collectionsState matches the data in the mock.onGet() reply handler.

    Debugging

    I had a few issues with this test at first. The data that I used in the reply handler to respond to my request did not match the data that the request resolved. This resulted in my request receiving a 404 status code.

    I also wanted to see what my request was doing when the mock adapter handles it. To accomplish this I can use this bit of code to inspect it;

      mock.onGet(`${c.WEBSERVICE_ENDPOINT}/collections/${collectionId}/`).reply(function(config) {
        console.log(JSON.stringify(config, null, 2))
        return [200]
      });

    The github repo for axios-mock-adapter has some nice tests itself which helped me resolve the bugs I ran into.

  • Objects and Arrays in ES6

    ES6 has some new tools for dealing with objects and arrays. These include destructuring assignment, enhanced object literals and the spread operator.

    Destructuring Assignment

    Object destructuring

    Object destructuring allows us to pull in specific attributes from an object and scope it locally. Consider the following cat object (also known as an object literal).

      var cat = {
        color: "grey",
        gender: "female",
        age: 2
      }

    We can pull out attributes and create local variables out of them. These variables can be changed without changing the original cat object.

      var { color, gender } = cat
      color = "black"
      gender = "male"
    
      console.log(color) // "black"
      console.log(gender) // "male"
    
      console.log(cat.color) // "grey"
      console.log(cat.gender) // "female"

    Just as object literals allow us to create multiple properties at once, we can destructure them, by extracting multiple properties at once. This is considered an object pattern.

      const { color: c, gender: g, age: a } = cat
    
      console.log(c) // "grey"
      console.log(g) // "female"
      console.log(a) // 2

    This is the same thing as doing the following in ES5.

      var c = cat.color
      var g = cat.gender
      var a = cat.age

    We can also destructure objects that are arguments in functions. Consider the following function that logs the cats gender.

      var getColorOfCat = ({color}) => {
        console.log(`The color is ${color}.`)
      }

    This is the same thing as doing the following;

      var getColorOfCat = cat => {
        console.log(`The color is ${cat.color}.`)
      }

    Instead of using dot notation to access the color of the cat object literal within the function, we can destructure the cat object and pull out it’s color attribute.

    This is a more declarative approach because we signal that we are only using the color attribute from the cat object.

    Array destructuring

    In ES6 we can pull out values from arrays and scope them to local variables.

      const [firstAnimal] = ["cat", "dog", "lion", "horse"]
      console.log(firstAnimal) // "cat"

    You can pass over values in arrays using commas to get to specific indexes.

      const [,,thirdAnimal,] = ["cat", "dog", "lion", "horse"]
      console.log(firstAnimal) // "lion"

    You can get all the remaining elements of an array using the rest operator. This operator looks like the spread operator in ES6 (…) except it is used inside function calls and arrays.

      const [firstAnimal,, ...remaining] = ["cat", "dog", "lion", "horse"]
      console.log(firstAnimal, ...remaining) // "cat", ["lion", "horse"]

    You can use array destructuring to swap values in variables.

      var pet1 = "cat", pet2 = "dog"
      [pet1, pet2] = [pet2, pet1]
      console.log(pet1, pet2) // "dog", "cat"

    This is the same as the following in ES5.

      var pet1 = "cat",
          pet2 = "dog"
    
      var pets = [pet2, pet1]
    
      pet1 = pets[0]
      pet2 = pets[1]

    Enhanced Object Literals

    Object literal enhancement allows us to create objects from variables that are in scope.

      var color = "grey"
      var age = 2
    
      var cat = { color, age }
    
      console.log(cat); // { color: "grey", age: 2 }

    color and age are now attributes in the new cat object. Object literal enhancement can also include functions.

      var color = "grey"
      var age = 2
      var getAttributes = function () {
        console.log(`Color is: ${this.color}, age is: ${this.age}`)
      }
    
      var cat = { color, age, getAttributes };
      cat.getAttributes(); // Color is: grey, age is: 2

    We can write a shorthand version of this without the function operator.

      var color = "grey"
      var age = 2
      var getAttributes() {
        console.log(`Color is: ${this.color}, age is: ${this.age}`)
      }

    We can write shorthand for objects whose properties are initialised by variables of the same name.

      var cat = {
        color,
        age,
        getAttributes() {
          console.log(`Color is: ${this.color}, age is: ${this.age}`)
        }
      }

    This is the same as writing the following in ES5.

      var cat = {
        color: color,
        age: age,
        getAttributes: function getAttributes() {
          console.log("Color is: " + this.color + ", age is: " + this.age)
        }
      };

    This could be quite handy in functions or modules that export objects.

      var cat = (age, gender) => age({age, gender})

    This is the same as writing the following in ES5.

      var cat = function cat(age, gender) {
        return { age: age, gender: gender }
      };

    Spread Operator

    The spread operator is three dots (…) and allows us to do powerful new things with arrays.

    Copy

    We can copy arrays.

      var animals = ["cat", "dog", "lion"]
      var animalsCopy = [...animals]

    The array copy animalsCopy is separate from the animals array and is unaffected by changes to animalsCopy.

      animalsCopy.push("zebra");
      console.log(animals) // ["cat", "dog", "lion"]
      console.log(animalsCopy) // ["cat", "dog", "lion", "zebra"]

    This is a great way to deal with immutable data that uses arrays.

    Function arguments

    The spread operator can be used in functions to collect arguments as an array. Consider this use of appply.

      function animals(x, y, z) { }
      var args = ["cat", "dog", "lion"]
      animals.apply(null, args)

    This can be called in ES6 using the spread operator like this;

      animals.apply(...args)

    Inversely, we can create a function that accepts n number of arguments as an array then uses the spread operator to operate on them.

      function animals(...args) {
        var [firstAnimal, ...remaining] = args
        console.log(firstAnimal)
        console.log(remaining.reverse())
      }
    
      animals("cat", "dog", "zebra"); // "cat" // ["zebra", "dog"]

    -

    I highly recommend using the Babel Repl to play around with ES6 code.