ES6 ES6 Latest Proposal, do, throw, partial function execution, pipeline operator |>, Curitization,:, Realm API

ES6 (29) ES6 latest proposal, do, throw, partial function execution, pipeline operator |>, Curitization,:, Realm API

1. do expression

Essentially, a block-level scope is a statement that encapsulates multiple operations together with no return value.

{
  let t = f();
  t = t * t + 1;
}

In the code above, the block-level scope encapsulates the two statements together. However, there is no way to get a value of T outside of a block-level scope, because a block-level scope does not return a value unless t is a global variable.

Now there is one proposal So that a block-level scope can become an expression, that is, a value can be returned by adding do before the block-level scope, making it a do expression, and then returning the value of the last expression executed internally.

let x = do {
  let t = f();
  t * t + 1;
};

In the code above, the variable x gets a return value (t * t + 1) for the entire block-level scope.

The logic of the do expression is simple: what is encapsulated, what is returned.

// Equivalent to <expression>
do { <Expression>; }

// Equivalent to <statement>
do { <Sentence> }

The advantage of a do expression is that it encapsulates multiple statements, making the program more modular, like a Lego building block.

let x = do {
  if (foo()) { f() }
  else if (bar()) { g() }
  else { h() }
};

The essence of the code above is to call different functions based on the result of the foo function and assign the return result to the variable x. Using the do expression, the intent of this operation is expressed very concisely and clearly. Moreover, do block-level scopes provide separate scopes, and internal operations can be isolated from the global scope.

It is worth mentioning that do expressions are very useful in JSX syntax.

return (
  <nav>
    <Home />
    {
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />
        }
      }
    }
  </nav>
)

In the code above, if you do not use a do expression, you can only use the ternary judgment operator (?:). That way, once the logic of judgment is complex, the code becomes unreadable.

2. throw expression

The JavaScript syntax states that throw is a command that throws errors and cannot be used in expressions.

// Report errors
console.log(throw new Error());

In the code above, console. The argument to log must be an expression, and if it is a throw statement, an error will be reported.

Now there is one proposal Allows throw to be used in expressions.

// Default value of parameter
function save(filename = throw new TypeError("Argument required")) {
}

// Return value of arrow function
lint(ast, {
  with: () => throw new Error("avoid using 'with' statements.")
});

// Conditional expression
function getEncoder(encoding) {
  const encoder = encoding === "utf8" ?
    new UTF8Encoder() :
    encoding === "utf16le" ?
      new UTF16Encoder(false) :
      encoding === "utf16be" ?
        new UTF16Encoder(true) :
        throw new Error("Unsupported encoding");
}

// Logical expression
class Product {
  get id() {
    return this._id;
  }
  set id(value) {
    this._id = value || throw new Error("Invalid value");
  }
}

In the code above, throw appears in the expression.

Syntax, the throw inside a throw expression is no longer a command, but an operator. To avoid confusion with the throw command, specify that throw appears at the beginning of the line and is always interpreted as a throw statement, not as a throw expression.

3. Partial execution of functions

grammar

Functions with multiple parameters sometimes need to bind one or more of them and return a new function.

function add(x, y) { return x + y; }
function add7(x) { return x + 7; }

In the code above, the add7 function is actually a special version of the add function. By binding a parameter to 7, you can get add7 from add.

// bind method
const add7 = add.bind(null, 7);

// Arrow function
const add7 = x => add(x, 7);

Both of these are redundant. The limitation of the bind method is more obvious, it must provide this, and it can only bind parameters from the front to the back, not only non-header parameters.

Now there is one proposal Makes it easier to bind parameters and return a new function. This is called partial application of a function.

const add = (x, y) => x + y;
const addOne = add(1, ?);//`?` And `...` It can only appear in a call to a function and returns a new function.

const maxGreaterThanZero = Math.max(0, ...);

Under the new proposal, Is a placeholder for a single parameter,... Is a placeholder for multiple parameters. The following forms are part of a function's execution.

f(x, ?)
f(x, ...)
f(?, x)
f(..., x)
f(?, x, ?)
f(..., x, ...)

What? And... It can only appear in a call to a function and returns a new function.

const g = f(?, 1, ...);
// Equivalent to
const g = (x, ...y) => f(x, 1, ...y);

