Hands-on Machine Learning with JavaScript

4 (3 reviews total)
By Burak Kanber
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Exploring the Potential of JavaScript

About this book

In over 20 years of existence, JavaScript has been pushing beyond the boundaries of web evolution with proven existence on servers, embedded devices, Smart TVs, IoT, Smart Cars, and more. Today, with the added advantage of machine learning research and support for JS libraries, JavaScript makes your browsers smarter than ever with the ability to learn patterns and reproduce them to become a part of innovative products and applications.

Hands-on Machine Learning with JavaScript presents various avenues of machine learning in a practical and objective way, and helps implement them using the JavaScript language. Predicting behaviors, analyzing feelings, grouping data, and building neural models are some of the skills you will build from this book. You will learn how to train your machine learning models and work with different kinds of data. During this journey, you will come across use cases such as face detection, spam filtering, recommendation systems, character recognition, and more. Moreover, you will learn how to work with deep neural networks and guide your applications to gain insights from data.

By the end of this book, you'll have gained hands-on knowledge on evaluating and implementing the right model, along with choosing from different JS libraries, such as NaturalNode, brain, harthur, classifier, and many more to design smarter applications.

Publication date:
May 2018
Publisher
Packt
Pages
356
ISBN
9781788998246

 

Chapter 1. Exploring the Potential of JavaScript

We will cover the following topics in this chapter:

  • Why JavaScript?
  • Why machine learning, why now?
  • Advantages and challenges of JavaScript
  • The CommonJS initiative
  • Node.js
  • TypeScript language
  • Improvements in ES6
  • Preparing the development environment
 

Why JavaScript?


I started writing about machine learning (ML) in JavaScript in 2010. At the time, Node.js was brand new and JavaScript was just beginning to come into its own as a language. For much of the history of the internet, JavaScript had been seen as a toy language, used to create simple dynamic interactions on web pages.

The perception of JavaScript began to change in 2005 with the release of the Prototype JavaScript Framework, which aimed to simplify AJAX requests and help developers deal with cross-browser XMLHttpRequest. The Prototype Framework also introduced the familiar dollar function as an alias for `document.getElementById`: `$(“myId”)`, for instance.

One year later, John Resig released the wildly popular jQuery library. At the time of writing, w3techs.com reports that jQuery is used on 96% of websites whose JavaScript libraries are known to them (which accounts for 73% of all websites). jQuery worked to make common JavaScript operations cross-browser compatible and easy to achieve, bringing important tools such as AJAX requests, Document Object Model (DOM) traversal and manipulation, and animations to web developers everywhere.

Then, in 2008, the Chrome web browser and the Chrome V8 JavaScript engine were released. Chrome and V8 introduced a marked performance improvement over older browsers: JavaScript was now fast, owing in large part to the V8 engine's innovative just-in-time compiler that builds machine code directly from JavaScript.

JavaScript became more popular as jQuery and Chrome took over the web. Developers historically have never loved JavaScript as a programming language, but with jQuery in the picture, running on a fast and modern browser, it became clear that JavaScript was an underutilized tool and capable of much more than it had been used for previously.

In 2009, the JavaScript developer community decided to break JavaScript free from the web browser environment. The CommonJS initiative was launched early that year, and Node.js followed after a few months. CommonJS modules' goal was to develop a standard library and improve the ecosystem for JavaScript so that it could be used outside of the browser environment. As part of this effort, CommonJS standardized a module-loading interface that allowed developers to build libraries that they could share with others.

The release of Node.js in mid-2009 rocked the JavaScript world by giving JavaScript developers a new paradigm to consider: JavaScript as a server-side language. Packing the Chrome V8 engine under the hood made Node.js surprisingly fast, though the V8 engine doesn't deserve all of the credit for the software's performance. The Node.js instance uses an event loop to process requests, so it can handle a large number of concurrent connections despite being single-threaded.

The novelty of JavaScript on the server, its surprising performance, and the early introduction of the npm registry which let developers publish and discover modules, attracted thousands of developers. The standard library published with Node.js was primarily low-level I/O APIs, and developers raced to see who could publish the first good HTTP request wrapper, the first easy-to-use HTTP server, the first high-level image processing library, and so on. The rapid early growth of the JavaScript ecosystem generated confidence in developers who were reluctant to adopt the new technology. JavaScript, for the first time, was being seen as a real programming language, rather than just something we tolerated because of web browsers.

While JavaScript as a programming platform was maturing, the Python community was busy working on ML, in part inspired by Google's success in the market. The foundational and very popular number processing library, NumPy, was released in 2006, though it had existed in one form or another for a decade prior. A ML library called scikit-learn was released in 2010, and that was the moment I decided to start teaching ML to JavaScript developers.

The popularity of ML in Python and the ease of building and training models with tools, such as scikit-learn, astounded me and many others. In my eyes, the surge in popularity caused an ML bubble; because models were so easy to build and run, I found that many developers didn't actually understand the mechanics of the algorithms and techniques they were using. Many developers lamented their underperforming models, not understanding that they themselves were the weak link in the chain.

Machine learning at the time had been seen as mystical, magical, academic, accessible only to a select few geniuses, and only accessible to Python developers. I felt differently. Machine learning is just a category of algorithms with no magic involved. Most of the algorithms are actually easy to understand and reason about!

