If You Dont Know How to Wrestle You Dont Know How to Fight

You Don't Know JS Even so: Get Started - second Edition

Chapter 3: Digging to the Roots of JS

If you lot've read Chapters 1 and ii, and taken the time to digest and percolate, you're hopefully starting to become JS a little more. If you skipped/skimmed them (especially Chapter 2), I recommend going dorsum to spend some more time with that material.

In Chapter 2, we surveyed syntax, patterns, and behaviors at a high level. In this chapter, our attention shifts to some of the lower-level root characteristics of JS that underpin virtually every line of code we write.

Be aware: this chapter digs much deeper than you're likely used to thinking about a programming linguistic communication. My goal is to help y'all capeesh the cadre of how JS works, what makes it tick. This chapter should begin to answer some of the "Why?" questions that may exist cropping upwardly as y'all explore JS. Withal, this textile is still not an exhaustive exposition of the linguistic communication; that'southward what the rest of the book series is for! Our goal here is still merely to get started, and get more comfortable with, the feel of JS, how it ebbs and flows.

Don't run and then speedily through this fabric that you get lost in the weeds. As I've said a dozen times already, take your time. Even however, you'll probably end this chapter with remaining questions. That'south OK, considering there's a whole volume series ahead of you lot to go on exploring!

Iteration

Since programs are essentially congenital to process data (and make decisions on that information), the patterns used to footstep through the data have a big 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 information source—to progressively handle the drove of data past processing the first function, then the next, and so on, rather than treatment the unabridged set all at in one case.

Imagine a data structure that represents a relational database SELECT query, which typically organizes the results as rows. If this query had only i or a couple of rows, you could handle the entire outcome set up at once, and assign each row to a local variable, and perform whatever operations on that data that were appropriate.

But if the query has 100 or ane,000 (or more!) rows, you'll demand iterative processing to bargain with this data (typically, a loop).

The iterator pattern defines a data structure chosen an "iterator" that has a reference to an underlying data source (similar the query result rows), which exposes a method like side by side(). Calling side by side() returns the next piece of data (i.due east., a "tape" or "row" from a database query).

You don't always know how many pieces of data that you will need to iterate through, and so the pattern typically indicates completion past some special value or exception once you iterate through the unabridged set and go by the end.

The importance of the iterator pattern is in adhering to a standard way of processing data iteratively, which creates cleaner and easier to sympathise code, as opposed to having every data construction/source define its own custom way of handling its information.

After many years of diverse JS community efforts around mutually agreed-upon iteration techniques, ES6 standardized a specific protocol for the iterator pattern straight in the language. The protocol defines a adjacent() method whose return is an object called an iterator result; the object has value and washed properties, where done is a boolean that is false until the iteration over the underlying data source is complete.

Consuming Iterators

With the ES6 iteration protocol in place, it'southward workable to consume a data source one value at a fourth dimension, checking afterwards each side by side() call for done to be true to finish the iteration. But this approach is rather manual, and then ES6 also included several mechanisms (syntax and APIs) for standardized consumption of these iterators.

Ane such mechanism is the for..of loop:

              // given an iterator of some information source:              var              it              =              /* .. */              ;              // loop over its results one at a time              for              (              allow              val              of              it              )              {              console              .              log              (              `Iterator value:                                  ${                  val                  }                `              )              ;              }              // Iterator value: ..              // Iterator value: ..              // ..            
Annotation:
Nosotros'll omit the transmission loop equivalent here, just it's definitely less readable than the for..of loop!

Another mechanism that's oftentimes used for consuming iterators is the ... operator. This operator actually has two symmetrical forms: spread and rest (or gather, as I adopt). The spread class is an iterator-consumer.

To spread an iterator, you have to have something to spread information technology into. There are two possibilities in JS: an array or an statement list for a function call.

An array spread:

              // spread an iterator into an array,              // with each iterated value occupying              // an assortment element position.              var              vals              =              [              ...it              ]              ;            

A function call spread:

              // spread an iterator into a part,              // call with each iterated value              // occupying an argument position.              doSomethingUseful              (              ...it              )              ;            