Partial execution of functions can also be used in object methods.

let obj = {
  f(x, y) { return x + y; },
};

const g = obj.f(?, 3);
g(1) // 4

Points of Attention

Some special attention is paid to the partial execution of functions.

(1) The partial execution of a function is based on the original function, and if the original function changes, the new function generated by the partial execution will immediately reflect the change.

let f = (x, y) => x + y;

const g = f(?, 3);
g(1); // 4

// Replacement function f
f = (x, y) => x * y;

g(1); // 3

In the code above, once a function is partially defined and executed, replacing the original function immediately affects the new function.

(2) If the value provided in advance is an expression, the expression is not evaluated at the time of definition, but at each call.

let a = 3;
const f = (x, y) => x + y;

const g = f(?, a);
g(1); // 4

// Change the value of a
a = 10;
g(1); // 11 The `a` is evaluated every time the function `g` is called.

In the code above, the argument provided in advance is variable a, so a is evaluated every time function g is called.

(3) If the new function has more parameters than placeholders, the extra parameters will be ignored.

const f = (x, ...y) => [x, ...y];
const g = f(?, 1);
g(2, 3, 4); // [2, 1]

In the code above, function g has only one placeholder, which means it can only accept one parameter, and any extra parameters are ignored.

Write the following so that the extra parameters are OK.

const f = (x, ...y) => [x, ...y];
const g = f(?, 1, ...);
g(2, 3, 4); // [2, 1, 3, 4];

(4).... Will only be collected once. If more than one... Is used in the partial execution of a function, then each.. will have the same value.

const f = (...x) => x;
const g = f(..., 9, ...);
g(1, 2, 3); // [1, 2, 3, 9, 1, 2, 3]

In the code above, g defines two... Placeholders, when actually executed, have the same value.

4. Pipeline Operators

The Unix operating system has a pipeline mechanism that passes the value of the previous operation to the next. This mechanism is useful so that simple operations can be combined into complex operations. Many languages have pipeline implementations, and now one proposal Let JavaScript also have a pipeline mechanism.

The JavaScript pipeline is an operator that writes |>. It has an expression on the left and a function on the right. The pipe operator evaluates the expression on the left by passing it to the function on the right.

x |> f
// Equivalent to
f(x)

The best benefit of a pipeline operator is that you can write nested functions as left-to-right chain expressions.

function doubleSay (str) {
  return str + ", " + str;
}

function capitalize (str) {
  return str[0].toUpperCase() + str.substring(1);
}

function exclaim (str) {
  return str + '!';
}

Here are three simple functions. If nested execution is required, the traditional and pipeline writings are as follows.

// Traditional Writing
exclaim(capitalize(doubleSay('hello')))
// "Hello, hello!"

// Writing of pipes
'hello'
  |> doubleSay
  |> capitalize
  |> exclaim
// "Hello, hello!"

A pipe operator can only pass one value, which means that the function to its right must be a single-parameter function. If it is a multiparameter function, it must proceed currying And change to a single-parameter version.

Curitization (returns a new function):

  • Currying is the technique of transforming a function that accepts multiple parameters into a function that accepts a single parameter (the first parameter of the original function) and returns a new function that accepts the remaining parameters and returns the result.
  • Looking at the abstraction of this explanation, we'll use the add function, which has been exemplified countless times, to make a simple implementation.
// Common add function
function add(x, y) {
    return x + y
}

// After Currying
function curryingAdd(x) {
    return function (y) {
        return x + y
    }
}

add(1, 2)           // 3
curryingAdd(1)(2)   // 3
  • In fact, the X and y parameters of the add function are changed to receive x with one function and return a function to process the y parameters. Now it should be clear that only a part of the parameter passed to the function is called to return a function to handle the remaining parameters.
  • What are the benefits of Currying:
    • Parameter Reuse
    • Confirm in advance
    • Delayed Run: Like the bind we use most often in js, the implementation mechanism is Currying.
function double (x) { return x + x; }
function add (x, y) { return x + y; }

let person = { score: 25 };
person.score
  |> double
  |> (_ => add(7, _))
// 57