Rather than showing developers how to import Bayes in Python, I wanted to show developers how to build the algorithms from scratch, an important step in building intuition. I also wanted my students to largely ignore the popular Python libraries that existed at the time, because I wanted to reinforce the notion that ML algorithms can be written in any language and Python is not required.

I chose JavaScript as my teaching platform. To be perfectly honest, I chose JavaScript in part because it was considered a bad language by many at that time. My message was machine learning is easy, you can even do it in JavaScript! Fortunately for me, Node.js and JavaScript were both becoming incredibly popular, and my early articles on ML in JavaScript were read by over a million curious developers in the following years.

I also chose JavaScript in part because I didn't want ML to be seen as a tool only accessible to academics, or computer scientists, or even college graduates. I believed, and still believe, that these algorithms can be thoroughly understood by any competent developer, given enough practice and repetition. I chose JavaScript because it allowed me to reach a new audience of frontend and full-stack web developers, many of whom were self-taught or had never studied computer science formally. If the goal was to demystify and democratize the field of ML, I felt it was much better to reach the web developer community rather than the backend Python programmer community, which as a whole was already more comfortable with ML at the time.

Python has always been and remains the language of choice for ML, in part due to the maturity of the language, in part due to the maturity of the ecosystem, and in part due to the positive feedback loop of early ML efforts in Python. Recent developments in the JavaScript world, however, are making JavaScript more attractive to ML projects. I think we will see a major ML renaissance in JavaScript within a few years, especially as laptops and mobile devices become ever more powerful and JavaScript itself surges in popularity.

 

Why machine learning, why now?


Several ML techniques have been around since before computers themselves, but many of the modern ML algorithms we use today were discovered all the way back in the 1970s and 1980s. They were interesting but not practical then, and were confined largely to academia.

What changed to give ML its massive rise in popularity? First, computers finally got fast enough to run non-trivial neural networks and large ML models. And then two things happened: Google andAmazon Web Services (AWS). Google proved the value of ML to the market in a very visible manner, and then AWS made scalable computing and storage resources readily available (AWS democratized it and created new competition).

Google PageRank, the ML algorithm powering Google Search, taught us all about business applications of ML. Sergei and Larry, the founders of Google, told the world that the massive success of their search engine and resultant advertising business was the PageRank algorithm: a relatively straightforward linear algebra equation, with a massive matrix.

Note

Note that neural networks are also relatively straightforward linear algebra equations with a massive matrix.

That was ML in all its glory; big data giving big insight which translates into a major market success. This got the world economically interested in ML.

AWS, with the launch of EC2 and hourly billing, democratized compute resources. Researchers and early-stage start ups were now able to launch large computing clusters quickly, train their models, and scale the cluster back down, avoiding the need for large capital expenditures on beefy servers. This created new competition and an inaugural generation of ML-focused start ups, products, and initiatives.

ML has recently had another surge in popularity, both in the developer and business communities. The first generation of ML-focused start ups and products have now come to maturity and are proving the value of ML in the market, and in many cases these companies are closing in on or have overtaken their competitors. The desire of companies to remain competitive in their market drove up the demand for ML solutions.

The late 2015 introduction of Google's neural network library, TensorFlow, energized developers by democratizing neural networks much in the same way that EC2 democratized computing power. Additionally, those first-generation start ups that focused on developers have also come to maturity, and now we can make a simple API request to AWS or Google Cloud Platform (GCP) that runs an entire pretrained Convolutional Neural Network (CNN) on an image, and tells me if I'm looking at a cat, a woman, a handbag, a car, or all four at once.

As ML is democratized it will slowly lose its competitive value, that is, companies will no longer be able to use ML to jump leaps and bounds ahead of the competition, because their competition will also be using ML. Everyone in the field is now using the same algorithms, and competition becomes a data war. If we want to keep competing on technology, if we want to find the next 10x improvement, then we'll either need to wait for, or preferably cause, the next big technological breakthrough.

If ML had not been such a success in the market, that would have been the end of the story. All the important algorithms would be known to all, and the fight would move to who can gather the best data, put walls around their garden, or exploit their ecosystem the best.

But introducing a tool such as TensorFlow into the market changed all of that. Now, neural networks have been democratized. It's surprisingly easy to build a model, train and run it on a GPU, and generate real results. The academic fog surrounding neural networks has been lifted, and now tens of thousands of developers are playing around with techniques, experimenting, and refining. This will launch a second major wave of ML popularity, particularly focused on neural networks. The next generation of ML and neural network-focused start ups and products is being born right now, and when they come to maturity in a few years, we should see a number of significant breakthroughs, as well as breakaway companies.

Each new market success we see will create demand for ML developers. The increase of the talent pool and democratization of technology causes technology breakthroughs. Each new technology breakthrough hits the market and creates new market successes, and the cycle will continue while the field itself advances at an accelerating pace. I think, for purely economic reasons, that we really are headed for an artificial intelligence (AI) boom.

 

Advantages and challenges of JavaScript


Despite my optimism towards the future of ML in JavaScript, most developers today would still choose Python for their new projects, and nearly all large-scale production systems are developed in Python or other languages more typical to ML.

