Another RayJune

You Don't Know JS Up & Going 小记

When you strive to comprehend your code, you create better work and become better at what you do. The code isn’t just your job anymore, it’s your craft.

These books each take on specific core parts of the language that are most commonly misunderstood or under-understood, and dive deep and exhaustively into them”

Preface

Mission

While this subset has been famously dubbed “The Good Parts,” I would implore you, dear reader, to instead consider it the “The Easy Parts,” “The Safe Parts,” or even “The Incomplete Parts.”
This You Don’t Know JS series offers a contrary challenge: learn and deeply understand all of JavaScript, even and especially “The Tough Parts.

“You should come away from reading with a firm confidence in your understanding, not just of the theoretical, but the practical “what you need to know” bits.”

Into programing

Executing a Program

How do those collections of programming statements tell the computer what to do? The program needs to be executed, also referred to as running the program.

For some computer languages, this translation of commands is typically done from top to bottom, line by line, every time the program is run, which is usually called interpreting the code.
For other languages, the translation is done ahead of time, called compiling the code, so when the program runs later, what’s running is actually the already compiled computer instructions ready to go.

It’s typically asserted that JavaScript is interpreted, because your JavaScript source code is processed each time it’s run. But that’s not entirely accurate. The JavaScript engine actually compiles the program on the fly and then immediately runs the compiled code.

NOTE:

For more information on JavaScript compiling, see the first two chapters of the Scope & Closures title of this series.

The best way to learn programming is to start coding!

Comparison

< (less than), > (greater than), <= (less than or loose-equals), >= (greater than or loose-equals), as in a <= b.

1
2
3
4
5
var a = '1';
var c = '-1';
if (a >= c) {console.log(a)};
// 1
console.log(typeof c) //"string"

Variables

“In some programming languages, you declare a variable (container) to hold a specific type of value, such as number or string. Static typing, otherwise known as type enforcement, is typically cited as a benefit for program correctness by preventing unintended value conversions.

Other languages emphasize types for values instead of variables. Weak typing, otherwise known as dynamic typing, allows a variable to hold any type of value at any time. It’s typically cited as a benefit for program flexibility by allowing a single variable to represent a value no matter what type form that value may take at any given moment in the program’s logic flow.”

By convention, JavaScript variables as constants are usually capitalized, with underscores _ between multiple words.

1
var TAX_RATE = 0.08; // 8% sales tax”

The TAX_RATE variable is only constant by convention—there’s nothing special in this program that prevents it from being changed. But if the city raises the sales tax rate to 9%, we can still easily update our program by setting the TAX_RATE assigned value to 0.09 in one place, instead of finding many occurrences of the value 0.08 strewn throughout the program and updating all of them.

The newest version of JavaScript at the time of this writing (commonly called “ES6”) includes a new way to declare constants, by using const instead of var:

1
2
3
4
5
6
7
// as of ES6:
const TAX_RATE = 0.08;
var amount = 99.99;
TAX_RATE = 2;
// VM503:1 Uncaught TypeError: Assignment to constant variable. at <anonymous>:1:3

As we discussed in “Values & Types”, values that aren’t already of an expected type are often coerced to that type. The if statement expects a boolean, but if you pass it something that’s not already boolean, coercion will occur.

The specific list of “falsy” values in JavaScript is as follows:

  • “” (empty string)
    8 0, -0, NaN (invalid number)
  • null, undefined
  • false

Functions

Functions are often used for code that you plan to call multiple times, but they can also be useful just to organize related bits of code into named collections, even if you only plan to call them once.

Scope

In JavaScript, each function gets its own scope. Scope is basically a collection of variables as well as the rules for how those variables are accessed by name. Only code inside that function can access that function’s scoped variables.

Practice

There is absolutely no substitute for practice in learning programming. No amount of articulate writing on my part is alone going to make you a programmer.
no amount of: 即使再多,即使再大

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const TAX_RATE = 0.08;
const PHONE_PRICE = 99.99;
const ACCESSORY_PRICE = 9.99;
var SPENDING_THRESHOLD = prompt('please input the number you want buy phones');
var bank_balance = prompt('please input your bank balance');
var amount = 0;
function calculateTax(amount) {
return amount * TAX_RATE;
}
function formatAmount(amount) {
return '$' + amount.toFixed( 2 );
}
// keep buying phones while you still have money
while (amount < bank_balance) {
// buy a new phone!
amount = amount + PHONE_PRICE;
// can we afford the accessory?
if (amount < SPENDING_THRESHOLD) {
amount = amount + ACCESSORY_PRICE;
}
}
// don't forget to pay the government, too
amount = amount + calculateTax( amount );
console.log(
'Your purchase: ' + formatAmount( amount )
);
// Your purchase: $334.76
// can you actually afford this purchase?
if (amount > bank_balance) {
console.log(
'You can\'t afford this purchase. :('
);
}
// You can't afford this purchase. :(

Review

Learning programming doesn’t have to be a complex and overwhelming process. There are just a few basic concepts you need to wrap your head around.

These act like building blocks. To build a tall tower, you start first by putting block on top of block on top of block. The same goes with programming. Here are some of the essential programming building blocks:

  • You need operators to perform actions on.
  • You need values and types to perform different kinds of actions like math on numbers or output with strings.
  • You need variables to store data (aka state) during your program’s execution.
  • You need conditionals like if statements to make decisions.
  • You need loops to repeat tasks until a condition stops being true.
  • You need functions to organize your code into logical and reusable chunks.
  • Code comments are one effective way to write more readable code, which makes your program easier to understand, maintain, and fix later if there are problems.

Finally, don’t neglect the power of practice. The best way to learn how to write code is to write code.

Into JavaScript

Especially if you’re new to JavaScript, you should expect to spend quite a bit of time reviewing the concepts and code examples here multiple times. Any good foundation is laid brick by brick, so don’t expect that you’ll immediately understand it all the first pass through.

Your journey to deeply learn JavaScript starts here.

Values & Types

The following built-in types are available:

  • string
  • number
  • boolean
  • null and undefined
  • object
  • symbol (new to ES6)

JavaScript provides a typeof operator that can examine a value and tell you what type it is.

typeof null is an interesting case because it errantly returns “object” when you’d expect it to return “null”.

Only values have types in JavaScript; variables are just simple containers for those values.

Also, note a = undefined. We’re explicitly setting a to the undefined value, but that is behaviorally no different from a variable that has no value set yet, like with the var a; line at the top of the snippet. A variable can get to this “undefined” value state in several different ways, including functions that return no values and usage of the void operator.

Objects

The object type refers to a compound value where you can set properties (named locations) that each hold their own values of any type. This is perhaps one of the most useful value types in all of JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
a: "hello world",
b: 42,
c: true
};
obj.a; // "hello world"
obj.b; // 42
obj.c; // true
obj["a"]; // "hello world"
obj["b"]; // 42
obj["c"]; // true

But rather than being proper built-in types, these should be thought of more like subtypes—specialized versions of the object type.

such as Array, Function

1
2
3
4
var arr = ['123', true, 'liga'];
console.log(arr.length); // 3
console.log(arr[0]); // '123'
console.log(typeof arr); // 'object'

but for function

1
2
3
4
5
6
function foo() {
console.log('The answer is 42')
}
foo.bar = 'nice to meet you';
console.log(typeof foo); // 'function'

functions are a subtype of objects—typeof returns “function”, which implies that a function is a main type—and can thus have properties, but you typically will only use function object properties (like foo.bar) in limited cases.

Equality

You should take special note of the == and === comparison rules if you’re comparing two non-primitive values, like objects (including function and array). Because those values are actually held by reference, both == and === comparisons will simply check whether the references match, not anything about the underlying values.

For example, arrays are by default coerced to strings by simply joining all the values with commas (,) in between. You might think that two arrays with the same contents would be == equal, but they’re not:

1
2
3
4
5
6
7
8
9
10
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
a == b; // false
a == c; // true
b == c; // true
a == b; // fasle
b === c; // false

Inequality

The biggest gotcha you may run into here with comparisons between potentially different value types, remember, there are no “strict inequality” forms to use—is when one of the values cannot be made into a valid number, such as:

1
2
3
4
5
var a = 42;
var b = "foo";
a < b; // false
a > b; // false

Wait, how can all three of those comparisons be false? Because the b value is being coerced to the “invalid number value” NaN in the < and > comparisons, and the specification says that NaN is neither greater than nor less than any other value.

let

In addition to creating declarations for variables at the function level, ES6 lets you declare variables to belong to individual blocks (pairs of { .. }), using the let keyword. Besides some nuanced details, the scoping rules will behave roughly the same as we just saw with functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function foo() {
var a = 1;
if (a >= 1) {
let b = 2;
while (b < 5) {
let c = b * 2;
b++;
console.log(a + c);
}
}
console.log(b);
}
foo();// 5 7 9
// VM1156:13 Uncaught ReferenceError: b is not defined at foo (<anonymous>:13:14) at <anonymous>:1:1

Because of using let instead of var, b will belong only to the if statement and thus not to the whole foo() function’s scope. Similarly, c belongs only to the while loop. Block scoping is very useful for managing your variable scopes in a more fine-grained fashion can make your code much easier to maintain over time.

Strict mode

Strict mode is a big win for code, and you should use it for all your programs.

You can opt in to strict mode for an individual function, or an entire file, depending on where you put the strict mode pragma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function foo() {
'use strict';
// this code is strict mode
function bar() {
// this code is strict mode
}
}
'use strict';
function foo() {
// this code is strict mode
function bar() {
// this code is strict mode
}
}

Not only will strict mode keep your code to a safer path, and not only will it make your code more optimizable, but it also represents the future direction of the language. It’d be easier on you to get used to strict mode now than to keep putting it off—it’ll only get harder to convert later!

For more information about strict mode, see Chapter 5 of the Types & Grammar title of this series.

IIFE (Immediately Invoked Function Expressions)

Because an IIFE is just a function, and functions create variable scope, using an IIFE in this fashion is often used to declare variables that won’t affect the surrounding code outside the IIFE:

1
2
3
4
5
6
7
8
var a = 42;
(function IIFE(){
var a = 10;
console.log(a); // 10
})();
console.log(a); // 42

Closure (important)

It will be one of the most important techniques in your JS skillset.
You can think of closure as a way to “remember” and continue to access a function’s scope (its variables) even once the function has finished running.

Consider:

1
2
3
4
5
6
7
8
9
10
11
function makeAdder(x) {
// parameter *x* is an inner variable
// inner function *add()* uses *x*, so
// it has a "closure" over it
function add(y) {
return y + x;
}
return add;
}

The reference to the inner add(..) function that gets returned with each call to the outer makeAdder(..) is able to remember whatever x value was passed in to makeAdder(..). Now, let’s use makeAdder(..):

1
2
3
4
5
6
7
8
9
10
11
12
13
// plusOne gets a reference to the inner add(..)
// function with closure over the x parameter of
// the outer makeAdder(..)
var plusOne = makeAdder(1);
// plusTen gets a reference to the inner add(..)
// function with closure over the *x* parameter of
// the outer makeAdder(..)
var plusTen = makeAdder(10);
plusOne(3); // 4 <-- 1 + 3
plusOne(41); // 42 <-- 1 + 41
plusTen(13); // 23 <-- 10 + 13

Modules

The most common usage of closure in JavaScript is the module pattern. Modules let you define private implementation details (variables, functions) that are hidden from the outside world, as well as a public API that is accessible from the outside.

Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function User() {
var username;
var password;
function doLogin(user, pw) {
username = user;
password = pw;
// do the rest of the login work
}
var publicAPI = {
login: doLogin
};
return publicAPI;
}
// create a *User* module instance
var fred = User();
fred.login('fred', '12Battery34!');
var a = User();
console.log(a === fred); // false

Executing User() creates an instance of the User module—a whole new scope is created, and thus a whole new copy of each of these inner variables/functions. We assign this instance to fred. If we run User() again, we’d get a new instance entirely separate from fred.

From here, go read the Scope & Closures title of this series for a much more in-depth exploration.

this Identifier

If a function has a this reference inside it, that this reference usually points to an object. But which object it points to depends on how the function was called.

Prototypes

When you reference a property on an object, if that property doesn’t exist, JavaScript will automatically use that object’s internal prototype reference to find another object to look for the property on. You could think of this almost as a fallback if the property is missing.

The internal prototype reference linkage from one object to its fallback happens at the time the object is created. The simplest way to illustrate it is with a built-in utility called Object.create(..).
Consider:

