Reliable JavaScript. Lawrence Spencer
Чтение книги онлайн.
Читать онлайн книгу Reliable JavaScript - Lawrence Spencer страница 8
x
, y
, and z
properties can also function as an object with x
and y
properties.
You could also produce the objects with a constructor function, which looks completely different but has the same result:
This is called duck typing, after the saying, “If it looks like a duck, walks like a duck and quacks like a duck, it is a duck.” In JavaScript, ducks are some of your best friends. It is possible to distinguish the cases thus:
However, there is almost never a reason to do so. A C# or Java programmer might attempt to learn whether an object is up to snuff through such inspections, but the JavaScript way is to simply check for the existence of the properties:
or
Duck typing is not sloppiness. It is an important way to give a component more reach.
NOTE Embrace duck typing. It allows a little code to accommodate a wide range of objects.
If you read Listing 1-1 with unusual attention, you might have wondered how the inner line
function manages to access the private variables of the outer rj3.svg.line
after the outer function has returned. Programmers from other languages might expect the variables getX, getY, and interpolate to pop off the stack once control exits the function that declared them. And so they would, except for one thing: JavaScript’s concept of closures.
We said earlier that when you call rj3.svg.line()
, it returns the inner line
function. There's more to it than that. It actually returns a closure, which you can think of as an object that from the outside looks like the function (inner line
), but on the inside also remembers the environment that prevailed when the function was created (the variables getX, getY and interpolate). You call inner line
’s functions as you normally would, but they are aware of line
’s original environment.
NOTE Closures are a very powerful design element in JavaScript. Every function is a closure.
Consider once more the call
statements in the while
loop:
What does getX.call(this,d,i)
really do? In English, it calls the getX
function, pretending that it is a member of the object this (more on that in a moment) and passing the arguments d and i. The special variable this is, loosely speaking, the “object before the dot” when you call the function in which this appears.
Why all this fuss and bother? Why not just say getX(d,i)
and be done with it? In JavaScript, the ability to specify this is an important design opportunity.
NOTE In JavaScript, “ this
” offers a design opportunity. Use it!
Listing 1-5 shows the power of this language feature. Here, the data are just an array of years. The function line.x
computes the desired x coordinate based on the index, i (now we’re using i!), but what’s going on with line.y
? It appears to be calling a function, getValue
, that is nowhere in scope.
LISTING 1-5: Extending the line generator to get values from an outer object (code filename rj3\pathFromFunction.js)
So where does getValue
come from? In the second part of the listing, a yearlyPriceGrapher object is instantiated that combines a line generator with a function, getValue
, that returns the value for a given year. In the call
the yearlyPriceGrapher is “dotted with” lineGenerator. That means that yearlyPriceGrapher becomes this in the y-accessor, which causes its getValue
to be invoked properly. The result is in Figure 1.4.
It is natural to think that this refers to the function in which it appears, or maybe the object enclosing the function. Not so. It refers to the object on which the function is called.
JavaScript Is Single-Threaded
Just one more thing to close out this section about language features: JavaScript is single-threaded. That doesn’t mean it uses a blocking model – far from it. It just means that you do asynchronous programming differently.
Where a multi-threaded language would allow you to start a task that runs in parallel to the code that spawned it, in JavaScript you merely enqueue a function to execute as soon after a certain event as possible. The triggering event may be the passage of a certain amount of time (in the case of setTimeout
), the arrival of data from a website (in the case of XMLHttpRequest.send
), or the click of a mouse, among many possibilities. JavaScript has an event loop that consumes the functions thus enqueued one at a time.
From a design point of view, this makes your life easier than it would be in a true multi-threaded environment. You never have to worry about getting interrupted, or about other objects accessing your variables when you think you have control.
It also means you shouldn’t hog the processor!
In Chapter 6, you will see how JavaScript Promise
s let you write code that does not block, yet is not a confusing scatter of event-handlers awkwardly connected by variables.
Avoiding JavaScript’s Pitfalls in Larger Systems
Why is a system that contains 50 classes (or objects, in JavaScript) more than ten times as challenging to write and maintain as a system that contains five? With five objects, even if each one draws on the services of all the others, there are at most 20 channels of communication (each of 5 objects calling 4 others – allowing ourselves to count A calling B as well as B calling A). With 50, there are 2450 (50 times 49) – more than 100 times as many.
With the advent of Single-Page Applications, node.js
, and other ways of making JavaScript shoulder the burdens of larger and larger systems on both client and server, the best JavaScript developers get serious about trimming those channels of communication to a bare minimum.
Where an object must interface with others to do its job, the connections are managed assiduously to ensure that they function properly in all circumstances.
This section will suggest ways to meet these goals.
Scripts Are Not Modules
Just last week, we were on the website of a company that makes a certain specialized device for user input. They had helpfully provided sample JavaScript code for using their device.
Argh! Their JavaScript library, suggested for all programmers to use, was over 1900 lines of one global variable or function after another – over 200 global functions in all. Most of the global functions were at least named so that collisions