《Understanding ES6》

  • The current best practice for block bindings is to use const by default and only use let when you know a variable’s value needs to change. This ensures a basic level of immutability in code that can help prevent certain types of errors.

  • Any attempt to read a property of null or undefined results in a runtime error.

  • you must always provide an initializer when using array destructuring with var, let, or const.

  • Symbols are a new type of primitive value in JavaScript and are used to create properties that can't be accessed without referencing the symbol.

  • When a primitive conversion is needed, Symbol.toPrimitive is called with a single argument, referred to as hint in the specification. (the hint argument is filled in by the JavaScript engine)

  • One of the most interesting problems in JavaScript has been the availability of multiple global execution environments.

  • Changing the string tag for native objects is also possible. Just assign to Symbol.toStringTag on the object's prototype. While I recommended not changing built-in objects in this way, there's nothing in the language that forbids doing so.

  • The with statement is one of the most controversial parts of JavaScript.

  • A set is a list of values that cannot contain duplicates. (The only exception is that -0 and +0 are considered to be the same.)

  • A map is a collection of keys that correspond to specific values.

  • All object properties must be strings.

  • JavaScript has the in operator that returns true if a property exists in an object without reading the value of the object. However, the in operator also searches the prototype of an object, which makes it only safe to use when an object has a null prototype.

  • the spread operator (...) as a way to split items in an array into separate function parameters. You can also use the spread operator to work on iterable objects, such as sets, to convert them into arrays.

  • ECMAScript 6 also includes weak sets, which only store weak object references and cannot store primitive values. A weak reference to an object does not prevent garbage collection if it is the only remaining reference.

  • Weak sets are created using the WeakSet constructor and have an add() method, a has() method, and a delete() method.

  • In general, if you only need to track object references, then you should use a weak set instead of a regular set.

  • Weak maps are to maps what weak sets are to sets: they're a way to store weak object references.

  • The most useful place to employ weak maps is when creating an object related to a particular DOM element in a web page.

  • In ECMAScript 5, it's possible to get close to having truly private data, by creating an object using a pattern such as this:

/* I often use it like this  >.< */
var Person = (function() {

    var privateData = {},
        privateId = 0;

    function Person(name) {
        Object.defineProperty(this, "_id", { value: privateId++ });

        privateData[this._id] = {
            name: name
        };
    }

    Person.prototype.getName = function() {
        return privateData[this._id].name;
    };

    return Person;
}());

/*
This example wraps the definition of `Person` 
in an IIFE that contains two private variables, 
`privateData` and `privateId`.

The big problem with this approach is that the 
data in `privateData` never disappears because 
there is no way to know when an object instance
is destroyed; the `privateData` object will always
contain extra data. This problem can be solved
by using a weak map instead, as follows:
*/

let Person = (function() {

    let privateData = new WeakMap();

    function Person(name) {
        privateData.set(this, { name: name });
    }

    Person.prototype.getName = function() {
        return privateData.get(this).name;
    };

    return Person;
}());