JavaScript, like any other tool, has its advantages and disadvantages. Much of the historic criticism of JavaScript has focused on a few common themes: strange behavior in type coercion, the prototypical object-oriented model, difficulty organizing large codebases, and managing deeply nested asynchronous function calls with what many developers call callback hell. Fortunately, most of these historic gripes have been resolved by the introduction of ES6, that is, ECMAScript 2015, a recent update to the JavaScript syntax.

Despite the recent language improvements, most developers would still advise against using JavaScript for ML for one reason: the ecosystem. The Python ecosystem for ML is so mature and rich that it's difficult to justify choosing any other ecosystem. But this logic is self-fulfilling and self-defeating; we need brave individuals to take the leap and work on real ML problems if we want JavaScript's ecosystem to mature. Fortunately, JavaScript has been the most popular programming language on GitHub for a few years running, and is growing in popularity by almost every metric.

There are some advantages to using JavaScript for ML. Its popularity is one; while ML in JavaScript is not very popular at the moment, the language itself is. As demand for ML applications rises, and as hardware becomes faster and cheaper, it's only natural for ML to become more prevalent in the JavaScript world. There are tons of resources available for learning JavaScript in general, maintaining Node.js servers, and deploying JavaScript applications. The Node Package Manager (npm) ecosystem is also large and still growing, and while there aren't many very mature ML packages available, there are a number of well built, useful tools out there that will come to maturity soon.

Another advantage to using JavaScript is the universality of the language. The modern web browser is essentially a portable application platform which allows you to run your code, basically without modification, on nearly any device. Tools like electron (while considered by many to be bloated) allow developers to quickly develop and deploy downloadable desktop applications to any operating system. Node.js lets you run your code in a server environment. React Native brings your JavaScript code to the native mobile application environment, and may eventually allow you to develop desktop applications as well. JavaScript is no longer confined to just dynamic web interactions, it's now a general-purpose, cross-platform programming language.

Finally, using JavaScript makes ML accessible to web and frontend developers, a group that historically has been left out of the ML discussion. Server-side applications are typically preferred for ML tools, since the servers are where the computing power is. That fact has historically made it difficult for web developers to get into the ML game, but as hardware improves, even complex ML models can be run on the client, whether it's the desktop or the mobile browser.

If web developers, frontend developers, and JavaScript developers all start learning about ML today, that same community will be in a position to improve the ML tools available to us all tomorrow. If we take these technologies and democratize them, expose as many people as possible to the concepts behind ML, we will ultimately elevate the community and seed the next generation of ML researchers.

 

The CommonJS initiative


In 2009, a Mozilla engineer named Kevin Dangoor realized that server-side JavaScript needed a lot of help in order to be useful. The concept of server-side JavaScript had already existed, but wasn't very popular due to a number of limitations, particularly in terms of the JavaScript ecosystem.

In a blog post written in January of 2009, Dangoor cited a few examples of where JavaScript needed some help. He wrote that the JavaScript ecosystem would need a standard library and standard interfaces for things such as file and database access. Additionally, the JavaScript environment needed a way to package, publish, and install libraries and dependencies for others to use, and also needed a package repository to host all of the aforementioned.

Out of all of this came the CommonJS initiative, whose most notable contribution to the JavaScript ecosystem is the CommonJS module format. If you've done any work with Node.js, you're probably already familiar with CommonJS: your package.json file is written in the CommonJS modules package specification format, and writing code like var app = require(‘./app.js’) in one file with module.exports = App in app.js is using the CommonJS module specification.

The standardization of modules and packages paved the way for a significant boost in JavaScript popularity. Developers were now able to use modules to write complex applications spanning many files, without polluting the global namespace. Package and library developers were able to build and publish new libraries of higher levels of abstraction than JavaScript's standard library. Node.js and npm would shortly grab onto these concepts and build a major ecosystem around package sharing.

 

Node.js


The release of Node.js in 2009 is possibly the single most important moment in JavaScript's history, though it would not have been possible without the release of the Chrome browser and Chrome's V8 JavaScript engine in the previous year.

Those readers who remember the launch of Chrome also recognize why Chrome dominated the browser wars: Chrome was fast, it was minimalist, it was modern, it was easy to develop for, and JavaScript itself ran much faster on Chrome than on other browsers.

Behind Chrome is the open source Chromium project, which in turn developed the V8 JavaScript engine. The innovation that V8 brought to the JavaScript world was its new execution model: instead of interpreting JavaScript in real time, V8 contains a JIT compiler that turns JavaScript directly into native machine code. This gambit paid off, and the combined effect of its stellar performance and its open source status led others to co-opt V8 for their own purposes.

Node.js took the V8 JavaScript engine, added an event-driven architecture around it, and added a low-level I/O API for disk and file access. The event-driven architecture turned out to be a critical decision. Other server-side languages and technologies, such as PHP, typically used a thread pool to manage concurrent requests, with each thread itself blocking while processing the request. Node.js is a single-threaded process, but using an event loop avoids blocking operations and instead favors asynchronous, callback-driven logic. While the single-threaded nature of Node.js is considered by many to be a drawback, Node.js was still able to handle many concurrent requests with good performance, and that was enough to bring developers to the platform.

