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 accesswelcomeat 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) ); // 6func.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 = Adminlet 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, sayHifunction makeClass(phrase) {
// declare a class and return it
return class {
sayHi() {
alert(phrase);
};
};
}
let User = makeClass("Hello");
new User().sayHi(); // Helloclass 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 functionclass Rabbit {}
let rabbit = new Rabbit();
// is it an object of Rabbit class?
alert( rabbit instanceof Rabbit ); // trueimports, 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.aliasfield 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
Objectis 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 undefinedTo 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(); // Ilyafunction 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); // JohnReference
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); // falsealert, 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 pressedencodeURI, encodeURIComponent, decodeURI, decodeURIComponent
escape,unescapeare deprecated- The difference between
-URIand-URIComponentis 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==. nullandundefinedequal==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); // 200let 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 Smithlet 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); // falsePrototypes
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
DOMContentLoadedevent 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
asyncordefer - Images and other resources may still continue loading.
- All scripts are executed except those that are external with
loadevent onwindowtriggers when the page and all resources are loaded. We rarely use it, because there’s usually no need to wait for so long.beforeunloadevent onwindowtriggers 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.
unloadevent 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.readyStateis the current state of the document, changes can be tracked in thereadystatechangeevent: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
srcis set, the script content is ignored. - The
typeandlanguageattributes 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
asyncscript then use aninline scriptwith no attributes placed above theasyncscripts.
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');