In both cases, the iterator-spread class of ... follows the iterator-consumption protocol (the aforementioned as the for..of loop) to retrieve all available values from an iterator and identify (aka, spread) them into the receiving context (array, statement list).

Iterables

The iterator-consumption protocol is technically defined for consuming iterables; an iterable is a value that tin be iterated over.

The protocol automatically creates an iterator instance from an iterable, and consumes just that iterator example to its completion. This means a single iterable could be consumed more than than once; each time, a new iterator instance would exist created and used.

So where exercise we notice iterables?

ES6 divers the basic data structure/drove types in JS as iterables. This includes strings, arrays, maps, sets, and others.

Consider:

              // an assortment is an iterable              var              arr              =              [              10              ,              20              ,              30              ]              ;              for              (              let              val              of              arr              )              {              console              .              log              (              `Assortment value:                                  ${                  val                  }                `              )              ;              }              // Array value: 10              // Array value: twenty              // Array value: xxx            

Since arrays are iterables, nosotros tin can shallow-copy an array using iterator consumption via the ... spread operator:

              var              arrCopy              =              [              ...arr              ]              ;            

We tin can as well iterate the characters in a cord one at a time:

              var              greeting              =              "Hullo world!"              ;              var              chars              =              [              ...greeting              ]              ;              chars              ;              // [ "H", "e", "fifty", "50", "o", " ",              //   "w", "o", "r", "l", "d", "!" ]            

A Map data construction uses objects every bit keys, associating a value (of whatever type) with that object. Maps have a different default iteration than seen here, in that the iteration is not just over the map's values just instead its entries. An entry is a tuple (ii-element assortment) including both a key and a value.

Consider:

              // given two DOM elements, `btn1` and `btn2`              var              buttonNames              =              new              Map              (              )              ;              buttonNames              .              set              (              btn1              ,              "Button 1"              )              ;              buttonNames              .              prepare              (              btn2              ,              "Push two"              )              ;              for              (              allow              [              btn              ,              btnName              ]              of              buttonNames              )              {              btn              .              addEventListener              (              "click"              ,              office              onClick              (              )              {              console              .              log              (              `Clicked                                  ${                  btnName                  }                `              )              ;              }              )              ;              }            

In the for..of loop over the default map iteration, nosotros use the [btn,btnName] syntax (called "array destructuring") to break down each consumed tuple into the respective key/value pairs (btn1 / "Button ane" and btn2 / "Push button 2").

Each of the built-in iterables in JS expose a default iteration, one which probable matches your intuition. Just you can too cull a more specific iteration if necessary. For example, if we want to consume only the values of the in a higher place buttonNames map, we can telephone call values() to become a values-only iterator:

              for              (              let              btnName              of              buttonNames              .              values              (              )              )              {              console              .              log              (              btnName              )              ;              }              // Push i              // Push button ii            

Or if we want the alphabetize and value in an array iteration, we can make an entries iterator with the entries() method:

              var              arr              =              [              10              ,              20              ,              30              ]              ;              for              (              let              [              idx              ,              val              ]              of              arr              .              entries              (              )              )              {              console              .              log              (              `[                  ${                  idx                  }                ]:                                  ${                  val                  }                `              )              ;              }              // [0]: 10              // [ane]: twenty              // [2]: xxx            

For the most part, all born iterables in JS have three iterator forms available: keys-merely (keys()), values-merely (values()), and entries (entries()).

Beyond merely using congenital-in iterables, you can as well ensure your own information structures attach 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 lawmaking that is overall more than readily recognizable and readable.

Annotation:
You may have noticed a nuanced shift that occurred in this discussion. Nosotros started by talking about consuming iterators, but then switched to talking about iterating over iterables. The iteration-consumption protocol expects an iterable, only the reason we can provide a direct iterator is that an iterator is just an iterable of itself! When creating an iterator instance from an existing iterator, the iterator itself is returned.

Closure

Possibly without realizing it, nearly every JS programmer has fabricated apply of closure. In fact, closure is one of the most pervasive programming functionalities beyond a majority of languages. It might even exist every bit important to understand as variables or loops; that's how key it is.