A few months later, the npm project was released. Building on top of the foundational work that CommonJS achieved, npm allowed package developers to publish their modules to a centralized registry (called the npm registry), and allowed package consumers to install and maintain dependencies with the npm command-line tool.

Node.js likely would not have broken into the mainstream if not for npm. The Node.js server itself provided the JavaScript engine, the event loop, and a few low-level APIs, but as developers work on bigger projects they tend to want higher-level abstractions. When making HTTP requests or reading files from disk, developers don't always want to have to worry about binary data, writing headers, and other low-level issues. The npm and the npm registry let the developer community write and share their own high-level abstractions in the form of modules other developers could simply install and require().

Unlike other programming languages which typically have high-level abstractions built in, Node.js was allowed to focus on providing the low-level building blocks and the community took care of the rest. The community stepped up by building excellent abstractions such as the Express.js web application framework, the Sequelize ORM, and hundreds of thousands of other libraries ready to be used after just a simple npm install command.

With the advent of Node.js, JavaScript developers with no prior server-side language knowledge were now able to build entire full-stack applications. The frontend code and backend code could now be written in the same language, by the same developers.

Ambitious developers were now building entire applications in JavaScript, though they ran into a few issues and solutions along the way. Single-page applications fully written in JavaScript became popular, but also became difficult to template and organize. The community responded by building frameworks such as Backbone.js (the spiritual predecessor to frameworks such as Angular and React), RequireJS (a CommonJS and AMD module loader), and templating languages such as Mustache (a spiritual predecessor to JSX).

When developers ran into issues with SEO on their single-page applications, they invented the concept of isomorphic applications, or codes that could be rendered both server side (so that web spiders could index the content) and client side (to keep the application fast and JavaScript-powered). This led to the invention of more JavaScript frameworks such as MeteorJS.

Eventually, JavaScript developers building single-page applications realized that often, their server-side and database requirements were lightweight, requiring just authentication, and data storage, and retrieval. This led to the development of serverless technologies or database-as-a-service (DBaaS) platforms such as Firebase, which in turn laid out a path for mobile JavaScript applications to become popular. The Cordova/PhoneGap project appeared around the same time, allowing developers to wrap their JavaScript code in a native iOS or Android WebView component and deploy their JavaScript applications to the mobile app stores.

For our purposes throughout this book, we'll be relying on Node.js and npm very heavily. Most of the examples in this book will use ML packages available on npm.

 

TypeScript language


The development and sharing of new packages on npm was not the only result of JavaScript's popularity. JavaScript's increasing usage as a primary programming language caused many developers to lament the lack of IDE and language tooling support. Historically, IDEs were more popular with developers of compiled and statically-typed languages such as C and Java, as it’s easier to parse and statically analyze those types of languages. It wasn't until recently that great IDEs started appearing for languages such as JavaScript and PHP, while Java has had IDEs geared towards it for many years.

Microsoft wanted better tooling and support for their large-scale JavaScript projects, but there were a few issues with the JavaScript language itself that got in the way. In particular, JavaScript's dynamic typing (the fact that var number could start its life as the integer 5, but then be assigned to an object later) precludes using static analysis tools to ensure type safety, and also makes it difficult for an IDE to find the correct variable or object to autocomplete with. Additionally, Microsoft wanted a class-based object-oriented paradigm with interfaces and contracts, but JavaScript's object-oriented programming paradigm was based on prototypes, not classes.

Microsoft therefore invented the TypeScript language in order to support large-scale JavaScript development efforts. TypeScript introduced classes, interfaces, and static typing to the language. Unlike Google's Dart, Microsoft made sure TypeScript would always be a strict superset of JavaScript, meaning that all valid JavaScript is also valid TypeScript. The TypeScript compiler does static type checking at compile time, helping developers catch errors early. Support for static typing also helps IDEs interpret code more accurately, making for a nicer developer experience.

Several of TypeScript's early improvements to the JavaScript language have been made irrelevant by ECMAScript 2015, or what we call ES6. For instance, TypeScript's module loader, class syntax, and arrow function syntax have been subsumed by ES6, and TypeScript now simply uses the ES6 versions of those constructs; however, TypeScript still brings static typing to JavaScript, which ES6 wasn't able to accomplish.

I bring up TypeScript here because, while we won't be using TypeScript in the examples in this book, some of the examples of ML libraries we examine here are written in TypeScript.

For instance, one example found on the deeplearn.js tutorials page shows code that looks like the following:

const graph = new Graph();
 // Make a new input in the graph, called 'x', with shape [] (a Scalar).
 const x: Tensor = graph.placeholder('x', []);
 // Make new variables in the graph, 'a', 'b', 'c' with shape [] and   
    random
 // initial values.
 const a: Tensor = graph.variable('a', Scalar.new(Math.random()));
 const b: Tensor = graph.variable('b', Scalar.new(Math.random()));
 const c: Tensor = graph.variable('c', Scalar.new(Math.random()));

