kota's memex

for

There are three forms of a for statement.

basic form

for (initialization; condition; increment)

in form

The in form enumerates the property names (keys) of an object. On each iteration, another property name string from the object is assigned to the variable. It is usually necessary to test object.hasOwnProperty(variable) to determine whether the property name is truly a member of the object or was found instead on the prototype chain.

for (myvar in obj) {
  if (obj.hasOwnProperty(myvar)) {
    ...
  }
}

of form (new in ES6)

The of for uses an object specific iterator method to loop over the "values" of an object. What "values" means depends on the object. For arrays this means all the values of the array, but not additional methods and properties. See below for creating your own iterable interface to customize the "values" for an object.

var arr = [3, 5, 7];
arr.foo = "hello";

for (var i in arr) {
  console.log(i); // "0", "1", "2", "foo"
}

for (var i of arr) {
  console.log(i); // "3", "5", "7"
  // note that "hello" is missing
}

while

While loops have a condition and will execute as long as the condition is true. Just like other languages.

let number = 0;
while (number <= 12) {
  console.log(number);
  number = number + 2;
}

do

A do loop is just like a while loop except that it will always execute at least once, the first time the condition isn't tested. Ths syntax is kinda jank, the test appears AFTER the body. I guess to reflect that, but it really harms readability.

do {
  yourName = prompt("Who are you?");
} while (!yourName);
console.log(yourName);

This example illustrates the one fucking time this miiight be used. If you had just used a while loop it would fail on account of yourName not yet being defined, but because the do loop executes once before testing you avoid that issue.

conditional (ternary)

console.log(true ? 1 : 2); - 1 console.log(false ? 1 : 2); - 2

iterator interface

The object given to a for/of loop is expected to be iterable. This means it has a method named with the symbol Symbol.iterator.

When called, that method should return an object that provides a second interface, iterator. This is the actual thing that iterates. It has a next method that returns the next value, if there is one, and a done property, which should be true when there are no more results. Note that next, value, and done property names are plain strings, not symbols. Only Symbol.iterator, which is likely to be added by a lot of different objects, is an actual symbol.

let okIterator = "OK"[Symbol.iterator]()
console.log(okIterator.next()) // { value: 'O', done: false }
console.log(okIterator.next()) // { value: 'K', done: false }
console.log(okIterator.next()) // { value: undefined, done: true }

Here's an implementation of a 2D matrix which can be iterated over:

class Matrix {
	constructor(width, height, element = (x, y) => undefined) {
		this.width = width
		this.height = height
		this.content = []

		for (let y = 0; y < height; y++) {
			for (let x = 0; x < width; x++) {
				this.content[y * width + x] = element(x, y)
			}
		}
	}

	get(x, y) {
		return this.content[y * this.width + x]
	}

	set(x, y, value) {
		this.content[y * this.width + x] = value
	}

	[Symbol.iterator]() {
		return new MatrixIterator(this)
	}
}

class MatrixIterator {
	constructor(matrix) {
		this.x = 0
		this.y = 0
		this.matrix = matrix
	}

	next() {
		if (this.y == this.matrix.height) return { done: true }

		let value = { x: this.x, y: this.y, value: this.matrix.get(this.x, this.y) }
		this.x++

		if (this.x == this.matrix.width) {
			this.x = 0
			this.y++
		}
		return { value, done: false }
	}
}

let matrix = new Matrix(2, 2, (x, y) => `value ${x},${y}`)
for (let { x, y, value } of matrix) {
	console.log(x, y, value)
}