Extension of ECMAScript 6 (ES6) entry function

1, Default value of function parameter

Before ES6, you can't directly specify the default value for the function's parameters. You can only use flexible methods.

function log(x, y) {
  y = y || 'World';            //If the left side of operator |, is true, it will directly return the value on the left side, otherwise it will return the value on the right side
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello World

The above code checks whether the parameter y of the function log is assigned. If not, the default value is World. The disadvantage of this method is that if the parameter y is assigned, but the corresponding Boolean value is false, the assignment will not work. Just like the last line of the above code, the parameter y is equal to the null character, and the result is changed to the default value.

In order to avoid this problem, it is usually necessary to determine whether the parameter y is assigned, and if not, then it is equal to the default value.

if (typeof y === 'undefined') {
  y = 'World';
}

ES6 allows you to set a default value for the parameters of a function, which is written directly after the parameter definition.

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

Parameter variables are declared by default, so they cannot be declared again with let or const.

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

When using parameter defaults, a function cannot have parameters with the same name.

// No mistake
function foo(x, x, y) {
  // ...
}

// Report errors
function foo(x, x, y = 1) {
  // ...
}
// SyntaxError: Duplicate parameter name not allowed in this context

In addition, it is easy to ignore that the default value of the parameter is not transferred, but the value of the default value expression is recalculated every time. That is, the default value of the parameter is evaluated lazily.

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

In the above code, the default value of parameter p is x + 1. In this case, every time the function foo is called, x + 1 will be recalculated instead of the default P equal to 100.

2, Use with deconstruction assignment defaults

The parameter default value can be used in combination with the default value of the deconstruction assignment.

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

The above code only uses the default value of the object's deconstruction assignment, not the default value of the function parameter. Only when the parameter of foo is an object can variables x and y be generated by deconstruction. If the foo function is called without parameters, the variables x and y will not be generated and an error will be reported. This can be avoided by providing default values for function parameters.

function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5

The above code specifies that if no parameter is provided, the parameter of function foo is an empty object by default.

Here is another example of deconstructing assignment defaults.

function fetch(url, { body = '', method = 'GET', headers = {} }) {
  console.log(method);
}

fetch('http://example.com', {})
// "GET"

fetch('http://example.com')
// Report errors

In the above code, if the second parameter of the function fetch is an object, you can set the default values for its three properties. This method can not omit the second parameter. If we combine the default value of function parameter, we can omit the second parameter. At this point, a double default appears.

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

3, Location of parameter defaults

In general, the parameter that defines the default value should be the tail parameter of the function. Because it's easier to see which parameters are omitted. If the non trailing parameter is set to the default value, in fact, this parameter cannot be omitted.

// Example 1
function f(x = 1, y) {
  return [x, y];
}

f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // Report errors
f(undefined, 1) // [1, 1]

// Example two
function f(x, y = 5, z) {
  return [x, y, z];
}

f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // Report errors
f(1, undefined, 2) // [1, 5, 2]

In the above code, parameters with default values are not tail parameters. At this point, you cannot omit only this parameter and the parameters after it, unless you explicitly enter undefined.

If undefined is passed in, the parameter will be triggered equal to the default value, and null has no such effect.

function foo(x = 5, y = 6) {
  console.log(x, y);
}

foo(undefined, null)
// 5 null

In the above code, the x parameter corresponds to undefined. As a result, the default value is triggered. If the y parameter is equal to null, the default value is not triggered.

4, The length property of the function

When a function parameter specifies a default value, the length property of the function returns the number of parameters without a default value. This is because the meaning of the length property is the number of arguments that the function expects to pass in.

// When the default value is set, the expected number of parameters will be different from the actual number
(function (x) {}).length; // 1
(function (x = 5) {}).length; // 0
(function (x, y, z = 5) {}).length; // 2

5, Scope

Once the default value of the parameter is set, the parameter will form a separate scope when the function is declared and initialized. After initialization, the scope disappears.

var x = 1;

function f(x, y = x) {
  console.log(y);
}

f(2) // 2

In the above code, the default value of parameter y is equal to variable x. When function f is called, the parameters form a separate scope. In this scope, the default variable x points to the first parameter x, not the global variable x, so the output is 2.

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

In the above code, when function f is called, the parameter y = x forms a separate scope. In this scope, the variable x itself is not defined, so it points to the outer global variable x. When a function is called, the local variable x inside the function body does not affect the default value variable x.

If the global variable x does not exist at this time, an error will be reported.

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // ReferenceError: x is not defined

6, rest parameter

ES6 introduces the rest parameter (in the form of...) , which is used to get the extra parameters of the function.

function add(...values) {
  let sum = 0;

  for (var val of values) {
    sum += val;
  }

  return sum;
}

add(2, 5, 3) // 10

rest parameter is a real array, and array specific methods can be used.

function printStr(...items) {
  items.forEach((item) => {
    console.log(item);
  });
}

printStr(1, 2, 3); // 1 2 3

The rest parameter can only be followed by the last parameter, otherwise an error will be reported.

function f(x, ...y, z) { } // Uncaught SyntaxError: Rest parameter must be last formal parameter

The length property of the function, excluding the rest parameter.

(function(...x) {}).length;  // 0

For more details about the rest function, see here

In depth interpretation of ES6 series (all 18 Lectures), see Introduction for details

7, Strict mode

Starting with ES5, strict mode can be set inside the function. However, ES2016 has made some modifications to stipulate that as long as the function parameters use default values, deconstruction assignment, or extension operators, the internal function cannot be explicitly set to strict mode, otherwise an error will be reported.

// The function parameter uses the default value, and the internal use of strict mode will result in an error: illegal 'use strict' directive in function with non simple parameter list
function test(x, y = x) {
  'use strict';
}

// The function parameter uses the default value, and the internal strict mode will report an error
function test ({x, y}) {
  'use strict';
};

// Function parameters use extension operators. If strict mode is used internally, an error will be reported
function test (...a) => {
  'use strict';
};

8, name attribute

The name property of the function, which returns the function name of the function.

function foo() {}
foo.name // "foo"

This property has long been widely supported by browsers, but it was not written to the standard until ES6.

It should be noted that ES6 has made some changes to the behavior of this attribute. If an anonymous function is assigned to a variable, the name property of ES5 will return an empty string, and the name property of ES6 will return the actual function name.

var f = function () {};

// ES5
f.name // ""

// ES6
f.name // "f"

If a named function is assigned to a variable, the name properties of ES5 and ES6 return the original name of the named function.

const bar = function baz() {};

// ES5
bar.name // "baz"

// ES6
bar.name // "baz"

The Function instance returned by the Function constructor. The value of the name property is anonymous.

(new Function).name // "anonymous"
bind Function returned, name Property value will be added bound Prefix.

function foo() {};
foo.bind({}).name // "bound foo"

(function(){}).bind({}).name // "bound "

9, Arrow function

ES6 allows you to define functions using "arrows" (= >).

var f = function(x) {
	return x;
} 

// The above example can be rewritten as follows with arrow function
var f = x => x;

If the function has no arguments or multiple arguments, you need to use parentheses.

// No parameters 
var f = () => "jidi";
// Equivalent to
var f = function() {
	return "jidi";
}

// There are multiple parameters
var f = (x, y) => x + y;
// Equivalent to
var f = function (x, y) {
	return x + y;
}

If there is more than one statement in the code block of the arrow function, enclose them with braces ({}), and return them with the return statement.

var sum = (x, y) => { return x + y; }

If the arrow function returns an object directly, parentheses must be added outside the object, otherwise an error will be reported.

// If the returned object is an object, parenthesis should be added outside the object, otherwise an error will be reported.
let getPerson = id => { id: id, name: "jidi" }; // Uncaught SyntaxError: Unexpected token ':'

// Parenthesized, no error reported
let getPerson = id => ({ id: id, name: "jidi" });

getPerson(1); // {id: 1, name: "jidi"}

Arrow functions can be used in conjunction with variable deconstruction.

// Arrow function with deconstruction assignment
const getStr= ({ name = "jidi", age = 22 } = {}) => name+ ' ' + age;

// Equate to
function getStr({ name = "jidi", age = 22 } = {}) {
  return name+ ' ' + age;
}

getStr(); // "jidi 22"
getStr({name: "java", age: 20}); // "java 20"

The arrow function can be used in conjunction with the rest parameter.

const numbers = (...number) => number;
// The rest parameter is an array, and the result returns an array
numbers(1, 2, 3, 4, 5); // [1,2,3,4,5]

// Use arrow function and rest parameter to calculate the sum of n numbers
const add = (...items) => {
	let total = 0;
	items.forEach(item => total += item)
	return total;
}

add(1, 2, 3); // 6

Video digests and digests orligan

In depth interpretation of ES6 series (all 18 Lectures), see Introduction for details

Notes on the use of arrow function

Although arrow function is more convenient to use, there are also some points needing attention of value.

  1. this object in the function body is the object when the function is defined, not the object when it is used.
  2. Cannot be used as a constructor, i.e. cannot use the new command.
  3. The arguments object cannot be used because it does not exist inside the arrow function and can be replaced with the rest parameter.
  4. If you cannot use the yield command, the arrow function cannot be used as a Generator function.
  5. The direction of this object is variable, but in the arrow function, it is fixed.
var id = 21;

// Arrow function form
function f1() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}
f1(); // id: 21
f1.call({ id: 42 }); // id: 42