The syntax looks like ES6 JavaScript except for the new colon notation seen in const x: Tensor = … : this code is telling the TypeScript compiler that the const x must be an instance of the Tensor class. When TypeScript compiles this code, it first checks that everywhere x is used expects a Tensor (it will throw an error if not), and then it simply discards the type information when compiling to JavaScript. Converting the preceding TypeScript code to JavaScript is as simple as removing the colon and the Tensor keyword from the variable definition.

You are welcome to use TypeScript in your own examples as you follow along with this book, however, you will have to update the build process that we set up later to support TypeScript.

 

Improvements in ES6


The ECMAScript committee, which defines the specification for the JavaScript language itself, released a new specification called ECMAScript 6/ECMAScript 2015 in June 2015. The new standard, called ES6 for short, was a major revision of the JavaScript programming language and added a number of new paradigms intended to make development of JavaScript programs easier.

While ECMAScript defines the specification for the JavaScript language, the actual implementation of the language is dependent on the browser vendors and the maintainers of the various JavaScript engines. ES6 by itself is only a guideline, and because the browser vendors each have their own timeline for implementing new language features, the JavaScript language and the JavaScript implementations diverged slightly. Features defined by ES6, such as classes, were not available in the major browsers, but developers wanted to use them anyway.

Enter Babel, the JavaScripttranspiler. Babel can read and parse different JavaScript flavors (such as ES6, ES7, ES8, and React JSX) and convert it or compile it into browser-standard ES5. Even today, the entirety of ES6 has not yet been implemented by the browser vendors, so Babel remains an essential tool for developers wishing to write ES6 code.

The examples in this book will use ES6. If you're not yet familiar with the newer syntax, here are a few of the major features you'll see used throughout this book.

Let and const

In ES5 JavaScript, we use the var keyword to define variables. In most cases, var can simply be replaced with let, with the major difference between the two constructs being the scoping of the variable with respect to blocks. The following example from MDN web docs, or previously Mozilla Developer Network (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let), demonstrates the subtle difference between the two:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // same variable!
    console.log(x);  // 2
  }
  console.log(x);  // 2
 }

 function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // different variable
    console.log(x);  // 2
  }
  console.log(x);  // 1
 }

So, while you must use additional caution in cases like the preceding one, in most cases you can simply replace var with let.

The const keyword, unlike let, defines a variable as a constant; that is, you cannot reassign a variable initialized with const at a later date. For example, the following code causes an error with a message similar to invalid assignment to const a:

const a = 1;
a = 2;

On the other hand the same code, using var or let to define a, would run successfully.

Note

Note that if a is an object, you are allowed to modify object properties of a.

The following code will run successfully:

const obj = {};
obj.name = ‘My Object’;

However, attempting to redefine objects such as in obj = {name: “other object”} would cause an error.

I find that in most programming contexts, const is typically more appropriate than let, as most variables you use never need to be redefined. My recommendation is to use const as much as you can, and use let only when you have a reason to redefine the variable later.

Classes

One very welcome change in ES6 is the addition of classes and class inheritance. Previously, object-oriented programming in JavaScript required prototypical inheritance, which many developers found unintuitive, like the following ES5 example:

var Automobile = function(weight, speed) {
   this.weight = weight;
   this.speed = speed;
}
Automobile.prototype.accelerate = function(extraSpeed) {
   this.speed += extraSpeed;
}
var RaceCar = function (weight, speed, boost) {
   Automobile.call(this, weight, speed);
   this.boost = boost;
}
RaceCar.prototype = Object.create(Automobile.prototype);
RaceCar.prototype.constructor = RaceCar;
RaceCar.prototype.accelerate = function(extraSpeed) {
  this.speed += extraSpeed + this.boost;
}

In the preceding code, extending an object requires calling the parent class in the child's constructor function, creating a clone of the parent's prototype object, and overriding the parent's prototype constructor with the child's prototype constructor. These steps were seen as unintuitive and burdensome by most developers.

Using ES6 classes, however, the code will look like this:

class Automobile {
 constructor(weight, speed) {
   this.weight = weight;
   this.speeed = speed;
 }
 accelerate(extraSpeed) {
   this.speed += extraSpeed;
 }
}
class RaceCar extends Automobile {
 constructor(weight, speed, boost) {
   super(weight, speed);
   this.boost = boost;
 }
 accelerate(extraSpeed) {
   this.speed += extraSpeed + this.boost;
 }
}

The preceding syntax is more in line with what we'd expect from object-oriented programming, and also makes inheritance much simpler.

It's important to note that under the hood, ES6 classes still use JavaScript's prototypical inheritance paradigm. Classes are just syntactic sugar on top of the existing system, so there is no significant difference between these two approaches other than clean code.

Module imports

ES6 also defines a module import and export interface. With the older CommonJS approach, modules are exported using the modules.export construct, and modules are imported with the require(filename) function. The ES6 approach looks a little different. In one file, define and export a class, as shown in the following code:

Class Automobile {
…
}
export default Automobile

And in another file, import the class, as shown in the following code:

import Automobile from ‘./classes/automobile.js’;
const myCar = new Automobile();

At present, Babel compiles ES6 modules to the same format as CommonJS modules, so you can use either the ES6 modules syntax or the CommonJS modules syntax if you’re using Babel.

Arrow functions