However it feels kind of hidden, most magical. And it'southward frequently talked near in either very abstract or very informal terms, which does little to help the states nail down exactly what it is.

Nosotros need to exist able to recognize where closure is used in programs, as the presence or lack of closure is sometimes the crusade of bugs (or even the cause of functioning problems).

So let'south define closure in a businesslike and physical manner:

Closure is when a function remembers and continues to access variables from exterior its scope, even when the function is executed in a different telescopic.

We see two definitional characteristics hither. First, closure is part of the nature of a function. Objects don't get closures, functions practise. 2nd, to find a closure, you must execute a office in a different scope than where that function was originally defined.

Consider:

              office              greeting              (              msg              )              {              return              function              who              (              name              )              {              panel              .              log              (              `                  ${                  msg                  }                ,                                  ${                  name                  }                !`              )              ;              }              ;              }              var              hello              =              greeting              (              "Hi"              )              ;              var              hello              =              greeting              (              "Hullo"              )              ;              howdy              (              "Kyle"              )              ;              // Hello, Kyle!              hello              (              "Sarah"              )              ;              // Hello, Sarah!              hi              (              "Grant"              )              ;              // How-do-you-do, Grant!            

Beginning, the greeting(..) outer office is executed, creating an instance of the inner function who(..); that function closes over the variable msg, which is the parameter from the outer scope of greeting(..). When that inner part is returned, its reference is assigned to the hello variable in the outer telescopic. Then we phone call greeting(..) a second time, creating a new inner role example, with a new closure over a new msg, and return that reference to be assigned to hello.

When the greeting(..) function finishes running, normally we would expect all of its variables to be garbage collected (removed from retentivity). Nosotros'd expect each msg to go away, just they don't. The reason is closure. Since the inner function instances are nonetheless alive (assigned to hullo and hello, respectively), their closures are all the same preserving the msg variables.

These closures are not a snapshot of the msg variable'due south value; they are a straight link and preservation of the variable itself. That means closure can actually notice (or brand!) updates to these variables over time.

              function              counter              (              stride              =              1              )              {              var              count              =              0              ;              return              function              increaseCount              (              )              {              count              =              count              +              pace              ;              return              count              ;              }              ;              }              var              incBy1              =              counter              (              1              )              ;              var              incBy3              =              counter              (              three              )              ;              incBy1              (              )              ;              // i              incBy1              (              )              ;              // 2              incBy3              (              )              ;              // 3              incBy3              (              )              ;              // 6              incBy3              (              )              ;              // nine            

Each instance of the inner increaseCount() function is closed over both the count and step variables from its outer counter(..) function'southward scope. step remains the same over time, but count is updated on each invocation of that inner function. Since closure is over the variables and not just snapshots of the values, these updates are preserved.

Closure is most common when working with asynchronous code, such as with callbacks. Consider:

              office              getSomeData              (              url              )              {              ajax              (              url              ,              function              onResponse              (              resp              )              {              console              .              log              (              `Response (from                                  ${                  url                  }                ):                                  ${                  resp                  }                `              )              ;              }              )              ;              }              getSomeData              (              "https://some.url/wherever"              )              ;              // Response (from https://some.url/wherever): ...            

The inner function onResponse(..) is closed over url, and thus preserves and remembers it until the Ajax phone call returns and executes onResponse(..). Even though getSomeData(..) finishes right away, the url parameter variable is kept alive in the closure for equally long every bit needed.

Information technology's not necessary that the outer scope be a function—information technology usually is, just not ever—just that there be at least one variable in an outer scope accessed from an inner function:

              for              (              permit              [              idx              ,              btn              ]              of              buttons              .              entries              (              )              )              {              btn              .              addEventListener              (              "click"              ,              role              onClick              (              )              {              panel              .              log              (              `Clicked on push (                  ${                  idx                  }                )!`              )              ;              }              )              ;              }            

Considering this loop is using let declarations, each iteration gets new cake-scoped (aka, local) idx and btn variables; the loop also creates a new inner onClick(..) function each time. That inner function closes over idx, preserving it for as long as the click handler is gear up on the btn. And then when each button is clicked, its handler tin can print its associated index value, because the handler remembers its respective idx variable.