// Normal function form
function f2() {
  setTimeout(function() {
    console.log('id:', this.id);
  }, 100);
}
f2();  // id: 21
f2.call({ id: 42 }); // id: 21

In the above code, the parameter of setTimeout is an arrow function. When the definition of the arrow function takes effect, when the f1 function is generated, the arrow function causes this to always point to the object where the function definition takes effect, so the output is 42. For ordinary functions, this points to the global object during execution, and the output should be 21.

This point is fixed. In fact, the arrow function does not have its own this, so the internal this is the external code block's this. So if the code in the above example is converted to ES5 code, it is in the following form.

// ES6
function f1() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

// ES5
function f1() {
  var _this = this;
  setTimeout(() => {
    console.log('id:', _this.id);
  }, 100);
}

In addition to this, arguments, super, and new.target do not exist in the arrow function. They also point to the corresponding variables of the outer function.

function f() {
  setTimeout(() => {
    console.log('arguments:', arguments);
  }, 100);
}

f(2, 4, 6, 8); // arguments: [2, 4, 6, 8]

In the above example, the srcomments variable inside the arrow function points to the arguments variable of the outer function f.

Because the arrow function does not have its own this, you cannot use call(), apply(), bind() to change the direction of this.

Arrow function not applicable

Arrow function can fix this. There are two places where arrow function cannot be used.

  • Defines the method of the object, and the object method contains this.
  • When you need to use this dynamically, you cannot use the arrow function.

