May 08, 2021 ES6
In essence, a block-level scope is a statement that encapsulates multiple operations without returning a value.
{
let t = f();
t = t * t + 1;
}
In the above code, the block-level scope encapsulates the two statements together. However, outside the block-level scope, there is no way to get a value for t because the block-level scope does not return a value unless t is a global variable.
Now there is a proposal that allows a block-level scope to become an expression, that is, a value can be returned by adding a 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 above code, the variable x gets the return value of the entire block-level scope (t-t-1).
The logic of a do expression is simple: what is encapsulated and what is returned.
// 等同于 <表达式>
do { <表达式>; }
// 等同于 <语句>
do { <语句> }
The advantage of do expressions is that you can encapsulate multiple statements and make your program more modular, piece by piece like a LEGO brick.
let x = do {
if (foo()) { f() }
else if (bar()) { g() }
else { h() }
};
The essence of the above code is to call different functions based on the result of the execution of the function foo and assign the return result to the variable x. U sing a do expression, the intent of this operation is expressed very concisely and clearly. Furthermore, do block-level scopes provide separate scopes where internal operations can be isolated from global scopes.
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 above code, if you do not use the do expression, you can only judge the operator (?:) with a thyme. In that case, once the judgment logic is complex, the code becomes difficult to read.
The JavaScript syntax states that
throw
is a command that throws an error and cannot be used in an expression.
// 报错
console.log(throw new Error());
In the above code, the .log of the console must be an expression, which would be reported as an error if it were a throw statement.
Now there is a proposal that allows throw to be used for expressions.
// 参数的默认值
function save(filename = throw new TypeError("Argument required")) {
}
// 箭头函数的返回值
lint(ast, {
with: () => throw new Error("avoid using 'with' statements.")
});
// 条件表达式
function getEncoder(encoding) {
const encoder = encoding === "utf8" ?
new UTF8Encoder() :
encoding === "utf16le" ?
new UTF16Encoder(false) :
encoding === "utf16be" ?
new UTF16Encoder(true) :
throw new Error("Unsupported encoding");
}
// 逻辑表达式
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.
Syntax, the throw inside the throw expression is no longer a command, but an operator. To avoid confusion with the throw command, it is specified that the throw appears at the top of the line and is interpreted as a throw statement, not a throw expression.
Multi-argument functions sometimes need to bind one or more of these arguments and then 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, and you can get add7 from add by binding an argument to 7.
// bind 方法
const add7 = add.bind(null, 7);
// 箭头函数
const add7 = x => add(x, 7);
Both of these writings are somewhat redundant. Among them, the limitations of the bind method are more obvious, it must provide this, and can only be from front to back one binding parameters, can not only bind non-head parameters.
Now there is a proposal that makes it easier to bind parameters and return a new function. This is called partial execution of a function.
const add = (x, y) => x + y;
const addOne = add(1, ?);
const maxGreaterThanZero = Math.max(0, ...);
According to the new proposal, ? i s a placeholder for a single argument, ... i s a placeholder for multiple parameters. The following forms are part of the execution of the function.
f(x, ?)
f(x, ...)
f(?, x)
f(..., x)
f(?, x, ?)
f(..., x, ...)
? A nd... Can only appear in function calls, and a new function is returned.
const g = f(?, 1, ...);
// 等同于
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
There are some special notes about the partial execution of functions.
(1) Part of the function's execution is based on the original function. If the original function changes, the new function generated by partial execution immediately reflects the change.
let f = (x, y) => x + y;
const g = f(?, 3);
g(1); // 4
// 替换函数 f
f = (x, y) => x * y;
g(1); // 3
In the code above, after the part of the function is defined for execution, 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 the time of each call.
let a = 3;
const f = (x, y) => x + y;
const g = f(?, a);
g(1); // 4
// 改变 a 的值
a = 10;
g(1); // 11
In the above code, the pre-provided argument is variable a, which is not valued until each time the function g is called.
(3) If the new function has more arguments than placeholders, the extra arguments are ignored.
const f = (x, ...y) => [x, ...y];
const g = f(?, 1);
g(2, 3, 4); // [2, 1]
In the above code, the function g has only one placeholder, which means that it can only accept one argument, and the extra arguments are ignored.
Write it down so that the extra parameters are no problem.
const f = (x, ...y) => [x, ...y];
const g = f(?, 1, ...);
g(2, 3, 4); // [2, 1, 3, 4];
(4) ... I t is only collected once, and if the partial execution of the function uses more than one ... then each ... The values of will be the same.
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 they are actually executed, have the same value.
The Unix operating system
管道机制
pipeline mechanism that passes the value of the previous operation to the latest operation. T
his mechanism is useful for combining simple operations into complex operations.
Many languages have pipeline implementations, and now there is a
proposal
for JavaScript to also have a pipeline mechanism.
JavaScript's pipeline is an operator that
|>
I
t has an expression on the left and a function on the right.
The pipeline operator evaluates the value of the expression on the left and into the function on the right.
x |> f
// 等同于
f(x)
The biggest benefit of pipeline operators is that nested functions can be written 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 + '!';
}
Above are three simple functions. If you want to nest execution, the traditional way of writing and pipe writing are as follows.
// 传统的写法
exclaim(capitalize(doubleSay('hello')))
// "Hello, hello!"
// 管道的写法
'hello'
|> doubleSay
|> capitalize
|> exclaim
// "Hello, hello!"
The pipeline operator can pass only one value, which means that the function to its right must be a single-argument function. If it is a multi-argument function, it must be Curryized and changed to a single-argument 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 code above, the add function requires two parameters. H owever, the pipeline operator can only pass in one value, so you need to provide another argument in advance and change it to a single-argument arrow function, . The underscore in this function has no special meaning and can be replaced with other symbols, using underscores only because it can visually indicate that this is a placeholder.
The pipeline operator also applies to the await function.
x |> await f
// 等同于
await f(x)
const userAge = userId |> await fetchUserById |> getAgeFromUser;
// 等同于
const userAge = getAgeFromUser(await fetchUserById(userId));
In European and American languages, longer values allow a separator (usually a comma) to be added every three bits to increase the readability of the values.
For example,
1000
can
1,000
There is now a proposal
to
allow JavaScript values to use underscores (
_
let budget = 1_000_000_000_000;
budget === 10 ** 12 // true
JavaScript's numeric separators do not specify the number of digits between them, that is, you can add a separator every three digits, or you can add one separator for each bit, every two digits, every four digits.
123_00 === 12_300 // true
12345_00 === 123_4500 // true
12345_00 === 1_234_500 // true
The number of numbers and scientific notation can also be used for numeric separators.
// 小数
0.000_001
// 科学计数法
1e10_000
Numeric separators have several use points of caution.
The following writing will report errors.
// 全部报错
3_.141
3._141
1_e12
1e_12
123__456
_1464301
1464301_
In addition to the hedding system, other in-progress values can also use separators.
// 二进制
0b1010_0001_1000_0101
// 十六进制
0xA0_B0_C0
Note that separators cannot be followed by prefixes 0b, 0B, 0o, 0O, 0x, 0X.
// 报错
0_b111111000
0b_111111000
The following three functions that convert strings into values do not support numeric separators. The main reason is that the designers of the proposal believe that numerical separators are primarily for the convenience of writing numeric values when encoding, rather than to process externally entered data.
Number('123_456') // NaN
parseInt('123_456') // 123
Math.sign()
to determine the positive or negative value of a value, but if the argument is -0, it returns -0.
Math.sign(-0) // -0
This results in 'Math.sign()' not very useful for judging the positive and negative of the symbol bits. J avaScript internally uses 64-bit floats (international standard IEEE 754) to represent values, and IEEE 754 states that the first place is the sign bit, 0 is the positive number, and 1 is the negative number. S o there will be two kinds of zeros, and 0 is the zero value when the sign bit is 0, and -0 is the zero value when the sign bit is 1. In real programming, it's cumbersome to tell whether a value is .0 or -0 because they are equal.
+0 === -0 // true
Currently, there is a proposal that introduces the Math.signbit() method to determine whether a number of sign bits 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 a sign bit of -0 that is set.
The algorithm for this method is as follows.
Arrow functions can
this
object, greatly reducing the
this
call
bind
apply
binding this object.
However, the arrow function is not
suitable for all situations, so there is now a proposal for a "function bind" operator to
call
apply
bind
calls.
The function binding operator is two colons side by side
::
with an object to the left and a function to the right.
The operator automatically binds the object on the left to the function on the
this
a contextual environment (that is, this object).
foo::bar;
// 等同于
bar.bind(foo);
foo::bar(...arguments);
// 等同于
bar.apply(foo, arguments);
const hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return obj::hasOwnProperty(key);
}
If the double colon is empty to the left and an object's method on the right, it is equivalent to binding the method to the object.
var method = obj::obj.foo;
// 等同于
var method = ::obj.foo;
let log = ::console.log;
// 等同于
var log = console.log.bind(console);
If the result of the operation of the double colon operator is still an object, it can be written chained.
import { map, takeWhile, forEach } from "iterlib";
getPlayers()
::map(x => x.character())
::takeWhile(x => x.strength > 100)
::forEach(x => console.log(x));
The Realm API provides sandboxing, which allows code to be quarantined to prevent isolated code from getting to global objects.
In the past,
<iframe>
used as a sandbox.
const globalOne = window;
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const globalTwo = iframe.contentWindow;
In the code
<iframe>
are independent
iframe.contentWindow
The Realm API can replace this feature.
const globalOne = window;
const globalTwo = new Realm().global;
In the code above, the Realm API provides a separate 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 is similar to 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, realm generates the evaluate() method for the top-level object, which can run the code.
The following code proves that the Realm top-level object is two objects from the original top-level object.
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 inside the Realm sandbox is different from the array in the original environment.
Only the APIs provided by the ECMAScript syntax can be run in the Realm sandbox, not the APIs 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, resulting in an error. Because console is not a syntax standard, it is provided by the host environment.
If you want to solve this problem, you can use the following code.
globalTwo.console = globalOne.console;
The Realm() constructor can accept an argument object that the intrinsics property of the object can specify 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 JSON method of the sandbox is normally different from the original JSON object. However, the Realm() constructor inherits the method of the original top-level object when it accepts the argument of 'inherit'.
Users can define Realm's sub-classes themselves to customize their own 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 fake top-level object window.
Unix's command-line scripts all
#!
C
ommand, 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 the Python script.
#!/usr/bin/env python
Now there is a
proposal to
introduce the command for JavaScript
#!
written on the first line of the script file or module file.
// 写在脚本文件第一行
#!/usr/bin/env node
'use strict';
console.log(1);
// 写在模块文件第一行
#!/usr/bin/env node
export {};
console.log(1);
With this line, the Unix command line can execute the script directly.
# 以前执行脚本的方式
$ node hello.js
# hashbang 的方式
$ ./hello.js
For the JavaScript engine, it's going to be the hashtag! Understand as a comment and ignore the line.
When a developer uses a module, they sometimes need to know some information about the template itself, such as the path to the module.
Now there is a
proposal to
add a meta attribute import.meta to the
import.meta
meta-information for the current module.
import.meta
be used inside the module and will report errors if used outside the module.
This property returns an object that has various properties that are meta-information about the script that is currently running. T
he specific properties are included, the standard does not specify, it is up to the individual operating environment to decide.
In general,
import.meta
at least two properties.
(1)import.meta.url
import.meta.url returns the URL path of the current module. F or example, the path to the current module master file is https://foo.com/main.js, and import.meta.url returns that path. If the module also has a data file .txt, you can use the following code to get the path to the data file.
new URL('data.txt', import.meta.url)
Note that in a Node .js environment, import.meta.url always returns a local path, which is a string of file:URL protocols, such as file:///home/user/foo.js.
(2)import.meta.scriptElement
Import.meta.scriptElement is a browser-specific meta-property that returns the element that loaded the module, equivalent to the document.currentScript property.
// HTML 代码为
// <script type="module" src="my-module.js" data-foo="abc"></script>
// my-module.js 内部执行下面的代码
import.meta.scriptElement.dataset.foo
// "abc"