/*
This version of the `Person` example uses a weak map
for the private data instead of an object. Because the
`Person` object instance itself can be used as a key, 
there's no need to keep track of a separate `ID`. When
the `Person` constructor is called, a new entry is made
into the weak map with a key of this and a value of an
object containing private information. 
*/
  • Anytime you're going to use only object keys, then the best choice is a weak map. That will allow you to optimize memory usage and avoid memory leaks by ensuring that extra data isn't kept around after it's no longer accessible.

  • Iterators are just objects with a specific interface designed for iteration.

  • A generator is a function that returns an iterator.

  • Perhaps the most interesting aspect of generator functions is that they stop execution after each yield statement. This ability to stop execution in the middle of a function is extremely powerful and leads to some interesting uses of generator functions.

  • Creating an arrow function that is also a generator is not possible.

  • All iterators created by generators are also iterables, as generators assign the Symbol.iterator property by default.

  • A for-of loop calls next() on an iterable each time the loop executes and stores the value from the result object in a variable.

  • The for-of statement will throw an error when used on, a non-iterable object, null, or undefined.

  • For sets, the keys are the same as the values, and so keys() and values() return the same iterator. For maps, the keys() iterator returns each unique key.

  • Generator delegation also lets you make further use of generator return values.

  • A lot of the excitement around generators is directly related to asynchronous programming. Asynchronous programming in JavaScript is a double-edged sword: simple tasks are easy to do asynchronously, while complex tasks become an errand in code organization. Since generators allow you to effectively pause code in the middle of execution, they open up a lot of possibilities related to asynchronous processing.

  • Because yield stops execution and waits for the next() method to be called before starting again, you can implement asynchronous calls without managing callbacks.

  • Interestingly, class declarations are just syntactic sugar on top of the existing custom type declarations.

  • Despite the similarities between classes and custom types, there are some important differences to keep in mind:

    1. Class declarations, unlike function declarations, are not hoisted. Class declarations act like let declarations and so exist in the temporal dead zone until execution reaches the declaration.
    2. All code inside of class declarations runs in strict mode automatically. There's no way to opt-out of strict mode inside of classes.
    3. All methods are non-enumerable. This is a significant change from custom types, where you need to use Object.defineProperty() to make a method non-enumerable.
    4. All methods lack an internal [[Construct]] method and will throw an error if you try to call them with new.
    5. Calling the class constructor without new throws an error.
    6. Attempting to overwrite the class name within a class method throws an error.
  • Classes and functions are similar in that they have two forms: declarations and expressions.

  • ECMAScript 6 continues this tradition by making classes first-class citizens as well.

  • Static members are not accessible from instances. You must always access static members from the class directly.

  • Accepting any type of expression after extends offers powerful possibilities, such as dynamically determining what to inherit from.

  • ECMAScript 6 classes start out as syntactic sugar for the classical inheritance model of ECMAScript 5, but add a lot of features to reduce mistakes.

  • To make JavaScript arrays easier to create, ECMAScript 6 adds the Array.of() and Array.from() methods.

  • When the Array constructor is passed a single numeric value, the length property of the array is set to that value. If a single non-numeric value is passed, then that value becomes the one and only item in the array. If multiple values are passed (numeric or not), then those values become items in the array.

  • To create an array with the Array.of() method, just pass it the values you want in your array.

  • slice() needs only numeric indices and a length property to function correctly, any array-like object will work.

  • The Array.from() call creates a new array based on the items in arguments.

  • If the mapping function is on an object, you can also optionally pass a third argument to Array.from() that represents the this value for the mapping function.

  • The Array.from() method works on both array-like objects and iterables. That means the method can convert any object with a Symbol.iterator property into an array.

  • If you represent a number that fits in an int8 as a normal JavaScript number, you'll waste 56 bits.

  • Using bits more efficiently is one of the use cases typed arrays address.

  • Typed arrays allow the storage and manipulation of eight different numeric types:

    1. Signed 8-bit integer (int8)
    2. Unsigned 8-bit integer (uint8)
    3. Signed 16-bit integer (int16)
    4. Unsigned 16-bit integer (uint16)
    5. Signed 32-bit integer (int32)
    6. Unsigned 32-bit integer (uint32)
    7. 32-bit float (float32)
    8. 64-bit float (float64)
  • The foundation for all typed arrays is an array buffer, which is a memory location that can contain a specified number of bytes.

  • Creating an array buffer is akin to calling malloc() in C to allocate memory without specifying what the memory block contains.

  • An array buffer always represents the exact number of bytes specified when it was created. You can change the data contained within an array buffer, but never the size of the array buffer itself.

  • Array buffers represent memory locations, and views are the interfaces you'll use to manipulate that memory.

  • Of course, reading information about memory isn't very useful on its own. You need to write data into and read data out of that memory to get any benefit.

  • Little-endian means the least significant byte is at byte 0, instead of in the last byte.

  • Each typed array is made up of a number of elements, and the element size is the number of bytes each element represents.

  • Unlike regular arrays, you cannot change the size of a typed array using the length property. The length property is not writable. (You cannot assign a value to a nonexistent numeric index in a typed array like you can with regular arrays, as typed arrays ignore the operation.)

  • The most importance difference between typed arrays and regular arrays is that typed arrays are not regular arrays. Typed arrays don't inherit from Array and Array.isArray() returns false when passed a typed array.

  • Zero is used in place of any invalid values. (Of course, strings are invalid data types in typed arrays, so the value is inserted as 0 instead. )

  • Finally, typed arrays methods have two methods not present on regular arrays: the set() and subarray() methods.

  • Typed arrays are not technically arrays, as they do not inherit from Array, but they do look and behave a lot like arrays. Typed arrays contain one of eight different numeric data types and are built upon ArrayBuffer objects that represent the underlying bits of a number or series of numbers.

  • Typed arrays are a more efficient way of doing bitwise arithmetic because the values are not converted back and forth between formats, as is the case with the JavaScript number type.

  • One of the most powerful aspects of JavaScript is how easily it handles asynchronous programming.

  • A promise specifies some code to be executed later (as with events and callbacks) and also explicitly indicates whether the code succeeded or failed at its job. You can chain promises together based on success or failure in ways that make your code easier to understand and debug.

  • JavaScript engines are built on the concept of a single-threaded event loop. Single-threaded means that only one piece of code is ever executed at a time.

  • JavaScript engines can only execute one piece of code at a time, so they need to keep track of code that is meant to run. That code is kept in a job queue.

  • The event loop is a process inside the JavaScript engine that monitors code execution and manages the job queue. Keep in mind that as a queue, job execution runs from the first job in the queue to the last.

  • Callback hell occurs when you nest too many callbacks.

  • A promise is a placeholder for the result of an asynchronous operation.

  • Each promise goes through a short lifecycle starting in the pending state, which indicates that the asynchronous operation hasn't completed yet.

  • An internal [[PromiseState]] property is set to "pending", "fulfilled", or "rejected" to reflect the promise's state. This property isn't exposed on promise objects, so you can't determine which state the promise is in programmatically.

  • Any object that implements the then() method in this way is called a thenable. All promises are thenables, but not all thenables are promises.

  • A fulfillment or rejection handler will still be executed even if it is added to the job queue after the promise is already settled.

  • When either resolve() or reject() is called inside the executor, a job is added to the job queue to resolve the promise. This is called job scheduling, and if you've ever used the setTimeout() or setInterval() functions, then you're already familiar with it.

  • The promise executor executes immediately, before anything that appears after it in the source code.

  • Calling resolve() triggers an asynchronous operation. Functions passed to then() and catch() are executed asynchronously, as these are also added to the job queue.

  • Fulfillment and rejection handlers are always added to the end of the job queue after the executor has completed.

  • The Promise.resolve() method accepts a single argument and returns a promise in the fulfilled state.

  • You can also create rejected promises by using the Promise.reject() method.

  • When you're unsure if an object is a promise, passing the object through Promise.resolve() or Promise.reject() (depending on your anticipated result) is the best way to find out because promises just pass through unchanged.

  • One of the most controversial aspects of promises is the silent failure that occurs when a promise is rejected without a rejection handler.

  • To properly track potentially unhandled rejections, use the rejectionHandled and unhandledRejection events to keep a list of potentially unhandled rejections. Then wait some period of time to inspect the list.

  • Always have a rejection handler at the end of a promise chain to ensure that you can properly handle any errors that may occur.

  • Another important aspect of promise chains is the ability to pass data from one promise to the next.

  • The promises passed to Promise.race() are truly in a race to see which is settled first. If the first promise to settle is fulfilled, then the returned promise is fulfilled; if the first promise to settle is rejected, then the returned promise is rejected.

  • That means both synchronous and asynchronous methods work correctly when called using yield, and you never have to check that the return value is a promise.

  • The only concern is ensuring that asynchronous functions like readFile() return a promise that correctly identifies its state.

  • Work is progressing on an await syntax that would closely mirror the promise-based example in the preceding section. The basic idea is to use a function marked with async instead of a generator and use await instead of yield when calling a function.

  • The async keyword before function indicates that the function is meant to run in an asynchronous manner. The await keyword signals that the function call to readFile("config.json") should return a promise, and if it doesn't, the response should be wrapped in a promise.

  • Promises have three states: pending, fulfilled, and rejected.

  • You can create a proxy to use in place of another object (called the target) by calling new Proxy(). The proxy virtualizes the target so that the proxy and the target appear to be the same object to functionality using the proxy.

  • The reflection API, represented by the Reflect object, is a collection of methods that provide the default behavior for the same low-level operations that proxies can override. There is a Reflect method for every proxy trap.

  • Proxy traps in JavaScript