10, Tail call optimization

What is a tail call?

Tail Call is an important concept of functional programming. It is very simple. In a word, it means that the last step of a function is to call another function.

function f(x){
  return g(x);   //The last step of function f is to call function g, which is called tail call.
}

The following three cases are not tail calls.

// Situation 1
function f(x){
  let y = g(x);
  return y;  //The first case is that after calling function g, there is assignment operation, so it is not tail call
}

// Situation two
function f(x){
  return g(x) + 1;  //Case 2 also belongs to operation after calling, even if it is written in one line.
}

// Situation three
function f(x){
  g(x);
}

//Case three is equivalent to the following code.

function f(x){
  g(x);
  return undefined;
}

The tail call does not necessarily appear at the end of the function, as long as it is the last step.

function f(x){
  if(x > 0){
    return m(x);
  }
  return n(x);
}

Tail call optimization

The reason why tail call is different from other calls is its special call location.

As we know, function calls will form a "call record", also known as "call frame", in memory to save information such as call location and internal variables. If function B is called inside function a, a call frame of B will be formed above the call frame of A. The call frame of B will not disappear until B runs and returns the result to a. If function C is also called inside function B, then there is a call frame of C, and so on. All call frames form a call stack.

Tail call is the last step of function operation, so it is not necessary to keep the call frame of outer function, because the call position, internal variables and other information will not be used again, as long as the call frame of inner function is directly used instead of the call frame of outer function.

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// Equate to
function f() {
  return g(3);
}
f();

