JavaScript
Table of Contents
Syntax
function
let age = 16; // take 16 as an example
if (age < 18) {
welcome(); // \ (runs)
// |
function welcome() { // |
alert("Hello!"); // | Function Declaration is available
} // | everywhere in the block where it's declared
// |
welcome(); // / (runs)
} else {
function welcome() { // for age = 16, this "welcome" is never created
alert("Greetings!");
}
}
// Here we're out of figure brackets,
// so we can not see Function Declarations made inside of them.
welcome(); // Error: welcome is not defined
- By put
let welcome;
before theif
, we can accesswelcome
at last.
let func = (arg1, arg2, ...argN) => expression
let double = n => n * 2; // paran can be omitted
let sum = (a, b) => { // the figure bracket opens a multiline function
let result = a + b;
return result; // if we use figure brackets, use return to get results
};
hoisting
There is also function hoisting. A crude timeline of how JS gets executed:
- Parse the scope and detect all function definitions
- Execute the code top-to-bottom with all functions found in step 1 available as variables. following cases are exceptions:
- Assignments are not evaluated until the code is executed.
- Wrapping a function in parenthesis (
()
) is a quick way to convert a function definition into a function expression
function
function sumAll(...args) { // args is the name for the array
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
func.call(context, arg1, arg2, ...)
function sayHi() {
alert(this.name);
}
let user = { name: "John" };
let admin = { name: "Admin" };
// use call to pass different objects as "this"
sayHi.call( user ); // this = John
sayHi.call( admin ); // this = Admin
let worker = {
someMethod() {
return 1;
},
slow(x) {
alert("Called with " + x);
return x * this.someMethod(); // (*)
}
};
function cachingDecorator(func) {
let cache = new Map();
return function(x) {
if (cache.has(x)) {
return cache.get(x);
}
let result = func.call(this, x); // "this" is passed correctly now
cache.set(x, result);
return result;
};
}
worker.slow = cachingDecorator(worker.slow); // now make it caching
alert( worker.slow(2) ); // works
alert( worker.slow(2) ); // works, doesn't call the original (cached)
class
class MyClass {
constructor(...) {
// ...
}
method1(...) {}
method2(...) {}
get something(...) {}
set something(...) {}
static staticMethod(..) {}
// ...
}
Classes provide "super" keyword for that.
super.method(...)
to call a parent method.super(...)
to call a parent constructor (inside our constructor only).- constructors in inheriting classes must call
super(...)
, and (!) do it before using this.- When a normal constructor runs, it creates an empty object as this and continues with it.
- But when a derived constructor runs, it doesn't do it. It expects the parent constructor to do this job.
Following two code blocks are equivalent:
function User(name) {
this.name = name;
}
User.prototype.sayHi = function() {
alert(this.name);
}
let user = new User("John");
user.sayHi();
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
let user = new User("John");
user.sayHi();
The proof of the fact:
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// proof: User is the "constructor" function
alert(User === User.prototype.constructor); // true
// proof: there are two methods in its "prototype"
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
function makeClass(phrase) {
// declare a class and return it
return class {
sayHi() {
alert(phrase);
};
};
}
let User = makeClass("Hello");
new User().sayHi(); // Hello
class PowerArray extends Array {
isEmpty() {
return this.length === 0;
}
// built-in methods will use this as the constructor
static get [Symbol.species]() {
return Array;
}
}
let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false
// filter creates new array using arr.constructor[Symbol.species] as constructor
let filteredArr = arr.filter(item => item >= 10);
// filteredArr is not PowerArray, but Array
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function
class Rabbit {}
let rabbit = new Rabbit();
// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // true
imports, exports
// mathConstants.js
export const pi = 3.14;
export const exp = 2.7;
export const alpha = 0.35;
// -------------
// myFile.js
import { pi, exp } from './mathConstants.js'; // Named import -- destructuring-like syntax
console.log(pi) // 3.14
console.log(exp) // 2.7
// -------------
// mySecondFile.js
import * as constants from './mathConstants.js'; // Inject all exported values into constants variable
console.log(constants.pi) // 3.14
console.log(constants.exp) // 2.7
// coolNumber.js
const ultimateNumber = 42;
export default ultimateNumber;
// ------------
// myFile.js
// You can just omit '.js'
import number from 'coolNumber';
// Default export, independently from its name, is automatically injected into number variable;
console.log(number) // 42
- Imports and exports must be at the top level
- Module imports are hoisted(internally moved to the beginning of the current scope)
- Imports which start with
@
(likeimport @/components/component
) means root of the project.- This is generally customized as
resolve.alias
field withinwebpack.config.js
.
- This is generally customized as
Types
Object
let user = new Object(); // "object constructor" syntax
let user = {
name: "John",
age: 30,
"likes birds": true // multiword property name must be quoted
};
alert( user.name ); // John
alert( user.age ); // 30
delete user.age;
// multiword
user["likes birds"] = true;
alert(user["likes birds"]); // true
let fruit = "apple";
let bag = {
[fruit]: 5, // the name of the property is taken from the variable fruit
};
alert(bag.apple);
function makeUser(name, age) {
return {
name, // same as name: name
age // same as age: age
// ...
};
}
let user = { name: "John", age: 30 };
alert("age" in user); // true, user.age exists
alert("blabla" in user); // false, user.blabla doesn't exist
alert(user.noSuchProperty === undefined); // true means "no such property"
for (key in object) {
// executes the body for each key among object properties
}
let user = { name: "John" };
let permissions1 = { canView: true };
let permissions2 = { canEdit: true };
// copies all properties from permissions1 and permissions2 into user
Object.assign(user, permissions1, permissions2);
// now user = { name: "John", canView: true, canEdit: true }
let user = {
name: "John",
age: 30
};
let clone = Object.assign({}, user);
let user = {
name: "John",
age: 30
};
// loop over values
for (let value of Object.values(user)) {
alert(value); // John, then 30
}
Iterator
let range = {
from: 1,
to: 5
};
// 1. call to for..of initially calls this
range[Symbol.iterator] = function() {
// 2. ...it returns the iterator:
return {
current: this.from,
last: this.to,
// 3. next() is called on each iteration by the for..of loop
next() {
// 4. it should return the value as an object {done:.., value :...}
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
};
// now it works!
for (let num of range) {
alert(num); // 1, then 2, 3, 4, 5
}
Map
- The main difference with
Object
is that Map allows keys of any type.
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomateos, onion
}
// iterate over values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
alert(entry); // cucumber,500 (and so on)
}
String
- Single and double quotes are literally same (except escaping)
- It seems that single quotes are preferred in the most famous libraries.
- Backticks are used for string interpolation.
let name = "John";
// embed a variable
alert( `Hello, ${name}!` ); // Hello, John!
// embed an expression
alert( `the result is ${1 + 2}` ); // the result is 3
// Multiple lines
let guestList = `Guests:
* John
* Pete
* Mary
`;
Symbol
// “Symbol” value represents a unique identifier.
// The first argument is a description("id", in this case), useful for debugging
let id1 = Symbol("id");
let id2 = Symbol("id");
alert(id1 == id2); // false
let user = { name: "John" };
// Work as a hidden property
let id = Symbol("id");
user[id] = "ID Value";
alert( user[id] ); // we can access the data using the symbol as the key
let id = Symbol("id");
let user = {
name: "John",
age: 30,
[id]: 123
};
for (let key in user) alert(key); // name, age (no symbols)
// the direct access by the symbol works
alert( "Direct: " + user[id] );
// read from the global registry
let id = Symbol.for("id"); // if the symbol did not exist, it is created
// read it again
let idAgain = Symbol.for("id");
// the same symbol
alert( id === idAgain ); // true
let sym = Symbol.for("name");
let sym2 = Symbol.for("id");
// get name from symbol
alert( Symbol.keyFor(sym) ); // name
alert( Symbol.keyFor(sym2) ); // id
// we can omit "function" and just write sayHi().
let user = {
name: "John",
age: 30,
sayHi() {
alert(this.name);
}
};
user.sayHi(); // John
function sayHi() {
alert(this);
}
sayHi(); // undefined
In this case this is undefined in strict mode. If we try to access this.name, there will be an error. In non-strict mode (if one forgets use strict) the value of this in such case will be the global object (window in a browser, we’ll get to it later). This is a historical behavior that "use strict" fixes. Please note that usually a call of a function that uses this without an object is not normal, but rather a programming mistake. If a function has this, then it is usually meant to be called in the context of an object
let user = {
name: "John",
hi() { alert(this.name); }
}
// split getting and calling the method in two lines
let hi = user.hi;
hi(); // Error, because this is undefined
To make user.hi()
calls work, JavaScript uses a trick – the dot '.' returns not a function, but a value of the special Reference Type. The value of Reference Type is a three-value combination (base, name, strict)
Any other operation like assignment hi = user.hi
discards the reference type as a whole.
So, as the result, the value of this is only passed the right way if the function is called directly using a dot obj.method()
or square brackets obj[method]()
syntax (they do the same here).
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ilya
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
alert(user.name); // Jack
alert(user.isAdmin); // false
// 1. A new empty object is created and assigned to this.
// 2. The function body executes. Usually it modifies this, adds new properties to it.
// 3. The value of this is returned.
function User(name) {
if (!new.target) { // if you run me without new
return new User(name); // ...I will add new for you
}
this.name = name;
}
let john = User("John"); // redirects call to new User
alert(john.name); // John
Reference
Global functions
alert("Hello");
// the same as
window.alert("Hello");
let user = "John";
alert(user); // John
alert(window.user); // undefined, don't have let
alert("user" in window); // false
alert, prompt, confirm
alert("Hello");
let age = prompt('How old are you?', 100); // always supply a 'default'
alert(`You are ${age} years old!`);
let isBoss = confirm("Are you the boss?");
alert( isBoss ); // true if OK is pressed
encodeURI, encodeURIComponent, decodeURI, decodeURIComponent
escape
,unescape
are deprecated- The difference between
-URI
and-URIComponent
is following:
setTimeout, setInterval
function sayHi() {
alert('Hello');
}
setTimeout(sayHi, 1000);
setTimeout(() => alert('Hello'), 1000);
let timerId = setTimeout(...);
clearTimeout(timerId);
// repeat with the interval of 2 seconds
let timerId = setInterval(() => alert('tick'), 2000);
// after 5 seconds stop
setTimeout(() => { clearInterval(timerId); alert('stop'); }, 5000);
Some use cases of setTimeout(...,0)
:
- To split CPU-hungry tasks into pieces, so that the script doesn’t “hang”
- To let the browser do something else while the process is going on (paint the progress bar).
Topics
Type Conversions
alert( Number(" 123 ") ); // 123
alert( Number("123z") ); // NaN (error reading a number at "z")
alert( Number(true) ); // 1
alert( Number(false) ); // 0
alert( Boolean(1) ); // true
alert( Boolean(0) ); // false
alert( Boolean("hello") ); // true
alert( Boolean("") ); // false
alert( 1 + '2' ); // '12' (string to the right)
alert( '1' + 2 ); // '12' (string to the left)
Check for an undefined or null variable
In general it is recommended to use =
instead of ==
. The proposed solution is an exception to this rule. The JSHint syntax checker even provides the eqnull
option for this reason.
Comparisons
alert( '2' > 1 ); // true, string '2' becomes a number 2
alert( '01' == 1 ); // true, string '01' becomes a number 1
alert( true == 1 ); // true
alert( false == 0 ); // true
alert( '' == false ); // true
alert( 0 == false ); // true
alert( 0 === false ); // false, because the types are different
alert( null > 0 ); // false
alert( null == 0 ); // false, null special rule applied
alert( null >= 0 ); // true, null is converted to 0
alert( null == undefined ); // true
alert( null === undefined ); // false
- Use
===
much more often than==
. null
andundefined
equal==
each other and do not equal any other value.- Don’t use comparisons
>=
>
<
<=
with a variable which may benull
/undefined
Style Guides
Garbage collection
- Mark and sweep
- Generational collection
- Incremental collection
- Idle-time collection
Destructuring
// let [firstName, surname] = arr;
let firstName = arr[0];
let surname = arr[1];
// first and second elements are not needed
let [, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert( title ); // Consul
let user = {};
[user.name, user.surname] = "Ilya Kantor".split(' ');
alert(user.name); // Ilya
let user = {
name: "John",
age: 30
};
// loop over keys-and-values
for (let [key, value] of Object.entries(user)) {
alert(`${key}:${value}`); // name:John, then age:30
}
let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];
alert(name1); // Julius
alert(name2); // Caesar
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2
// default values
let [name = "Guest", surname = "Anonymous"] = ["Julius"];
alert(name); // Julius (from array)
alert(surname); // Anonymous (default used)
let options = {
title: "Menu",
width: 100,
height: 200
};
let {title, width, height} = options;
alert(title); // Menu
alert(width); // 100
alert(height); // 200
let options = {
title: "Menu",
width: 100,
height: 200
};
// { sourceProperty: targetVariable }
let {width: w, height: h, title} = options;
// width -> w
// height -> h
// title -> title
alert(title); // Menu
alert(w); // 100
alert(h); // 200
let options = {
title: "Menu"
};
let {width: w = 100, height: h = 200, title} = options;
alert(title); // Menu
alert(w); // 100
alert(h); // 200
let title, width, height;
// error in this line
{title, width, height} = {title: "Menu", width: 200, height: 100};
// okay now
({title, width, height} = {title: "Menu", width: 200, height: 100});
alert( title ); // Menu
// we pass object to function
let options = {
title: "My menu",
items: ["Item1", "Item2"]
};
// ...and it immediately expands it to variables
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
// title, items – taken from options,
// width, height – defaults used
alert( `${title} ${width} ${height}` ); // My Menu 200 100
alert( items ); // Item1, Item2
}
showMenu(options);
// these two calls are almost the same:
func(1, 2, 3);
func.apply(context, [1, 2, 3])
// The only syntax difference between call and apply is that call expects a list of arguments, while apply takes an array-like object with them.
let args = [1, 2, 3];
func.call(context, ...args); // pass an array as list with spread operator
func.apply(context, args); // is same as using apply
// method borrowing (Use Array method with iterable)
function hash() {
alert( [].join.call(arguments) ); // 1,2
}
hash(1, 2);
function showArgs() {
alert( Array.prototype.join.call(arguments, " - ") );
}
setTimeout(() => user.sayHi(), 1000); // Hello, John!, but problematic
let user = {
firstName: "John"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // John
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Hello"); // Hello, John ("Hello" argument is passed to say)
say("Bye"); // Bye, John ("Bye" is passed to say)
function partial(func, ...argsBound) {
return function(...args) { // (*)
return func.call(this, ...argsBound, ...args);
}
}
// Usage:
let user = {
firstName: "John",
say(time, phrase) {
alert(`[${time}] ${this.firstName}: ${phrase}!`);
}
};
// add a partial method that says something now by fixing the first argument
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());
user.sayNow("Hello");
// Something like:
// [10:00] Hello, John!
function curry(f) {
return function(..args) {
// if args.length == f.length (as many arguments as f has),
// then pass the call to f
// otherwise return a partial function that fixes args as first arguments
};
}
let user = {};
Object.defineProperty(user, "name", {
value: "John"
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
- writable
- if true, can be changed, otherwise it’s read-only.
- enumerable
- if true, then listed in loops, otherwise not listed.
- configurable
- if true, the property can be deleted and these attributes can be modified, otherwise not.
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
}
};
alert(user.fullName); // John Smith
let animal = {
eats: true
};
let rabbit = {
jumps: true
};
rabbit.__proto__ = animal;
// animal has methods
let animal = {
walk() {
if (!this.isSleeping) {
alert(`I walk`);
}
},
sleep() {
this.isSleeping = true;
}
};
let rabbit = {
name: "White Rabbit",
__proto__: animal
};
// modifies rabbit.isSleeping
rabbit.sleep();
alert(rabbit.isSleeping); // true
alert(animal.isSleeping); // undefined (no such property in the prototype)
let animal = {
eats: true
};
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype = animal;
let rabbit = new Rabbit("White Rabbit"); // rabbit.__proto__ == animal
alert( rabbit.eats ); // true
function Rabbit() {}
// by default:
// Rabbit.prototype = { constructor: Rabbit }
let rabbit = new Rabbit(); // inherits from {constructor: Rabbit}
alert(rabbit.constructor == Rabbit); // true (from prototype)
function Rabbit() {}
Rabbit.prototype = {
jumps: true
};
let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // false
Prototypes
obj.prototype
- is used by children
obj.__proto__
- links to parents
__proto__
is deprecated in favor of Object.getPrototypeOf
/ Reflect.getPrototypeOf
and Object.setPrototypeOf
/ Reflect.setPrototypeOf
__proto__
getter function exposes the value of the internal Prototype of an object. For objects created using an object literal, this value is Object.prototype
.
The proto
setter allows the Prototype of an object to be mutated. The object must be extensible according to Object.isExtensible()
: if it is not, a TypeError
is thrown. The value provided must be an object
or null
. Providing any other value will do nothing.
x.__proto__
has a property setter, which takes the value as a prototype(null
, or object
), not the value itself. So, it's impossible to set the actual value to the property named __proto__
. To do that, we should create a plain object(without default Object.prototype
) as follows:
let obj = Object.create(null);
let key = prompt("What's the key?", "__proto__");
obj[key] = "some value";
alert(obj[key]); // "some value"
Inheritance
// Same Animal as before
function Animal(name) {
this.name = name;
}
// All animals can eat, right?
Animal.prototype.eat = function() {
alert(`${this.name} eats.`);
};
// Same Rabbit as before
function Rabbit(name) {
this.name = name;
}
Rabbit.prototype.jump = function() {
alert(`${this.name} jumps!`);
};
// setup the inheritance chain
Rabbit.prototype.__proto__ = Animal.prototype; // (*)
let rabbit = new Rabbit("White Rabbit");
rabbit.eat(); // rabbits can eat too
rabbit.jump();
Mixins
// mixin
let sayHiMixin = {
sayHi() {
alert(`Hello ${this.name}`);
},
sayBye() {
alert(`Bye ${this.name}`);
}
};
// usage:
class User {
constructor(name) {
this.name = name;
}
}
// copy the methods
Object.assign(User.prototype, sayHiMixin);
// now User can say hi
new User("Dude").sayHi(); // Hello Dude!
Please note that the call to the parent method super.say() from sayHiMixin looks for the method in the prototype of that mixin, not the class.
Error handling
try {
// code...
} catch (err) {
// error handling
} finally {
... execute always ...
}
throw <error object>
// The "pseudocode" for the built-in Error class defined by JavaScript itself
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (different names for different built-in error classes)
this.stack = <nested calls>; // non-standard, but most environments support it
}
}
window.location
Some keywords are optional, but there is a general guideline for compatibility:
window.location
- is preferred over
location
window.location.href = url
- When redirecting, is preferred over
window.location = url;
window.location.replace(url);
:; When redirecting, but want to remove the current page from the browser history, use
DOM
Page lifecycle
DOMContentLoaded
event triggers on document when DOM is ready. We can apply JavaScript to elements at this stage.- All scripts are executed except those that are external with
async
ordefer
- Images and other resources may still continue loading.
- All scripts are executed except those that are external with
load
event onwindow
triggers when the page and all resources are loaded. We rarely use it, because there’s usually no need to wait for so long.beforeunload
event onwindow
triggers when the user wants to leave the page.- If it returns a string, the browser shows a question whether the user really wants to leave or not.
unload
event on window triggers when the user is finally leaving, in the handler we can only do simple things that do not involve delays or asking a user. Because of that limitation, it’s rarely used.document.readyState
is the current state of the document, changes can be tracked in thereadystatechange
event:loading
– the document is loading.interactive
– the document is parsed, happens at about the same time asDOMContentLoaded
, but before it.complete
– the document and resources are loaded, happens at about the same time aswindow.onload
, but before it.
Promise
let promise = new Promise(function(resolve, reject) {
// executor (the producing code, "singer")
});
promise.then(
function(result) { /* handle a successful result */ },
function(error) { /* handle an error */ }
);
let promise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("Whoops!")), 1000);
});
// .catch(f) is the same as promise.then(null, f)
promise.catch(alert); // shows "Error: Whoops!" after 1 second
new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1000); // (*)
}).then(function(result) { // (**)
alert(result); // 1
return result * 2;
}).then(function(result) { // (***)
alert(result); // 2
return result * 2;
}).then(function(result) {
alert(result); // 4
return result * 2;
});
async function f() {
return 1;
}
f().then(alert); // 1
// works only inside async functions
let value = await promise;
Events
<input value="Click me" onclick="alert('Click!')" type="button">
<!-- -->
<script>
function countRabbits() {
for(let i=1; i<=3; i++) {
alert("Rabbit number " + i);
}
}
</script>
<input type="button" onclick="countRabbits()" value="Count rabbits!">
<!-- -->
<input id="elem" type="button" value="Click me">
<script>
elem.onclick = function() {
alert('Thank you');
};
</script>
element.addEventListener(event, handler[, phase]);
elem.addEventListener('click', {
handleEvent(event) {
alert(event.type + " at " + event.currentTarget);
}
});
- Capturing phase :: the event goes down to the element.
- Target phase :: the event reached the target element.
- Bubbling phase :: the event bubbles up from the element.
How-to
Parse URI
var parser = document.createElement('a');
parser.href = "http://example.com:3000/pathname/?search=test#hash";
parser.protocol; // => "http:"
parser.hostname; // => "example.com"
parser.port; // => "3000"
parser.pathname; // => "/pathname/"
parser.search; // => "?search=test"
parser.hash; // => "#hash"
parser.host; // => "example.com:3000"
Put <script>
- As a rule, only the simplest scripts are put into HTML.
- The benefit of a separate file is that the browser will download it and then store in its cache.
- If
src
is set, the script content is ignored. - The
type
andlanguage
attributes are not required.
- If the script is modular and does not rely on any scripts then use
async
. - If the script relies upon or is relied upon by another script then use
defer
. - If the script is small and is relied upon by an
async
script then use aninline script
with no attributes placed above theasync
scripts.
Add breadcrumb
let pathname = window.location.pathname;
let segments = pathname.split('/').filter(x => x != '');
let hrefs = segments.reduce((arr, seg) => {
let prev = arr.length > 0 ? arr[arr.length-1] : '';
let href = prev + '/' + seg;
arr.push(href);
return arr;
}, []);
let lis = segments.map((seg, i) => {
let li = document.createElement('li');
let a = document.createElement('a');
let t = document.createTextNode(seg);
a.setAttribute('href', hrefs[i]);
a.appendChild(t);
li.appendChild(a);
return li;
});
let breadcrumb = document.getElementById('breadcrumb');
let ol = breadcrumb.getElementsByTagName('ol')[0];
lis.forEach(li => ol.appendChild(li));
let aTags = ol.getElementsByTagName('a');
aTags[aTags.length-1].removeAttribute('href');