01. What is prototypical inheritance and how useful is it?
This is useful when making objects that share the same values and operations. By having these live on a prototype, only one copy of them needs to exist, making projects memory efficient.
Prototypes can be added to objects on creation using Object.create(), or after with Object.setPrototypeOf(). ES2015 has a 'class' keyword that, when used with 'extends', will use that value as its prototype.
A common way to assist users is to provide useful focus management. A calendar, for example, should be able to cycle through days using the arrow keys, with up and down keys skipping a week at a time. It is just a case of listening for keyboard events while focus is within that component.
03. What is event bubbling and how is it different to event capturing?
Both event capturing and bubbling are both part of a process called 'event propagation', where browsers respond to events happening on the page. Older browsers used to do one or the other, but nowadays they all do both.
The first phase – the capturing phase – occurs as soon as an event happens. It starts at the topmost level, which is either 'document' or 'window' depending on the event. From there it goes through and whatever lives inside it until it reaches the element the event occurred within.
The second phase – the bubbling phase – then happens. It repeats the process but in reverse, beginning with the element the event was triggered by and 'bubbling' to the topmost element. When adding event listeners, this is the default.
04. How does event delegation improve code on sites with lots of interactive elements?
Websites often have lots of dynamic content regularly changing on the page. If these elements need to be interactive, these would need some kind of event listener picking up those interactions. If each element required its own listener, this would clutter up code and increase what the browser needs to keep track of.
Event delegation is a technique that uses event bubbling to its advantage. By adding a listener to a parent element, developers are alerted to events for any of its children.
Inside the event callback, the original target of that event will always be the 'target', which can be used to decide what to do next. For example, a data attribute could hold an ID to reference an object property.
05. What are closures and how can they be useful in organising code?
Calling outer() will show 'I love Web Designer!', but if 'shout' or 'x' are referenced outside of outer(), both will be undefined. A closure is a combination of a function alongside its lexical environment. In this case, the closure is the 'outer' function.
These are useful when creating multiple components, as anything declared inside will not affect others. They can be used to create private functions and variables in a similar way to other object-oriented languages like Python. The module pattern uses closures extensively to provide structured ways for modules to interact.
06. What does 'use strict' mean at the top of a block of code?
Above we are accessing a variable that doesn't exist. Outside of strict mode, this would instead add myVar to the global scope, which if we are not careful could completely overwrite functionality defined earlier in the script. In strict mode, this throws an error and halts any destruction. ES2015 modules are in strict mode by default, but in closures created with functions, 'use strict' can be applied at a function level as well as an entire file.
The browser then makes a second pass to execute the code, knowing where these functions and variables apply. As a block is executed, its function and variable declarations are 'hoisted' to the top of the block.
In this example, we are able to use the 'welcome' function as it is hoisted up to the top of the script.
08. What is the difference between an arrow function and a regular function?
ES2015 provided lots of changes and one to make an quick impact was arrow functions.
The key difference, despite being shorter to write, is that arrow functions do not create their own value for 'this'. They will instead use the value of the enclosing block. In the above example, this would log out 1, 2, 3, etc once every second. With a regular function, this.x would be undefined, so it would log NaN. The body of an arrow function is assumed to be the return value from it. This makes them useful for promises, where values are passed through. Regular functions must explicitly return a value, or will return undefined.
09. Where should I use the 'let' and 'const' keywords instead of 'var'?
Another fundamental change with ES2015 was the introduction of 'let' and 'const' as alternative ways to define variables. Variables declared in this way are limited to the block they were defined in. This provides more certainty that values created inside different blocks won't interfere with code outside.
If the value of the variable will not change, use 'const' instead of 'let'. Errors are thrown when attempting to redefine a constant. Objects and array variables can still change internally, but are not completely replaced.
Both 'let' and 'const' do not get hoisted like 'var', so cannot be referenced before they are initialised. Between the start of the block and the initialisation is known as the 'temporal dead zone' and can often be a source of confusion.
10. What is functional programming and how is it different?
It is an alternative way of creating programs by passing application state exclusively through functions. By avoiding side effects, it's possible to develop code that's easy to understand.
All work must be done inside 'pure' functions. These are functions that are not affected by any data outside the scope of that function. In other words, if the same values are supplied to the function, the same result will always be returned.
This also means that there can be no shared state between functions. Any state within an application that needs to be used should be passed as a parameter to a function, rather than accessed directly from it.
Finally, code should avoid changing values after they are made. For example, each change of an object should return a copy of that object with the value changed. This is to avoid side effects, which can cause bugs and make code harder to test.
11. How can I use JS to improve the performance of my site?
Keep it passive
Jagged scrolling is a clear sign something is up. In some cases, the browser is being forced to wait because of listeners applied to the page. Events such as 'wheel' or 'touchmove' are able to cancel scrolling, so the page has to wait until the event has completed before the default scrolling behaviour can begin.
This can cause jerky and inconsistent scrolling, which makes for a poor user experience.
To get around this, pass an object as the third parameter when adding an event listener. By marking the event as passive, the browser can assume scrolling will not be affected, so it can start immediately.
This third parameter replaces the 'useCapture' option in older browsers, so it is important to use feature detection when making use of this type of listener. To intentionally disable scrolling, applying 'touch-action: none' in CSS will help in more browsers.
Events like scrolling and resizing fire as quickly as they can to make sure whatever is listening can stay up to date. If something resource intensive is happening on each event, this can quickly grind a page to a halt.
Debouncing is a technique that throttles how often the callback to one of these events is called. The implementation of a debounce function and how often the function gets called will vary by project, but reducing the events to five times a second, for example, will see an instant improvement on the page.
Focus on the viewport
A common use of the scroll event is to detect when an element comes into view on the page. Even with debouncing, calling getBoundingClientRect() requires the browser to reanalyse the layout of the entire page. There is a new browser API called IntersectionObserver, which reports on the visibility of observed elements by calling a function whenever they enter or exit the viewport. For infinite scrolling sites, this can be used as a flag to remove or recycle older views.
IntersectionObserver is available in all the latest browsers except Safari. It's worth using this API and falling back to older techniques, as the difference is vastly noticeable.
Separate expensive work
If you know a process is going to take a long time to run, it is a good idea to put it inside a web worker. These are scripts that run on a separate thread, which leaves the user interface running smoothly. Scripts can talk to each other through a special message method. Web workers don’t have access to the DOM and some properties on the window object, so these messages can be used to pass the necessary information.
Just because a block of code runs, however, does not mean it's future-proof. Will your codebase still make sense a few years later?
Avoid spaghetti code
When first creating a project, it can be tempting to write everything together. While that makes it explicit what each block of code is doing, it also means all behaviour is coupled together as well. If another area of the project needs that functionality, it will either be copied or rewritten. If bugs are found or a new feature is added, each version will need updating individually, which can be a time-consuming process.
By keeping code modular, functionality can be included where it's needed. Once it's been written, any changes are instantly available. Techniques such as the module pattern can be implemented without affecting the rest of the project, which makes them easier to implement.
Be framework agnostic
Many modern frameworks, like React or Polymer, encourage developers to keep things modular through the creation of components. Each has its own way of communicating between other parts of the project.
What happens when the next best framework arrives? Switching – or even just updating – frameworks can be a laborious task. Finding new ways for these old components to work together can eat up precious time.
Once modules are written, keep them clean. Anybody reading through them should understand its functionality to speed up debugging.
Self-documenting code is a powerful tool to future-proof your code. Using descriptive names for variables in iterators, for example, can be clearer to read than generic names like 'i'.
Most importantly, be consistent. By sticking to a uniform style, all code becomes readable. Use a style guide to define how code should look and use tools like ESLint to enforce it.
Work at scale
Being readable also extends to project structure. Without one, things can get overwhelming very quickly.
When first starting out, having files in one directory keeps things simple. When scripts import modules, there is no confusion as to where they are located.
As projects age, large collections of files can get lost in the masses. Keep a structure that scales well, such as storing all modules that deal with users in a 'users' directory. Ultimately, the best structure will depend on the project. For a single-page app, keeping model, view and controller logic separate is a must.
Keep things testable
Frameworks like React encourage creating small, reusable components. Even with a scalable project structure, it can be hard to be sure they all still work correctly. By writing tests, projects can be deployed with confidence.
Modules need to be written in a way that means they can be tested in isolation. This means having as few external dependencies as possible and not relying on global state.
There's far more to project testing, such as integration and functional testing. These should cover as much of a project as possible to keep code working long into the future.
Language of tomorrow
The best way to future-proof code, however, is by writing it in the syntax of the future. While this might sound like a step into the unknown, there are tools that make it as easy as possible.
Eventually, projects will be able to skip the transpilation step altogether. But for now, they are a must to keep code future-proofed.
To improve your web design skills further, head to our conference Generate and experience a packed schedule of industry-leading speakers, workshops and valuable networking opportunities. Don't miss it! Get your Generate ticket now.