links: JS MOC
Iteration
Since programs are built to process data(and make decisions on that data), the patterns used to step through the data has high impact on the program’s readability.
The iterator pattern has been around for decades, and suggests a standardized approach to consuming data from a source one chunk at a time. The idea is that it’s more common and helpful to iterate the data source — to progressively handle the collection of data by processing the first part, and then the next, and so on rather than handling the entire set all at once.
The iterator pattern defines a data structure called an “iterator” that has a reference to an underlying data source(like the database query result rows) which exposes a method like next(). Calling next() returns the next piece of data (i.e., a “record” or “row” from a database query)
You don’t always know how many pieces of data that you will need to iterate through, so the pattern typically indicates completion by special value or exception once you iterate through the entire set and go past the end
The importance of iterator pattern is in adhering to a standard way of processing data iteratively, which creates cleaner and easier to understand code, as supposed to having every data structure/ source define its own custom way of handling its data
ES6 defined a specific protocol for the iterator pattern in the language. The protocol defines the next() method whose return is an object called an iterator result; the object has value and done properties, where done is a boolean that is false until the iteration of data source is complete
Consuming Iterators
With the ES6 iteration protocol in place, it’s workable to consume a data source one value at a time, checking after each next() for done to be true to stop iteration. But this approach is rather manual, so ES6 also added several mechanisms (syntax and API’s) for standardized consumption of these iterators.
one such mechanism is the for...of loop
// given an iterator of some data source:
var it = /*..*/
// loop over its results one at a time
for(let val of it) {
console.log(`Iterator value: ${ val }`)
}
// Iterator value: ..
// Iterator value: ..
// ..
Another operator that’s often used for consuming iterators is the ... operator. This operator actually has two symmetrical forms: spread and rest (or gather). The spread form is an iterator consumer
To spread an iterator, you have to have something to spread it into. There are two possibilities in JS: an array or an argument list of a function call.
An array Spread:
// spread an iterator into an array,
// with each iterated value occupying
// an array element position.
var vals = [...it]
A function call Spread:
// spread an iterator into a function,
// call with each iterated value
// occupying an argument position.
doSomethingUseful(...it)
In both cases, the iterator-spread form of ... follows the iterator-consumption protocol (the same as the for..of loop) to retrieve all available values from an iterator and place (aka, spread) them into the receiving context (array, argument list)
Iterables
The iterator-consumption protocol is technically defined for consuming iterables; an iterable is a value that can be iterated over
The protocol automatically creates an iterator instance from an iterable and consumes just that iterator instance to its completion. This means a single iterable can be consumed more than once; each time, a new iterator instance would be created and used.
Read more about Iterators and Iterables
ES6 defined basic data structure/collection types in JS as iterables This includes strings, arrays, maps, sets and others
A Map data structure uses objects as keys, associating a value(of any type) with that object. Maps have a different default iteration that seen here, in that the iteration is not just over map’s values but instead its entries. An entry is a tuple(2 element array) including both a key and value
Consider:
// given two DOM elements, `btn1` and `btn2`
var buttonNames = new Map()
buttonNames.set(btn1, "Button 1")
buttonNames.set(btn2, "Button 2")
for(let [btn, btnName] of buttonNames) {
btn.addEventListener('click', function onClick() {
console.log(`Clicked: ${btnName}`)
})
}
In the for..of loop over the default map iteration, we use the [btn, btnName] syntax (called array destructuring) to break down each consumed tuple into the respective key value pairs (btn1 / "Button 1")
Each of the built-in JS iterables expose a default iteration, one which likely matches your intuition. But you can also choose a more specific iteration if necessary. For example, if we want to consume only the values of the above buttonNames map, we can call values() to get values only iterator.
for(let btnName of buttonNames.values()) {
console.log(btnName)
}
// Button 1
// Button 2
Or if we want index and value of array iteration, we can make entries iterator with the entries() method
var arr = [10, 20, 30]
for(let [idx, val] of arr.entries()) {
console.log(`[${ idx }]: ${ val }`)
}
// [0]: 10
// [1]: 20
// [2]: 30
For the most all built-in iterables in JS have three iterator forms available: keys-only(keys() 🔑 ), values-only (values()) and entries (entries()).
Beyond just using built-in iterables you can also ensure your own data structures adhere to the iteration protocol; doing so means you opt into the ability to consume your data with for..of loops and the ... operator. “Standardizing” on this protocol means code that is overall more readily recognizable and readable.
Closure
Closure is a fundamental topic and important to understand.
We need to be able to recognize where closure is used in programs, as the presence or lack of closure is sometimes the cause of the bugs (or even the cause of performance issues)
Here’s a pragmatic and concrete definition of closure
Closure is when a function remembers and continues to access variables from outside it’s scope, even when the function is executed in a different scope
We see two definitional characteristics here. First, closure is part of the nature of a function. Objects don’t get closures, functions do. Second, to observe a closure you must execute a function in different scope than where that function was originally defined.
Consider:
function greeting(msg) {
return function who(name) {
console.log(`${ msg }, ${ name }!`)
}
}
var hello = greeting('Hello')
var howdy = greeting('Howdy')
hello('Kyle')
// Hello, Kyle!
hello('Subramanya')
// Hello, Subramanya!
howdy('Mamatha')
//Howdy, Mamatha!
Another example
function counter(step = 1) {
var count = 0
return function increaseCount() {
count = count + step
return count
}
}
var incBy1 = counter(1)
var incBy3 = counter(3)
incBy1() // 1
incBy1() // 2
incBy3() // 3
incBy3() // 6
incBy3() // 9
Closure is most common when working with asynchronous code, such as with callbacks.
Consider:
function getSomeData(url) {
ajax(url, function onResponse(resp) {
console.log(`Response (from ${ url }): ${ resp }`)
})
}
getSomeData("https://some.url/wherever");
// Response (from https://some.url/wherever): ...
It’s not necessary that the outer scope to be a function. Consider:
for(let [idx, btn] of buttons.entries()) {
btn.addEventListener('click', function onClick() {
console.log(`Clicked on button (${ idx })!`)
})
}
this keyword
this is one of the JS’s most powerful mechanism and also the most misunderstood. One misconception is that a function’s this refers to the function itself. Because of how this works in other languages, another misconception is that this points to the instance that a method belongs to. Both are incorrect
When a function is defined, it is attached to it’s enclosing scope via closure. Scope is the set of rules that controls how references to variables are resolved.
But functions also have another characteristic besides their scope that influences what they can access. This characteristic is best described as an execution context, and it’s exposed to the function via this keyword.
Scope is static and contains a fixed set of variables available at the moment and location you define a function, but a function’s execution context is dynamic, entirely dependent on how it’s called (regardless of where it is defined or even called from)
this is not a fixed characteristic of a function based on the function’s definition, but rather a dynamic characteristic that’s determined each time the function is called.
One way to think of execution context is that it’s a tangible object whose properties are made available while it executes. Compare that to scope, which can also be thought of an object; except the scope object is hidden inside the JS engine, it’s always same for that function, and it’s properties take the form of identifier variables available inside the function.
function classroom(teacher) {
return function study() {
console.log(`${ teacher } says to study ${ this.topic }`)
}
}
var assignment = classroom("Chakravarthy")
the outer classroom(..) makes no reference to a this keyword, so it’s just like any other function we have seen so far. But the inner study(..) function does reference this, which makes it a this aware function. In other words it’s function that is dependent on it’s execution context.
The inner study() function returned by classroom(“Kyle”) is assigned to a variable called assignment. So how can assignment() (aka study()) be called?
assignment()
// Subramanya says to study undefined -- Oops :(
In this snippet, we call assignment() as a plain, normal function, without providing it any execution context.
Since this program is not in strict mode (see Chapter 1, “Strictly Speaking”), context-aware functions that are called without any context specified default the context to the global object (window in the browser). As there is no global variable named topic (and thus no such property on the global object), this.topic resolves to undefined.
Now consider:
var homework = {
topic: "JS"
assignment: assignment
}
homework.assignment()
// Subramanya says to study JS
A copy of the assignment function reference is set as a property on the homework object, and then it’s called as homework.assignment(). That means the this for that function call will be the homework object. Hence, this.topic resolves to "JS".
Lastly:
var otherHomework = {
topic: "Math"
};
assignment.call(otherHomework);
// Subramanya says to study Math
A third way to invoke function is with the call(..) method, which takes an object (otherHomework here) to use for setting the this reference for the function call. The property reference this.topic resolves to "Math".
The benefit of this aware functions—and their dynamic context— is the ability to more flexibly re-use a single function with data from different objects. A function that closes over scope can never reference a different scope or set of variables. But a function that has dynamic this context awareness can be helpful for certain tasks.
Prototypes
Where this is a characteristic of function execution, a prototype is a characteristic of an object, and specifically resolution of property access.
Think about prototype as a linkage between two objects; the linkage is hidden behind the scenes, though there are ways to expose and observe it. This prototype linkage occurs when an object is created; it’s linked to another object that already exists
A series of objects linked together via prototypes is called the prototype chain.
The purpose of this prototype linkage (i.e., from an object B to another object A ) is so that accesses against B for properties/methods that B does not have, are delegated to A to handle. Delegation of property/method access allows two or more objects cooperate with each other to perform a task.
Consider defining an object as a normal literal:
var homework = {
topic: 'JS'
}The homework object only has a single property on it: topic. However it’s default prototype linkage connects to the Object.prototype which has common built-in methods on it like toString() and valueOf() among others.
We can observe this prototype linkage delegation from homework to Object.prototype:
homework.toString() // [object Object]homework.toString() works even though homework doesn’t have a toString() method defined; the delegation invokes Object.prototype.toString() instead.
Object Linkage
To define an object prototype linkage, you can create the object using the Object.create(..) utility:
var homework = {
topic: 'JS'
}
var otherHomework = Object.create(homework)
otherHomework.topic // 'JS'The first argument to Object.create(..) specifies an object to link the newly created object to, and then returns the newly created (and linked!) object.
Delegation through the prototype chain only applies for accesses to lookup the value in a property. if you assign to a property of an object, that will apply directly to the object, regardless of where that object is prototype linked to.
| TIP: |
|---|
Object.create(null) creates an object that is not prototype linked anywhere, so it’s purely just a standalone object. In some circumstances that may be preferable. |
Consider:
homework.topic
// 'JS'
otherHomework.topic
// 'JS'
otherHomework.topic = 'Math'
otherHomework.topic
// 'Math'
homework.topic
// 'JS' -- not 'Math'The assignment to topic creates a property of that name directly on otherHomework; there is no effect on the topic property on homework. The next statement then accesses otherHomework.topic, and we see the non delegated answer from that new property: ‘Math’
The topic on otherHomework is “shadowing” the property of the same name on the homework object in the chain.
this revisited
this keyword true importance shines when considering how it powers prototype-delegated function calls. Indeed one of the main reasons this supports dynamic context based on how the function is called is so that the method calls on objects which delegate through the prototype chain still maintain the expected this.
Consider:
var homework = {
study() {
console.log(`Please study ${ this.topic }`)
}
}
var jsHomework = Object.create(homework)
jsHomework.topic = 'JS'
jsHomework.study()
// Please study JS
var mathHomework = Object.create(homework)
mathHomework.topic = 'Math'
mathHomework.study()
// Please study Math
The two objects jsHomework and mathHomework each prototype link to the single homework object, which has the study() function. jsHomework and mathHomework each given their own topic property.
jsHomework.study() delegates to homework.study(), but it’s this (this.topic) for it’s execution resolves to jsHomework because of how the function is called, so this.topic is “JS”.
The preceding code snippet would be far less useful if this was resolved to homework. Yet, in many other languages, it would seem this would be homework because the study() method is indeed defined on homework.
Unlike many other languages, JS’s this being dynamic is a critical component of allowing prototype delegation, and indeed class, to work as expected.
tags: fundamentals , javascript