One quirky, useful, but somewhat annoying aspect of ES5 JavaScript is its heavy use of callbacks that run asynchronously. You are probably intimately familiar with jQuery code that looks something like this:

$(“#link”).click(function() {
  var $self = $(this);
  doSomethingAsync(1000, function(resp) {
    $self.addClass(“wasFaded”);
    var processedItems = resp.map(function(item) {
      return processItem(item);
    });
    return shipItems(processedItems);
  });
});

We're forced to create a variable called $self because the original this context is lost in our inner anonymous function. We also have a lot of boilerplate and difficult-to-read code due to needing to create three separate anonymous functions.

Arrow function syntax is both syntactic sugar that helps us write anonymous functions with a shorter syntax, and also a functional update that preserves the context of this inside an arrow function.

For instance, the preceding code may be written in ES6 as follows:

$(“#link”).click(function() {
  dozsSomethingAsync(1000, resp => {
    $(this).addClass(“wasFaded”);
    const processedItems = resp.map(item => processItem(Item));
    return shipItems(processedItems);
  });
});

You can see in the preceding code that we no longer need a $self variable to preserve this, and our call to .map is much simpler, no longer requiring the function keyword, parentheses, curly braces, or a return statement.

Now let's look at some equivalent functions. Let's look at the following code:

const double = function(number) {
  return number * 2;
}

The preceding code would be similar to:

const double = number => number * 2;
// Is equal to:
const double = (number) => { return number * 2; }

In the aforementioned examples, we can omit the parentheses around the number parameter because the function only requires one parameter. If the function required two parameters, we would be required to add parentheses as in the next example. Additionally, if the body of our function only requires one line, we can omit the function body curly braces and omit the return statement.

Let's look at another equivalence, with multiple parameters, as shown in the following code:

const sorted = names.sort(function (a, b) {
  return a.localeCompare(b);
});

The preceding code would be similar to:

const sorted = names.sort((a, b) => a.localeCompare(b));

I find that arrow functions make themselves most useful in situations like the preceding one, when you're doing data transformations, especially where using Array.map, Array.filter, Array.reduce, and Array.sort calls with straightforward function bodies. Arrow functions are less useful in jQuery because of jQuery's tendency to give you data using the this context, which you don't receive with anonymous arrow functions.

Object literals

ES6 makes some improvements to object literals. There are several improvements, but the one you'll see most is the implicit naming of object properties. In ES5 it would be as follows:

var name = ‘Burak’;
var title = ‘Author’;
var object = {name: name, title: title};

In ES6, if the property name and the variable name are the same as the preceding one, you can simplify it to the following:

const name = ‘Burak’;
const title = ‘Author’;
const object = {name, title};

Additionally, ES6 introduces the object spread operator, which simplifies shallow object merges. For instance, take a look at the following code in ES5:

function combinePreferences(userPreferences) {
 var defaultPreferences = {size: ‘large’, mode: ‘view’};
 return Object.assign({}, defaultPreferences, userPreferences);
}

The preceding code will create a new object from defaultPreferences, and merge in properties from userPreferences. Passing an empty object to the Object.assign instance first parameter ensures that we create a new object rather than overwriting defaultPreferences (which isn't an issue in the preceding example, but is an issue in real-life use cases).

And now, let's take a look at the same in ES6:

function combinePreferences(userPreferences) {
 var defaultPreferences = {size: ‘large’, mode: ‘view’};
 return {...defaultPreferences, ...userPreferences};
}

This approach does the same as the ES5 example, but is quicker and easier to read in my opinion than the Object.assign method. Developers familiar with React and Redux, for instance, often use the object spread operator when managing reducer state operations.

The for...of function

The for loops over arrays in ES5 are often achieved using the for (index in array) syntax, which looks something like this:

var items = [1, 2, 3 ];
for (var index in items) {
var item = items[index];
…
 }

And ES6 adds the for...of syntax, which saves you a step, as you can see from the following code:

const items = [1, 2, 3 ];
for (const item of items) {
 …
 }

Promises

Promises, in one form or another, have been available in JavaScript for a while. All jQuery users are familiar with the idea. A promise is a reference to a variable that is generated asynchronously and may become available in the future.

The ES5 way of doing things, if you weren't already using some sort of third-party promise library or jQuery's deferred's, was to accept a callback function to an asynchronous method and run the callback upon successful completion, as shown in the following code:

function updateUser(user, settings, onComplete, onError) {
  makeAsyncApiRequest(user, settings, function(response) {
    if (response.isValid()) {
      onComplete(response.getBody());
    } else {
      onError(response.getError())
    }
  });
}
updateUser(user, settings, function(body) { ... }, function(error) { ... });

In ES6, you may return a Promise which encapsulates the asynchronous request and either gets resolved or rejected, as shown in the following code:

function updateUser(user, settings) {
  return new Promise((resolve, reject) => {
    makeAsyncApiRequest(user, settings, function(response) {
      if (response.isValid()) {
        resolve(response.getBody());
      } else {
        reject(response.getError())
      }
    });
  });
}
updateUser(user, settings)
  .then(
    body => { ... },
    error => { ... }
  );

The real power of promises is that they can be passed around as objects, and promise handlers can be chained.

The async/await functions

The async and await keywords are not an ES6 feature but rather an ES8 feature. While promises bring huge improvements to the way we deal with asynchronous calls, promises also are susceptible to lots of method chaining, and in some cases force us to use asynchronous paradigms when we really just want to write a function that acts asynchronously but reads as if it were a synchronous function.

Now let's take a look at the following example from MDN's asynchronous function reference page (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function):

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}
async function asyncCall() {
  console.log('calling');
  var result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: "resolved"
}
asyncCall();

