Latest ES6 proposal

Posted by Savahn on Sun, 20 Feb 2022 14:41:33 +0100

1, do expression

In essence, a block level scope is a statement that encapsulates multiple operations without a return value.

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

In the above code, the block level scope encapsulates the two statements together. However, there is no way to get the value of T outside the block level scope, because the block level scope does not return a value unless t is a global variable.
Now there is a proposal to make the block level scope become an expression, that is, it can return a value. The way is to add do before the block level scope to make it become a do expression, and then return the value of the last internal expression.

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

In the above code, variable x will get the return value of the whole block level scope (t * t + 1).

The logic of the do expression is very simple: what is encapsulated will be returned.

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

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

The advantage of do expression is that it can encapsulate multiple statements and make the program more modular, just like Lego blocks.

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

The essence of the above code is to call different functions according to the execution result of function foo and assign the returned result to variable x. Using the do expression, the intention of this operation is expressed very succinctly and clearly. Moreover, the do block level scope provides a separate scope, and internal operations can be isolated from the global scope.

It is worth mentioning that the do expression is very easy to use in JSX syntax.

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

In the above code, if you do not use the do expression, you can only use the ternary judgment operator (?:). In that case, once the judgment logic is complex, the code will become very difficult to read.

2, throw expression

JavaScript syntax stipulates that throw is a command used to throw errors and cannot be used in expressions.

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

In the above code, console The parameter of log must be an expression. If it is a throw statement, an error will be reported.

Now there is a proposal to allow 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 above code, throw appears in the expression.

Syntactically, throw in the throw expression is no longer a command, but an operator. In order to avoid confusion with the throw command, it is specified that throw appears at the beginning of the line and is interpreted as a throw statement rather than a throw expression.

3, Partial execution of function

Multi parameter functions sometimes need to bind one or more parameters and then return a new function.

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

In the above code, 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);

There is some redundancy in the above two ways. Among them, the limitation of bind method is more obvious. It must provide this, and can only bind parameters from the front to the back, not only non header parameters.

Now there is a proposal to make 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, ?);

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

According to the new proposal,? Is a placeholder for a single parameter,... Is a placeholder for multiple parameters. The following forms belong to the partial execution of functions.

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

? And... Can only appear in the call of a function and will return a new function.

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

The partial execution of the function can also be used for the method of the object.

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

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

4, Pipeline operator

Unix operating system has a pipeline mechanism, which can pass the value of the previous operation to the next operation. This mechanism is very useful, so that simple operations can be combined into complex operations. Many languages have pipeline implementation, and now there is a proposal to let JavaScript also have pipeline mechanism.

The pipeline of JavaScript is an operator, writing | >. On its left is an expression and on its right is a function. The pipeline operator passes the value of the expression on the left to the function on the right for evaluation.

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

The biggest advantage of pipeline operator is that nested functions can be written as chained expressions from left to right.

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

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

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

Above are three simple functions. If nested execution is required, the traditional writing method and pipeline writing method are as follows.

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

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

The pipeline operator can only pass one value, which means that the function to the right of it must be a single parameter function. If it is a multi parameter function, it must be coriolised and changed to a single parameter version.

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 above code, the add function requires two parameters. However, the pipeline operator can only pass in one value, so you need to provide another parameter in advance and change it into a single parameter arrow function = > add(7, _). The underline in this function has no special meaning and can be replaced by other symbols. The underline is only used because it can vividly 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 judge the positive and negative of a value, but if the parameter is - 0, it will return - 0.

Math.sign(-0) // -0

This leads to the negative and positive of the judgment symbol bit, math Sign () is not very useful. JavaScript internally uses 64 bit floating-point numbers (international standard IEEE 754) to represent values. IEEE 754 stipulates that the first bit is a sign bit, 0 represents a positive number and 1 represents a negative number. Therefore, there are two kinds of zeros, + 0 is the zero value when the sign bit is 0, and - 0 is the zero value when the sign bit is 1. In actual programming, it is very troublesome to judge whether a value is + 0 or - 0 because they are equal.

+0 === -0 // true

At present, there is a proposal to introduce 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

You can see that the method correctly returns the sign bit of - 0, which is set.

The algorithm of this method is as follows.

  • If the parameter is NaN, false is returned
  • If the parameter is true, it returns - 0
  • Returns true if the parameter is negative
  • false in other cases

6, Double colon operator

The arrow function can bind this object, which greatly reduces the writing methods of explicitly binding this object (call, apply, bind). However, the arrow function is not applicable to all situations, so now there is a proposal to propose a "function bind" operator to replace call, apply and bind calls.

The function binding operator is two colons (::) side by side. The left side of the double colon is an object and the right side is a function. The operator will automatically bind the object on the left to the function on the right as the context (i.e. 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 the 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 chain.

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

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

Topics: Javascript Front-end