Remember: this closure is not over the value (like 1 or 3), only over the variable idx itself.

Closure is ane of the most prevalent and important programming patterns in any language. But that's especially true of JS; information technology'south difficult to imagine doing annihilation useful without leveraging closure in one manner or some other.

If you're still feeling unclear or shaky nigh closure, the bulk of Book 2, Scope & Closures is focused on the topic.

this Keyword

1 of JS'south near powerful mechanisms is also i of its most misunderstood: the this keyword. One mutual misconception is that a part'south this refers to the part itself. Because of how this works in other languages, another misconception is that this points the instance that a method belongs to. Both are incorrect.

As discussed previously, when a function is divers, it is attached to its enclosing telescopic via closure. Scope is the set of rules that controls how references to variables are resolved.

Simply functions too have another characteristic besides their scope that influences what they can access. This characteristic is best described every bit an execution context, and it's exposed to the function via its this keyword.

Scope is static and contains a fixed ready of variables bachelor at the moment and location you define a function, simply a office'south execution context is dynamic, entirely dependent on how it is called (regardless of where it is defined or even called from).

this is not a stock-still characteristic of a function based on the role's definition, but rather a dynamic characteristic that'south adamant each time the function is called.

1 way to call up almost the execution context is that information technology'due south a tangible object whose properties are made available to a part while it executes. Compare that to scope, which tin can also exist idea of as an object; except, the telescopic object is subconscious inside the JS engine, it's always the same for that function, and its properties take the class of identifier variables bachelor inside the function.

              function              classroom              (              teacher              )              {              render              part              study              (              )              {              panel              .              log              (              `                  ${                  instructor                  }                                says to study                                  ${                  this                  .                  topic                  }                `              )              ;              }              ;              }              var              assignment              =              classroom              (              "Kyle"              )              ;            

The outer classroom(..) function makes no reference to a this keyword, so it's just like any other function we've seen so far. Simply the inner study() office does reference this, which makes it a this-aware function. In other words, it's a function that is dependent on its execution context.

NOTE:
report() is as well closed over the teacher variable from its outer scope.

The inner study() part returned past classroom("Kyle") is assigned to a variable called assignment. So how tin assignment() (aka study()) be called?

              consignment              (              )              ;              // Kyle says to written report undefined  -- Oops :(            

In this snippet, we call assignment() as a evidently, normal function, without providing information technology any execution context.

Since this program is not in strict way (meet Affiliate 1, "Strictly Speaking"), context-aware functions that are called without whatever context specified default the context to the global object (window in the browser). As in that location 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"              ,              consignment:              assignment              }              ;              homework              .              assignment              (              )              ;              // Kyle says to study JS            

A re-create of the assignment function reference is set as a property on the homework object, so information technology's called every bit homework.assignment(). That ways the this for that function telephone call will be the homework object. Hence, this.topic resolves to "JS".

Lastly:

              var              otherHomework              =              {              topic:              "Math"              }              ;              assignment              .              telephone call              (              otherHomework              )              ;              // Kyle says to study Math            

A 3rd way to invoke a role is with the call(..) method, which takes an object (otherHomework here) to apply for setting the this reference for the function call. The holding reference this.topic resolves to "Math".

The same context-enlightened function invoked three different ways, gives different answers each time for what object this volition reference.

The benefit of this-enlightened functions—and their dynamic context—is the ability to more flexibly re-utilise a single function with information from unlike objects. A function that closes over a scope can never reference a unlike telescopic or gear up of variables. But a function that has dynamic this context awareness tin exist quite helpful for certain tasks.

Prototypes

Where this is a characteristic of function execution, a paradigm is a feature of an object, and specifically resolution of a property access.

Think about a prototype as a linkage between two objects; the linkage is hidden behind the scenes, though there are means to expose and observe it. This paradigm linkage occurs when an object is created; it'due south linked to another object that already exists.

A series of objects linked together via prototypes is chosen the "prototype concatenation."