The resolveAfter2Seconds function is a normal JavaScript function that returns an ES6 promise. The magic is in the asyncCall function, which is marked by the async keyword. Inside asyncCall, we invoke resolveAfter2Seconds with the await keyword, rather than using the more familiar promise .then(result => console.log(result)) construct we'd normally use in ES6. The await keyword makes our async function wait for the promise to resolve before continuing, and returns the result of the Promise directly. In this manner, async/await can convert asynchronous functions that use promises to read like synchronous functions, which should help keep deeply nested promise calls and asynchronous function stats neat and easy to read.

The async and await features are part of ES8, not ES6, so when we set up Babel in a few minutes we'll need to be sure to include all new versions of EMCAScript in our configuration, not just ES6.

 

Preparing the development environment


The examples in this book will use both the web browser environment and the Node.js environment. While Node.js Version 8 and higher has support for ES6+, not all browser vendors have complete support yet for ES6+ features, and we will therefore be using Babel to transpile all of our code regardless.

This book will try its best to use the same project structure for all examples, whether they're executed on the command line in Node.js or run in the browser. Because we're attempting to standardize this project structure, not every project will use all of the features we set up in this section.

The tools you will need are:

  • Your favorite code editor, such as Vim, Emacs, Sublime Text, or WebStorm
  • An up-to-date web browser such as Chrome or Firefox
  • Node.js Version 8 LTS or higher; this book will use version 9.4.0 for all examples
  • The Yarn package manager (optional; you may use npm instead)
  • Various build tools such as Babel and Browserify

Installing Node.js

If you're a macOS user, the easiest way to install Node.js is through a package manager such as Homebrew or MacPorts. For best compatibility with the examples in this book, install Node.js version 9.4.0 or greater.

Windows users can also use the Chocolatey package manager to install Node.js, otherwise you may follow the instructions on the Node.js current download page: https://nodejs.org/en/.

Linux users should be careful if installing Node.js through their distribution's package manager, as the shipped version of Node.js may be a much older version. If your package manager uses a version older than V8, you may either add a repository to your package manager, build from source, or install from binary, as appropriate for your system.

Once you've installed Node.js, ensure that it runs and is the correct version by running node --version from the command line. The output will look like the following:

$ node --version
 V9.4.0

This is also a good time to test that npm also works:

$ npm --version
 5.6.0

Optionally installing Yarn

Yarn is a package management tool similar to and compatible with npm, though I find it is faster and easier to work with. If using Homebrew on macOS, you may simply install it using brew install yarn; otherwise follow the instructions found on Yarn's installation guide page (https://yarnpkg.com/en/docs/install#windows-stable).

If you want to use npm instead of Yarn, you may; both respect the same format for package.json, though they have slightly different syntaxes for commands such as add, require, and install. If you're using npm instead of Yarn, simply replace the commands with the correct function; the package names used will all be the same.

Creating and initializing an example project

Use the command line, your favorite IDE, or your file browser to create a directory somewhere on your machine called MLinJSBook, with a subdirectory called Ch1-Ex1.

Navigate your command line to the Ch1-Ex1 folder, and run the command yarn init, which like npm init will create a package.json file and prompt you for basic information. Respond to the prompts, answering appropriately. You will not be publishing this package so the answers aren't too important, however, when prompted for the application's entry point, type in dist/index.js.

Next, we need to install a few build tools that we'll use for the majority of our example projects:

  • babel-core: The Babel transpiler core
  • babel-preset-env: The Babel parser preset that parses ES6, ES7, and ES8 code
  • browserify: A JavaScript bundler which can compile multiple files into a single file
  • babelify: The Babel plugin for Browserify

Install these as development environment requirements by issuing the following command:

yarn add -D babel-cli browserify babelify babel-preset-env

Creating a Hello World project

To test that everything is building and running, we'll create a very simple two-file Hello World project and add our build script.

First, create two subdirectories under your Ch1-Ex1 folder: src and dist. We'll use this convention for all projects: src will contain JavaScript source code, dist will contain built source code and any additional assets (images, CSS, HTML files, and so on) required by the project.

In the src folder, create a file called greeting.js with the following code:

const greeting = name => 'Hello, ' + name + '!';
export default greeting;

Then create another file called index.js with the following:

import greeting from './greeting';
console.log(greeting(process.argv[2] || 'world'));

This small application tests whether we can use basic ES6 syntax and module loading, as well as access command-line arguments given to Node.js.

Next, open up the package.json file in Ch1-Ex1, and add the following section to the file:

"scripts": {
 "build-web": "browserify src/index.js -o dist/index.js -t [ babelify -  
  -presets [ env ] ]",
 "build-cli": "browserify src/index.js --node -o dist/index.js -t [  
  babelify --presets [ env ] ]",
 "start": "yarn build-cli && node dist/index.js"
},