In the code above, the add function requires two parameters. However, a pipe operator can only pass in one value, so you need to provide another parameter in advance and change it to a single-parameter arrow function_ => Add (7,). Underlining in this function has no special meaning. It can be replaced by other symbols. Underlining is only used because it can visually represent that this is a placeholder.

The pipeline operator also applies to the await function.

x |> await f
// Equivalent to
await f(x)

const userAge = userId |> await fetchUserById |> getAgeFromUser;
// Equivalent to
const userAge = getAgeFromUser(await fetchUserById(userId));

5. Math.signbit()

Math.sign() is used to determine whether a value is positive or negative, but if the parameter is -0, it returns -0.

Math.sign(-0) // -0

This results in Math.sign() is not very useful. JavaScript uses 64-bit floating point numbers (International Standard IEEE 754) for numeric values. IEEE 754 specifies that the first bit is a sign bit, 0 is a positive number, and 1 is a negative number. So there are two kinds of zeros, +0 is a zero when the sign bit is 0, and -0 is a zero when the sign bit is 1. In actual programming, it is very difficult to determine whether a value is + 0 or -0 because they are equal.

+0 === -0 // true

At present, there is one proposal , introduced Math. The signbit() method determines whether the sign bit of a number is set.

Math.signbit(2) //false
Math.signbit(-2) //true
Math.signbit(0) //false
Math.signbit(-0) //true

As you can see, the method correctly returns that the symbol bits for -0 are set.

The algorithm for this method is as follows.

  • Returns false if the parameter is NaN
  • Returns true if the parameter is -0
  • Returns true if the parameter is negative
  • Other cases return false

6. Double colon operator

The arrow function can bind this objects, greatly reducing the explicit binding of this objects (call, apply, bind). However, the arrow function is not applicable in all situations, so now there is one proposal A function bind operator is proposed to replace call, apply, and bind calls.

The function binding operator is a side-by-side two colons (:), with a double colon on the left and a function on the right. It automatically binds the left object to the right function as the context (that is, the this object).

foo::bar;
// Equivalent to
bar.bind(foo);

foo::bar(...arguments);
// Equivalent to
bar.apply(foo, arguments);

const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
  return obj::hasOwnProperty(key);
}

If the left side of the double colon is empty and the right side is a method of an object, it is equivalent to binding the method to the object.

var method = obj::obj.foo;
// Equivalent to
var method = ::obj.foo;

let log = ::console.log;
// Equivalent to
var log = console.log.bind(console);

If the result of the double colon operator is still an object, it can be written in chains.

import { map, takeWhile, forEach } from "iterlib";

getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));

7. Realm API

Realm API Provides sandbox es that allow code isolation to prevent isolated code from reaching global objects.

Previously, often Use <iframe> As a sandbox.

const globalOne = window;
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const globalTwo = iframe.contentWindow;

In the code above, <iframe>global objects are independent (iframe.contentWindow). The Realm API can replace this functionality.

const globalOne = window;
const globalTwo = new Realm().global;

In the code above, the Realm API provides a single global object, new Realm().global.

The Realm API provides a Realm() constructor to generate a Realm object. The object's global property points to a new top-level object that resembles the original top-level object.

const globalOne = window;
const globalTwo = new Realm().global;

globalOne.evaluate('1 + 2') // 3
globalTwo.evaluate('1 + 2') // 3

In the code above, the evaluate() method of the top-level object generated by Realm can run the code.

The code below demonstrates that the Realm top-level object and the original top-level object are two objects.

let a1 = globalOne.evaluate('[1,2,3]');
let a2 = globalTwo.evaluate('[1,2,3]');
a1.prototype === a2.prototype; // false
a1 instanceof globalTwo.Array; // false
a2 instanceof globalOne.Array; // false

In the code above, the prototype object of the array in the Realm sandbox is different from the array in the original environment.

The Realm sandbox can only run API s provided by the ECMAScript syntax, not those provided by the host environment.

globalTwo.evaluate('console.log(1)')
// throw an error: console is undefined

In the code above, there is no console object in the Realm sandbox, causing an error. Because console is not a grammar standard, it is provided by the host environment.

If you want to solve this problem, you can use the code below.

globalTwo.console = globalOne.console;

The Realm() constructor accepts a parameter object whose intrinsics property specifies how the Realm sandbox inherits the original top-level object.