Proxy Trap Overrides the Behavior Of Default Behavior
get Reading a property value Reflect.get()
set Writing to a property Reflect.set()
has The in operator Reflect.has()
deleteProperty The delete operator Reflect.deleteProperty()
getPrototypeOf Object.getPrototypeOf() Reflect.getPrototypeOf()
setPrototypeOf Object.setPrototypeOf() Reflect.setPrototypeOf()
isExtensible Object.isExtensible() Reflect.isExtensible()
preventExtensions Object.preventExtensions() Reflect.preventExtensions()
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor()
defineProperty Object.defineProperty() Reflect.defineProperty
ownKeys Object.keys, Object.getOwnPropertyNames(), Object.getOwnPropertySymbols() Reflect.ownKeys()
apply Calling a function Reflect.apply()
construct Calling a function with new Reflect.construct()
  • Each trap overrides some built-in behavior of JavaScript objects, allowing you to intercept and modify the behavior.

  • To validate the values of properties, you'd use the set trap and inspect the value that is passed in.

  • An object shape is the collection of properties and methods available on the object. JavaScript engines use object shapes to optimize code, often creating classes to represent the objects.

  • The has trap is called whenever the in operator is used.

  • The delete operator removes a property from an object and returns true when successful and false when unsuccessful.

  • First, the getPrototypeOf trap must return an object or null, and any other return value results in a runtime error.

  • One of the most important features of ECMAScript 5 was the ability to define property attributes using the Object.defineProperty() method.

  • Proxies let you intercept calls to Object.defineProperty() and Object.getOwnPropertyDescriptor() using the defineProperty and getOwnPropertyDescriptor traps, respectively.

  • You can also have Object.defineProperty() silently fail by returning true and not calling the Reflect.defineProperty() method.

  • No matter what object is passed as the third argument to the Object.defineProperty() method, only the properties enumerable, configurable, value, writable, get, and set will be on the descriptor object passed to the defineProperty trap.

  • The Object.defineProperty() and Reflect.defineProperty() methods are exactly the same except for their return values. The Object.defineProperty() method returns the first argument, while Reflect.defineProperty() returns true if the operation succeeded and false if not.

  • The ownKeys proxy trap intercepts the internal method [[OwnPropertyKeys]] and allows you to override that behavior by returning an array of values.

  • The ownKeys trap receives a single argument, the target, and must always return an array or array-like object; otherwise, an error is thrown.

  • The ownKeys trap also affects the for-in loop, which calls the trap to determine which keys to use inside of the loop.

  • Of all the proxy traps, only apply and construct require the proxy target to be a function.

  • Creating callable class constructors is something that is only possible using proxies.

  • A String property name P is an array index if and only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal to 232-1.

  • The simplest way to create a class that uses a proxy is to define the class as usual and then return a proxy from the constructor.

  • When the internal [[Get]] method is called to read a property, the operation looks for own properties first. If an own property with the given name isn't found, then the operation continues to the prototype and looks for a property there. The process continues until there are no further prototypes to check.

  • The internal [[Set]] method also checks for own properties and then continues to the prototype if needed.

  • The has trap is therefore only called when the search reaches the proxy object in the prototype chain. When using a proxy as a prototype, that only happens when there's no own property of the given name.

  • Even though it takes a little bit of extra code to create a class with a proxy in its prototype chain, it can be worth the effort if you need such functionality.

  • Only the get, set, and has proxy traps will ever be called on a proxy when it's used as a prototype, making the set of use cases much smaller.

  • JavaScript's "shared everything" approach to loading code is one of the most error-prone and confusing aspects of the language.

  • One goal of ECMAScript 6 was to solve the scope problem and bring some order to JavaScript applications. That's where modules come in.

  • Modules are JavaScript files that are loaded in a different mode (as opposed to scripts, which are loaded in the original way JavaScript worked). This different mode is necessary because modules have very different semantics than scripts:

    1. Module code automatically runs in strict mode, and there's no way to opt-out of strict mode.
    2. Variables created in the top level of a module aren't automatically added to the shared global scope. They exist only within the top-level scope of the module.
    3. The value of this in the top level of a module is undefined.
    4. Modules don't allow HTML-style comments within code (a leftover feature from JavaScript's early browser days).
    5. Modules must export anything that should be available to code outside of the module.
    6. Modules may import bindings from other modules.
  • The real power of modules is the ability to export and import only bindings you need, rather than everything in a file.

  • You can use the export keyword to expose parts of published code to other modules.

  • Each exported function or class also has a name; that's because exported function and class declarations require a name. You can't export anonymous functions or classes using this syntax unless you use the default keyword.

  • Once you have a module with exports, you can access the functionality in another module by using the import keyword.

  • The list of bindings to import looks similar to a destructured object, but it isn't one.

  • When importing a binding from a module, the binding acts as if it were defined using const.

  • namespace import

  • Keep in mind, however, that no matter how many times you use a module in import statements, the module will only be executed once. After the code to import the module executes, the instantiated module is kept in memory and reused whenever another import statement references it.

  • Imports without bindings are most likely to be used to create polyfills and shims.

你可能感兴趣的:(《Understanding ES6》)