// Equate to
g(3);

In the above code, if function g is not a tail call, function f needs to save the values of internal variables m and n, the call location of G and other information. However, after calling g, the function f is finished, so at the last step, the call frame of f(x) can be deleted completely, and only the call frame of g(3) can be reserved.

This is called Tail call optimization, that is, only the call frame of inner function is reserved. If all functions are tail calls, it can be completely achieved that there is only one call frame per execution, which will greatly save memory. This is the meaning of "Tail call optimization".

Note: only when the internal variables of the outer function are no longer used, the call frame of the inner function will replace the call frame of the outer function, otherwise "tail call optimization" cannot be carried out.

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

The function above will not perform tail call optimization because the inner function uses the inner variable one of the outer function addOne.

Note: currently only Safari browser supports tail call optimization, neither Chrome nor Firefox.

11, Tail recursion

Definition

Function calls themselves, called recursion. If the tail calls itself, it is called tail recursion

Recursion consumes a lot of memory, because it needs to save hundreds of call frames at the same time, so it is easy to have a "stack overflow" error. However, because there is only one call frame, stack overflow will never occur.

function f(n){
  if(n === 1) return 1;
  return n * f(n - 1);
}
console.log(f(5)); // 120

The above is factorial operation, which calculates the factorial of N, and saves n call records at most, with complexity O(n)

If it is changed to tail recursion, only one call record is kept, complexity O(1)

function f(n, total){
  if(n === 1) return total;
  return f(n -1 ,n * total);
}
console.log(f(5,1));

Rewriting recursive functions

The implementation of tail recursion often needs to rewrite recursive functions to ensure that the last step only calls itself. The way to do this is to rewrite all the internal variables used into function parameters. For example, in the example above, factorial needs to use an intermediate variable total, which is then rewritten as a function parameter. The disadvantage of this is that it is not very intuitive. At first glance, it is hard to see why two parameters 5 and 1 need to be passed in to calculate the factorial of 5?

There are two ways to solve this problem. The first method is to provide a normal function in addition to the tail recursive function.

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

function factorial(n) {
  return tailFactorial(n, 1);
}

factorial(5) // 120

The above code calls tail recursive function tailFactorial through a normal factorial function, which looks much more normal.

Functional programming has a concept called currying, which means to convert multi parameter functions into single parameter forms. You can also use currification here.

function currying(fn, n) {
  return function (m) {
    return fn.call(this, m, n);
  };
}

function tailFactorial(n, total) {
  if (n === 1) return total;
  return tailFactorial(n - 1, n * total);
}

const factorial = currying(tailFactorial, 1);

factorial(5) // 120

The above code changes tail recursive function tailFactorial to factorial that only takes one parameter through currification.

The second method is much simpler, which is to use the function default value of ES6.

function factorial(n, total = 1) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5) // 120

In the above code, the parameter total has a default value of 1, so this value is not provided when calling.

To summarize, recursion is essentially a circular operation. Pure functional programming languages do not have loop operation commands, and all loops are implemented by recursion, which is why tail recursion is extremely important to these languages. For other languages that support "tail call optimization" (such as Lua, ES6), you only need to know that loops can be replaced by recursion. Once you use recursion, you'd better use tail recursion.

Strict mode

ES6 tail call optimization is only enabled in strict mode, and normal mode is invalid.

This is because in normal mode, there are two variables inside the function, which can track the call stack of the function.

func.arguments: returns the parameters of the function at the time of the call.
func.caller: returns the function that calls the current function.
When tail call optimization occurs, the call stack of the function will be rewritten, so the above two variables will be distorted. Strict mode disables these two variables, so the tail call mode only works in strict mode.

function restricted() {
  'use strict';
  restricted.caller;    // Report errors
  restricted.arguments; // Report errors
}
restricted();

The implementation of tail recursive optimization

Tail recursion optimization only works in strict mode. Is there any way to use tail recursion optimization in normal mode or those environments that do not support this function? The answer is yes, that is to realize tail recursive optimization.