The purpose of this prototype linkage (i.e., from an object B to another object A) is and so that accesses against B for properties/methods that B does non have, are delegated to A to handle. Delegation of belongings/method admission allows two (or more than!) objects to cooperate with each other to perform a chore.

Consider defining an object as a normal literal:

              var              homework              =              {              topic:              "JS"              }              ;            

The homework object but has a single property on information technology: topic. Even so, its default paradigm linkage connects to the Object.prototype object, which has common built-in methods on it similar 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 take a toString() method defined; the delegation invokes Object.prototype.toString() instead.

Object Linkage

To ascertain 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 so returns the newly created (and linked!) object.

Figure iv shows how the three objects (otherHomework, homework, and Object.epitome) are linked in a prototype chain:

Prototype chain with 3 objects

Fig. 4: Objects in a epitome chain

Delegation through the prototype chain only applies for accesses to lookup the value in a holding. If you assign to a property of an object, that will use directly to the object regardless of where that object is prototype linked to.

TIP:
Object.create(aught) creates an object that is not prototype linked anywhere, so information technology'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" -- non "Math"            

The assignment to topic creates a property of that name directly on otherHomework; there'south no effect on the topic belongings on homework. The next argument so accesses otherHomework.topic, and we see the non-delegated answer from that new holding: "Math".

Figure 5 shows the objects/properties later on the assignment that creates the otherHomework.topic property:

3 objects linked, with shadowed property

Fig. 5: Shadowed property 'topic'

The topic on otherHomework is "shadowing" the belongings of the same name on the homework object in the chain.

Annotation:
Another frankly more than convoluted but perhaps still more common mode of creating an object with a prototype linkage is using the "prototypal class" blueprint, from earlier class (see Chapter 2, "Classes") was added in ES6. We'll cover this topic in more item in Appendix A, "Prototypal 'Classes'".

this Revisited

We covered the this keyword before, but its true importance shines when because how information technology powers image-delegated role calls. Indeed, one of the primary reasons this supports dynamic context based on how the function is chosen is so that method calls on objects which delegate through the paradigm chain nonetheless 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 report JS              var              mathHomework              =              Object              .              create              (              homework              )              ;              mathHomework              .              topic              =              "Math"              ;              mathHomework              .              report              (              )              ;              // Delight study Math            

The two objects jsHomework and mathHomework each paradigm link to the single homework object, which has the study() function. jsHomework and mathHomework are each given their own topic belongings (run into Figure 6).

4 objects prototype linked

Fig. half dozen: Two objects linked to a mutual parent

jsHomework.written report() delegates to homework.study(), but its this (this.topic) for that execution resolves to jsHomework because of how the part is called, so this.topic is "JS". Similarly for mathHomework.study() delegating to homework.study() but yet resolving this to mathHomework, and thus this.topic as "Math".

The preceding code snippet would be far less useful if this was resolved to homework. All the same, in many other languages, it would seem this would be homework because the report() method is indeed defined on homework.

Unlike many other languages, JS's this being dynamic is a critical component of assuasive prototype delegation, and indeed grade, to work as expected!

Asking "Why?"

The intended have-away from this affiliate is that at that place's a lot more to JS under the hood than is obvious from glancing at the surface.

As you are getting started learning and knowing JS more closely, ane of the most of import skills you can do and bolster is curiosity, and the fine art of asking "Why?" when you see something in the language.

Even though this chapter has gone quite deep on some of the topics, many details have still been entirely skimmed over. In that location'south much more to learn here, and the path to that starts with y'all asking the right questions of your code. Asking the right questions is a critical skill of condign a better programmer.

In the final chapter of this book, we're going to briefly await at how JS is divided, as covered across the residual of the You Don't Know JS Yet book series. As well, don't skip Appendix B of this book, which has some practice code to review some of the primary topics covered in this volume.

robertsoperepien.blogspot.com

Source: https://github.com/getify/You-Dont-Know-JS/blob/2nd-ed/get-started/ch3.md

0 Response to "If You Dont Know How to Wrestle You Dont Know How to Fight"

Postar um comentário

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel