邹明潮 Notes: YDKJS Types & Grammar

Type

  • undeclared: a variable that has not been formally declared
  • undefined: a variable that has been declared without a value in it
var a;

a; // undefined
b; // ReferenceError: b is not defined
/* the typeof operator returns "undefined" even for "undeclared" variables. This is a special safety guard in the behavior of typeof */
typeof a; // "undefined"
typeof b; // "undefined"

Check a variable(DEBUG) wether is declared in either global scope or built-in API.

// oops, this would throw an error!
if (DEBUG) {
console.log( "Debugging is starting" );
}

// this is a safe existence check
if (typeof DEBUG !== "undefined") {
console.log( "Debugging is starting" );
}

Unlike referencing undeclared variables, there is no ReferenceError thrown if you try to access an object property that doesn't exist.

if (window.DEBUG) {
// ..
}

Values

JavaScript strings are immutable, while arrays are quite mutable. And none of the string methods that alter its content in-place, but rather must create and return new strings.

Small Decimal Values

/* the representations for 0.1 and 0.2 in binary floating-point are not exact, so when they are added, the result is not exactly 0.3. It's really close: 0.30000000000000004. */
0.1 + 0.2 === 0.3; // false

The most commonly accepted practice is to use a tiny “rounding error” value as the tolerance for comparison. This tiny value is often called “machine epsilon,” which is commonly 2^-52 for the kind of numbers in JavaScript.

if (!Number.EPSILON) {
Number.EPSILON = Math.pow(2,-52);
}
function numbersCloseEnoughToEqual(n1,n2) {
return Math.abs( n1 - n2 ) < Number.EPSILON;
}

var a = 0.1 + 0.2;
var b = 0.3;

numbersCloseEnoughToEqual( a, b ); // true
numbersCloseEnoughToEqual( 0.0000001, 0.0000002 ); // false

Simple values (primitives) are always assigned/passed by value-copy: null, undefined, string, number, boolean, and ES6's symbol.

Compound values — object and function -- always create a copy of the reference on assignment or passing.

var a = 2;
var b = a; // `b` is always a copy of the value in `a`
b++;
a; // 2
b; // 3

var c = [1,2,3];
var d = c; // `d` is a reference to the shared `[1,2,3]` value
d.push( 4 );
c; // [1,2,3,4]
d; // [1,2,3,4]

The type of the value solely controls wether that value will be assigned by value-copy or reference-copy.

/* The problem is that the underlying scalar primitive value is not mutable. If a Number object holds the scalar primitive value 2, that exact Number object can never be changed to hold another value; you can only create a whole new Number object with a different value.When x is used in the expression x + 1, the underlying scalar primitive value 2 is unboxed (extracted) from the Number object automatically, so the line x = x + 1 subtly changes x from being a shared reference to the Number object, holding the scalar primitive value 3 as a result of the addition operation 2 + 1. Therefore, b on the outside still references the original unmodified/immutable Number object holding the value 2. */function foo(x) {
x = x + 1;
x; // 3
}

var a = 2;
var b = new Number( a ); // or equivalently `Object(a)`

foo( b );
console.log( b ); // 2, not 3

In JavaScript, references are not like references/pointers in other language — they’re never pointed at other variables/references, only at the underlying values.

Natives

Here’s a list of the most commonly used natives:

  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol() -- added in ES6!

As you can see, these natives are actually built-in functions.

JavaScript provides object wrappers around primitive values, known as natives (String, Number, Boolean, etc). These object wrappers give the values access to behaviors appropriate for each object subtype (String#trim() and Array#concat(..)).

If you have a simple scalar primitive value like "abc" and you access its length property or some String.prototypemethod, JS automatically "boxes" the value (wraps it in its respective object wrapper) so that the property/method accesses can be fulfilled.

Grammar

? : & = are right-associative

a ? b : c ? d : e                     //a ? b : (c ? d : e)var a, b, c;
a = b = c = 42;

Preference: && > || > ? : > =

var a = 42;
var b = "foo";
var c = false;

var d = a && b || c ? c || b ? a : c && b : a;

d; // 42
((a && b) || c) ? ((c || b) ? a : (c && b)) : a

Let’s solve it now:

  1. (a && b) is "foo".
  2. "foo" || c is "foo".
  3. For the first ? test, "foo" is truthy.
  4. (c || b) is "foo".
  5. For the second ? test, "foo" is truthy.
  6. a is 42.

Conclusion: use operator precedence/associativity where it leads to shorter and cleaner code, but use ( ) manual grouping in places where it helps create clarity and reduce confusion.

/* Never refer to a named parameter and its corresponding arguments slot at the same time */function foo(a) {
a = 42;
console.log( arguments[0] );
}

foo( 2 ); // 42 (linked)
foo(); // undefined (not linked)

Case 1:

/* The return 42 runs right away, which sets up the completion value from the foo() call. This action completes the tryclause and the finally clause immediately runs next. Only then is the foo() function complete, so that its completion value is returned back for the console.log(..) statement to use. */function foo() {
try {
return 42;
}
finally {
console.log( "Hello" );
}

console.log( "never runs" );
}

console.log( foo() );
// Hello
// 42

Case 2:

/* A return inside a finally has the special ability to override a previous return from the try or catch clause, but only if return is explicitly called */function baz() {
try {
return 42;
}
finally {
// override previous `return 42`
return "Hello";
}
}
baz(); // "Hello"

Case 1:

/* This works because the case clause can have any expression (not just simple values), which means it will strictly match that expression's result to the test expression (true). Since a == 42 results in true here, the match is made. */var a = "42";

switch (true) {
case a == 10:
console.log( "10 or '10'" );
break;
case a == 42:
console.log( "42 or '42'" );
break;
default:
// never gets here
}
// 42 or '42'

Case 2:

/* The way this snippet processes is that it passes through all the case clause matching first, finds no match, then goes back up to the default clause and starts executing. Since there's no break there, it continues executing in the already skipped over case 3 block, before stopping once it hits that break. */var a = 10;

switch (a) {
case 1:
case 2:
// never gets here
default:
console.log( "default" );
case 3:
console.log( "3" );
break;
case 4:
console.log( "4" );
}
// default
// 3

--

--

--

邹明潮 Noder, Frontend Developer

Love podcasts or audiobooks? Learn on the go with our new app.

Angular Constructor vs. ngOnInit

What is Koa.js, and how does it work?

READ MORE….https://pin.it/4uS5nc7

Implementing Quicksort in JavaScript

Maximum Split of Positive Even Integers

Simple drag and drop file upload in React

java error

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
邹明潮

邹明潮

邹明潮 Noder, Frontend Developer

More from Medium

Understanding Array.sort() Method In JavaScript

Creating web apps: the tale of a millennial JS developper

Using JavaScript and window.postMessage() for Safe Cross-Domain Communication

Mastering React: Build Switchable Dark Mode with Styled-Components