Its principle is very simple. The reason why tail recursion needs to be optimized is that there are too many call stacks, resulting in overflow. As long as the call stack is reduced, it will not overflow. How can I reduce the call stack? It is to replace "recursion" with "Circulation".

Here is a normal recursive function.

function sum(x, y) {
  if (y > 0) {
    return sum(x + 1, y - 1);
  } else {
    return x;
  }
}

sum(1, 100000)
// Uncaught RangeError: Maximum call stack size exceeded(...)

In the above code, sum is a recursive function, parameter x is the value to be accumulated, and parameter y controls the number of recursions. Once sum recursion is specified 100000 times, an error will be reported, indicating that the maximum number of times the call stack is exceeded.

trampoline functions can turn recursive execution into circular execution.

function trampoline(f) {
  while (f && f instanceof Function) {
    f = f();
  }
  return f;
}

The above is an implementation of trampoline function, which takes a function f as a parameter. As long as f returns a function after execution, execution continues. Note that here is to return a function and execute the function instead of calling the function inside the function, so as to avoid recursive execution and eliminate the problem of too large call stack.

Then, all we have to do is rewrite the original recursive function to return another function for each step.

function sum(x, y) {
  if (y > 0) {
    return sum.bind(null, x + 1, y - 1);
  } else {
    return x;
  }
}

In the above code, each execution of sum function will return another version of itself.

Now, using the trampoline function to perform sum, there is no call stack overflow.

trampoline(sum(1, 100000))
// 100001

Trampoline function is not the real tail recursive optimization, the following implementation is.

function tco(f) {
  var value;
  var active = false;
  var accumulated = [];

  return function accumulator() {
    accumulated.push(arguments);
    if (!active) {
      active = true;
      while (accumulated.length) {
        value = f.apply(this, accumulated.shift());
      }
      active = false;
      return value;
    }
  };
}

var sum = tco(function(x, y) {
  if (y > 0) {
    return sum(x + 1, y - 1)
  }
  else {
    return x
  }
});

sum(1, 100000)
// 100001

In the above code, tco function is the implementation of tail recursive optimization, and its secret lies in the active state variable. By default, this variable is not active. Once in the tail recursive optimization process, this variable is activated. Then, every round of recursive sum returns undefined, so recursive execution is avoided; and the accumulated array stores the parameters of every round of sum execution, which always has values, which ensures that the while loop inside the accumulator function will always execute. In this way, it is very clever to change "recursion" into "loop", and the parameters of the next round will replace the parameters of the previous round, so as to ensure that the call stack has only one layer.

12, Trailing comma of function parameter

ES2017 allows the last argument of a function to have a trailing comma.

// ES2017 allows function parameters to be followed by commas
function add(x, y,) {
	return x + y;
}

13, Function.prototype.toString()

ES2019 changes toString() method of function instance. As like as two peas, the toString () method returns the function code omitted by annotations and spaces. The modified toStirng() method returns the same code.

function add(...items) {
    // Summation function
    let total = 0;
    items.forEach(item => total += item);
    return total;
} 

add.toString();	// "function add(...items) {
				//	   //Summation function
				//	   let total = 0;
				//	   items.forEach(item => total += item);
				//	   return total;
				//	}"

14, Parameter omission of catch command

Try in JavaScript The catch structure explicitly requires that the catch command must be followed by a parameter to accept the error object thrown by the try code block.

try {
  // ...
} catch (err) {
  // Handling errors
}

In the above code, the catch command is followed by the err parameter.

In many cases, the catch code block may not use this parameter. However, in order to ensure the correct grammar, it is necessary to write. ES2019 has changed to allow the catch statement to omit parameters.

try {
  // ...
} catch {
  // ...
}

15, Reference link

Getting started with ECMAScript 6
Author: Ruan Yifeng

It's finished
cry

Published 23 original articles, praised 0, visited 3017
Private letter follow

Tags: REST Programming Attribute Java

Posted on Sun, 08 Mar 2020 08:06:26 -0400 by AStrangerWCandy