const r1 = new Realm();
r1.global === this;
r1.global.JSON === JSON; // false

const r2 = new Realm({ intrinsics: 'inherit' });
r2.global === this; // false
r2.global.JSON === JSON; // true

In the code above, the sandbox's JSON method is normally different from the original JSON object. However, once the Realm() constructor accepts {intrinsics:'inherit'} as an argument, it inherits the method of the original top-level object.

Users can define their own subclasses of Realm to customize their sandboxes.

class FakeWindow extends Realm {
  init() {
    super.init();
    let global = this.global;

    global.document = new FakeDocument(...);
    global.alert = new Proxy(fakeAlert, { ... });
    // ...
  }
}

In the code above, FakeWindow simulates a false top-level object window.

8. #! command

Command line scripts for Unix support #! Command, also known as Shebang or Hashbang. This command is placed on the first line of the script to specify the executor of the script.

For example, the first line of a Bash script.

#!/bin/sh

The first line of a Python script.

#!/usr/bin/env python

Now there is one proposal , introduced # for JavaScript scripts! Command, written on the first line of a script or module file.

// Write on the first line of the script file
#!/usr/bin/env node
'use strict';
console.log(1);

// Write on the first line of the module file
#!/usr/bin/env node
export {};
console.log(1);

With this line, the Unix command line can execute the script directly.

# How scripts were previously executed
$ node hello.js

# The way hashbang works
$ ./hello.js

For the JavaScript engine, it will #! Understand as a comment and ignore this line.

9. import.meta

When a developer uses a module, he or she sometimes needs to know some information about the template itself (such as the path to the module). Now there is one proposal , a meta-attribute import was added to the import command. Meta, returns the meta information of the current module.

import.meta can only be used inside a module and will error if used outside the module.

This property returns an object whose various properties are the meta-information of the currently running script. What attributes are included in the standard is not specified and it is up to each operating environment to decide. Generally speaking, import.meta has at least two of the following properties.

(1)import.meta.url

Import. Meta. The URL returns the URL path of the current module. For example, the path to the main file of the current module is https://foo.com/main.js , import.meta.url returns to this path. If there is also a data file in the module. Txt, then you can use the code below to get the path to this data file.

new URL('data.txt', import.meta.url)

Note that Node. In the JS environment, import. Meta. A URL always returns a local path, which is a string of the file:URL protocol, such as file:///home/user/foo.js .

(2)import.meta.scriptElement

Import. Meta. A scriptElement is a browser-specific meta-attribute that returns the <script>element that loads the module, equivalent to a document. CurrtScript property.

// The HTML code is
// <script type="module" src="my-module.js" data-foo="abc"></script>

// my-module.js executes the following code internally
import.meta.scriptElement.dataset.foo
// "abc"

10. JSON module

The import command can only be used to load ES modules at this time. There is now one proposal Allows loading of JSON modules.

Suppose you have a JSON module file config.json.

{
  "appName": "My App"
}

Currently, only fetch() can be used to load JSON modules.

const response = await fetch('./config.json');
const json = await response.json();

Once the import command is able to load the JSON module directly, it can be written as follows.

import configData from './config.json' assert { type: "json" };
console.log(configData.appName);

In the example above, the entire JSON object is imported as a configData object from which JSON data can be obtained.

The assert {type:'json'} at the end of the command is indispensable when the ==import command imports a JSON module. This is called an import assertion and tells the JavaScript engine that the JSON module is now loaded. == You might ask why you didn't pass. What about the JSON suffix name? Because browsers traditionally do not use suffix names to determine file types, the Standards Committee wants to follow this practice, which also avoids some security issues.

Import assertions are the standard way JavaScript imports modules in other formats, and the JSON module will be the first module to be imported using this syntax. In the future, importing CSS modules, HTML modules, and so on, will also be supported.

The import() function for dynamically loading modules also supports loading JSON modules.

import('./config.json', { assert: { type: 'json' } })

After the script loads the JSON module, it can also be output with the export command. In this case, export and import can be combined into one statement.

export { config } from './config.json' assert { type: 'json' };

Keywords: Javascript Front-end ECMAScript

Added by php new bie on Mon, 27 Dec 2021 08:19:49 +0200