1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
a: 42
};
// create 'bar' and link it to 'foo'
var bar = Object.create(foo);
bar.b = 'hello world';
bar.b; // "hello world"
bar.a; // 42 <-- delegated to *foo*

The Object.create() method creates a new object with the specified prototype object and properties.

This linkage may seem like a strange feature of the language. The most common way this feature is used—and I would argue, abused—is to try to emulate/fake a “class” mechanism with “inheritance.”
But a more natural way of applying prototypes is a pattern called “behavior delegation,” where you intentionally design your linked objects to be able to delegate from one to the other for parts of the needed behavior.

For more information about prototypes and behavior delegation, see Chapters 4-6 of the this & Object Prototypes title of this series.

Old & New

There are two main techniques you can use to “bring” the newer JavaScript stuff to the older browsers: polyfilling and transpiling.

Polyfilling

“The word “polyfill” is used to refer to taking the definition of a newer feature and producing a piece of code that’s equivalent to the behavior, but is able to run in older JS environments.

For example, ES6 defines a utility called Number.isNaN(..) to provide an accurate, non-buggy check for NaN values, deprecating the original isNaN(..) utility. But it’s easy to polyfill that utility so that you can start using it in your code regardless of whether the end user is in an ES6 browser or not.

Consider:

1
2
3
4
5
if (!Number.isNaN) {
Number.isNaN = function isNaN(x) {
return x !== x;
};
}

The if statement guards against applying the polyfill definition in ES6 browsers where it will already exist. If it’s not already present, we define Number.isNaN(..).

The check we do here takes advantage of a quirk with NaN values, which is that they’re the only value in the whole language that is not equal to itself. So the NaN value is the only one that would make x !== x be true.

Not all new features are fully polyfillable. Sometimes most of the behavior can be polyfilled, but there are still small deviations. You should be really, really careful in implementing a polyfill yourself, to make sure you are adhering to the specification as strictly as possible.
Or better yet, use an already vetted set of polyfills that you can trust, such as those provided by ES5-Shim and ES6-Shim.

Transpiling

There’s no way to polyfill new syntax that has been added to the language. The new syntax would throw an error in the old JS engine as unrecognized/invalid.
So the better option is to use a tool that converts your newer code into older code equivalents. This process is commonly called “transpiling,” a term for transforming + compiling.

Essentially, your source code is authored in the new syntax form, but what you deploy to the browser is the transpiled code in old syntax form. You typically insert the transpiler into your build process, similar to your code linter or your minifier.

There are several important reasons you should care about transpiling:

  • The new syntax added to the language is designed to make your code more readable and maintainable. The older equivalents are often much more
  • If you transpile only for older browsers, but serve the new syntax to the newest browsers, you get to take advantage of browser performance optimizations with the new syntax. This also lets browser makers have more real-world code to test their implementations and optimizations on.
  • Using the new syntax earlier allows it to be tested more robustly in the real world, which provides earlier feedback to the JavaScript committee (TC39). If issues are found early enough, they can be changed/fixed before those language design mistakes become permanent.

Here’s a quick example of transpiling. ES6 adds a feature called “default parameter values.” It looks like this:

1
2
3
4
5
6
7
8
9
10
11
function foo(a = 2) {
console.log( a );
}
foo(); // 2
foo( 42 ); // 42
function foo() {
var a = arguments[0] !== (void 0) ? arguments[0] : 2;
console.log( a );
}

Into YDKJS

The YDKJS series is dedicated to the proposition that all JS developers can and should learn all of the parts of this great language. No person’s opinion, no framework’s assumptions, and no project’s deadline should be the excuse for why you never learn and deeply understand JavaScript.

We take each important area of focus in the language and dedicate a short but very dense book to fully explore all the parts of it that you perhaps thought you knew but probably didn’t fully.

“You Don’t Know JS” isn’t a criticism or an insult. It’s a realization that all of us, myself included, must come to terms with. Learning JavaScript isn’t an end goal but a process. We don’t know JavaScript, yet. But we will!

文章标题:You Don't Know JS Up & Going 小记

文章作者:RayJune

时间地点:上午9:01,于又玄图书馆

原始链接:http://rayjune.xyz/2017/09/19/You-Dont-Know-JS-Up&Going-note/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。