This defines three simple command-line scripts:

  • Build-web uses Browserify and Babel to compile everything that src/index.js touches into a single file called dist/index.js
  • Build-cli is similar to build-web, except it also uses Browserify's node option flag; without this option we would not be able to access command-line arguments given to Node.js
  • Start is intended only for CLI/Node.js examples, and both builds and runs the source code

Your package.json file should now look something like the following:

{
"name": "Ch1-Ex1",
"version": "0.0.1",
"description": "Chapter one example",
"main": "src/index.js",
"author": "Burak Kanber",
"license": "MIT",
"scripts": {
  "build-web": "browserify src/index.js -o dist/index.js -t [ babelify --presets [ env ] ]",
  "build-cli": "browserify src/index.js --node -o dist/index.js -t [ babelify --presets [ env ] ]",
  "start": "yarn build-cli && node dist/index.js"
},
"dependencies": {
  "babel-core": "^6.26.0",
  "babel-preset-env": "^1.6.1",
  "babelify": "^8.0.0",
  "browserify": "^15.1.0"
}}

Let's put this simple application through a few tests. First, make sure that yarn build-cli works. You should see something like the following:

$ yarn build-cli
yarn run v1.3.2
$ browserify src/index.js --node -o dist/index.js -t [ babelify --presets [ env ] ]
Done in 0.59s.

At this point, confirm that the dist/index.js file has been built, and try running it directly, using the following code:

$ node dist/index.js
Hello, world!

Also try passing in your name as an argument to the command, using the following code:

$ node dist/index.js Burak
Hello, Burak!

Now, let's try the build-web command, as shown in the following code. Because this command omits the node option, we expect that our argument will not work:

$ yarn build-web
yarn run v1.3.2
$ browserify src/index.js -o dist/index.js -t [ babelify --presets [ env ] ]
Done in 0.61s.
$ node dist/index.js Burak
Hello, world!

Without the node option, our arguments are not forwarded to the script, and it defaults to saying Hello, world!, which is the expected result here.

Finally, let's test our yarn start command, using the following code, to make sure it builds the CLI version of the application and also forwards our command-line arguments, using the following code:

$ yarn start "good readers"
yarn run v1.3.2
$ yarn build-cli && node dist/index.js 'good readers'
$ browserify src/index.js --node -o dist/index.js -t [ babelify --presets [ env ] ]
Hello, good readers!
Done in 1.05s.

The yarn start command successfully built the CLI version of the application and forwarded our command-line arguments to the program.

We will try our best to use the same structure for each of the examples in this book, however, pay attention to the beginning of each chapter as each example may require some additional setup work.

 

Summary


In this chapter, we've discussed the important moments of JavaScript's history as applied to ML, starting from the launch of Google (https://www.google.com/) and finishing up at the end of 2017 with the release of Google's deeplearn.js library.

We’ve discussed some advantages to using JavaScript for machine learning, and also some of the challenges we’re facing, particularly in terms of the machine learning ecosystem.

We then took a tour of the most important recent developments in the JavaScript language, and had a brief introduction to ES6, the newest stable JavaScript language specification.

Finally, we set up an example development environment using Node.js, the Yarn package manager, Babel, and Browserify—tools that we will use throughout the rest of the book in our examples.

In the next chapter, we'll begin exploring and processing the data itself.

About the Author

  • Burak Kanber

    Burak Kanber is an entrepreneur, software engineer, and the co-author of "Genetic Algorithms in Java". He earned his Bachelor's and Master's degrees in Mechanical Engineering from the prestigious Cooper Union in New York City, where he concentrated on software modeling and simulation of hybrid vehicle powertrains.

    Currently, Burak is a founder and the CTO of Tidal Labs, a popular enterprise influencer marketing platform. Previously, Burak had founded several startups, most notably a boutique design and engineering firm that helped startups and small businesses solve difficult technical problems. Through Tidal Labs, his engineering firm, and his other consulting work, Burak has helped design and produce dozens of successful products and has served as a technical advisor to many startups.

    Burak's core competencies are in machine learning, web technologies (specifically PHP and JavaScript), engineering (software, hybrid vehicles, control systems), product design and agile development. He's also worked on several interactive art projects, is a musician, and is a published engineer.

    Browse publications by this author

Latest Reviews

(3 reviews total)
The first few chapters are fine, but the last few chapters are just general theoretical statements. They are not practical.
Loving this book. Between the math, theory, and code examples, I can't put it down!
I give the book 5 stars, I have yet to look at the book.

Recommended For You

Hands-On Machine Learning with TensorFlow.js

Get hands-on with the browser-based JavaScript library for training and deploying machine learning models effectively

By Kai Sasaki
Python Machine Learning - Third Edition

Applied machine learning with a solid foundation in theory. Revised and expanded for TensorFlow 2, GANs, and reinforcement learning.

By Sebastian Raschka and 1 more
Clean Code in JavaScript

Get the most out of JavaScript for building web applications through a series of patterns, techniques, and case studies for clean coding

By James Padolsey
React Material-UI Cookbook

Develop modern-day applications by implementing Material Design principles in React using Material-UI

By Adam Boduch