current position:Home>ES6 summary (06): function extension

ES6 summary (06): function extension

2021-08-27 10:07:17 Xiao Qi, don't make trouble

This is my participation 8 The fourth of the yuegengwen challenge 11 God , Check out the activity details :8 Yuegengwen challenge

One 、 The default value of the function parameter

1. Basic usage

ES6 Before , You can't directly specify default values for parameters of a function , We can only take a flexible approach .

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

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

The above code checks the function log Parameters of y There is no assignment , without , The default value is World.

ES6 Allows you to set default values for parameters of a function , That is, it 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
 Copy code 

You can see ,ES6 Writing ratio ES5 It's a lot simpler , And it's very natural .

Parameter variables are declared by default , So it can't be used let or const Once again .

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

In the above code , Parameter variable x Is declared by default , In the body of a function , Out-of-service let or const Once again , Otherwise, an error will be reported .

When using parameter defaults , Functions cannot have arguments with the same name .

//  Don't complain 
function foo(x, x, y) {
  // ...
}

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

in addition , One thing that is easy to ignore is , The default value of the parameter is not passed , Instead, the value of the default value expression is recalculated every time . in other words , The default value of the parameter is inert .

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

foo() // 100

x = 100;
foo() // 101
 Copy code 

In the above code , Parameters p The default value of is x + 1. At this time , Every time you call a function foo, Will recalculate x + 1, Not by default p be equal to 100.

2. Used in conjunction with deconstruction assignment defaults

The default value of the parameter can be the same as the default value of the deconstruction assignment , Use in combination .

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
 Copy code 

The above code only uses the default value of the deconstruction assignment of the object , The default value of the function parameter is not used . Only if foo When the parameter is an object , Variable x and y Will be generated by deconstruction assignment . If the function foo No arguments were provided during the call , Variable x and y It doesn't generate , To report a mistake .

By providing default values for function parameters , This can be avoided .

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

foo() // undefined 5
 Copy code 

The above code specifies , If no parameters are provided , function foo The default parameter is an empty object .

Here is another example of deconstructing the default value of assignment .

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

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

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

In the above code , If the function fetch The second argument to is an object , You can set default values for its three properties . The second parameter cannot be omitted in this way , If combined with the default value of function parameters , You can omit the second parameter . At this time , There is a double default .

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

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

In the above code , function fetch When there is no second parameter , The default value of the function parameter will take effect , Then the default value of the deconstruction assignment takes effect , Variable method Will get the default value GET.

3. The location of the default value of the parameter

Usually , Parameters that define default values , It should be the tail parameter of the function . Because it's easier to see , What parameters are omitted . If the parameter is not tail, set the default value , In fact, this parameter cannot be omitted .

//  Patients with a 
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 2 
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]
 Copy code 

In the above code , Parameters with default values are not tail parameters . At this time , You cannot omit only this parameter , Without omitting the parameters after it , Unless you explicitly enter undefined.

If you pass in undefined, It will trigger that the parameter is equal to the default value ,null There is no such effect .

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

foo(undefined, null)
// 5 null
 Copy code 

In the above code ,x Parameters of the corresponding undefined, The result triggers the default value ,y The parameter is equal to null, There is no trigger default .

4. Functional length attribute

After specifying the default value , Functional length attribute , The number of parameters without a specified default value will be returned . in other words , After specifying the default value ,length Attributes will be distorted .

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
 Copy code 

In the above code ,length The return value of the property , Equal to the number of parameters of the function minus the number of parameters with default values specified .

such as , The last function above , Three parameters are defined , There is a parameter c Default value specified , therefore length Attribute is equal to the 3 subtract 1, Finally get 2.

This is because length Property means : The number of arguments expected to be passed in by this function . After a parameter specifies a default value , The expected number of parameters passed in does not include this parameter . Empathy , Later rest Parameters are not included length attribute .

(function(...args) {}).length // 0
 Copy code 

If the parameter with default value is not tail parameter , that length The attribute will no longer count all the following parameters .

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
 Copy code 

5. Scope

Once the default value of the parameter is set , When the function performs declaration initialization , Parameters form a separate scope (context). Wait until initialization is complete , The scope disappears . This grammatical behavior , When the parameter default is not set , It's not going to happen .

var x = 1;

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

f(2) // 2
 Copy code 

In the above code , Parameters y The default value of is equal to the variable x. Call function f when , Parameters form a separate scope . In this scope , Default value variable x Point to the first parameter x, Instead of global variables x, So the output is 2.

Let's look at the following example .

let x = 1;

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

f() // 1
 Copy code 

In the above code , function f Invocation time , Parameters y = x Form a separate scope . In this scope , Variable x There is no definition in itself , So global variables that point to the outer layer x. When a function is called , Local variables in function body x The default value variable is not affected x.

If at this time , Global variables x non-existent , You're going to report a mistake .

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

f() // ReferenceError: x is not defined
 Copy code 

Here's how to write , You will also report mistakes. .

var x = 1;

function foo(x = x) {
  // ...
}

foo() // ReferenceError: x is not defined
 Copy code 

In the above code , Parameters x = x Form a single scope . What is actually carried out is let x = x, Because of the temporary dead zone , This line of code will report an error ”x Undefined “.

If the default value of the parameter is a function , The scope of the function also follows this rule . Please see the following example .

let foo = 'outer';

function bar(func = () => foo) {
  let foo = 'inner';
  console.log(func());
}

bar(); // outer
 Copy code 

In the above code , function bar Parameters of func The default value of is an anonymous function , The return value is a variable foo. Function parameters form a separate scope , There are no variables defined foo, therefore foo Global variable pointing to outer layer foo, So output outer.

If it's written like this , You're going to report a mistake .

function bar(func = () => foo) {
  let foo = 'inner';
  console.log(func());
}

bar() // ReferenceError: foo is not defined
 Copy code 

In the above code , In anonymous functions foo Point to the outer layer of the function , But the outer layer of the function does not declare variables foo, So I made a mistake .

Here's a more complex example .

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1
 Copy code 

In the above code , function foo The parameters of form a separate scope . In this scope , First declare the variable x, Then declare the variable y,y The default value of is an anonymous function . The variables inside this anonymous function x, The first parameter pointing to the same scope x. function foo An internal variable is declared inside x, This variable is the same as the first parameter x Because it is not the same scope , So it's not the same variable , So execute y after , Internal variables x And external global variables x The value of has not changed .

If you will var x = 3 Of var Remove , function foo Internal variables x Point to the first parameter x, And inside anonymous functions x It's consistent , So the final output is 2, And the outer global variables x Still unaffected .

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo() // 2
x // 1
 Copy code 

6. application

Using parameter defaults , A parameter can be specified and cannot be omitted , If omitted, an error is thrown .

function throwIfMissing() {
  throw new Error('Missing parameter');
}

function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}

foo()
// Error: Missing parameter
 Copy code 

Code above foo function , If there are no parameters when calling , The default value will be called throwIfMissing function , To throw an error .

You can also see from the above code , Parameters mustBeProvided The default value of is equal to throwIfMissing The result of the function ( Note the function name throwIfMissing Followed by a pair of parentheses ), This indicates that the default value of the parameter is not executed at definition time , Instead, execute... At run time . If the parameter has been assigned , The function in the default value will not run .

in addition , You can set the parameter default value to undefined, It shows that this parameter can be omitted .

function foo(optional = undefined) { ··· }
 Copy code 

Two 、rest Parameters

ES6 introduce rest Parameters ( In the form of ... Variable name ), Extra arguments to get functions , So you don't need to use arguments Object .rest The argument is an array , This variable puts extra parameters in the array .

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

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

  return sum;
}

add(2, 5, 3) // 10
 Copy code 

Code above add Function is a summation function , utilize rest Parameters , You can pass any number of arguments... To this function .

Here's a rest Parameters instead of arguments Examples of variables .

// arguments How to write variables 
function sortNumbers() {
  return Array.prototype.slice.call(arguments).sort();
}

// rest How to write parameters 
const sortNumbers = (...numbers) => numbers.sort();
 Copy code 

There are two ways to write the above code , After comparison, it can be found that ,rest The parameters are written more naturally and concisely .

arguments Objects are not arrays , It's an array like object . So in order to use the array method , You have to use Array.prototype.slice.call Turn it into an array first .rest Parameter does not have this problem , It's a real array , Array specific methods can be used . Here is a utilization rest Parameter override array push Examples of methods .

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item);
    console.log(item);
  });
}

var a = [];
push(a, 1, 2, 3)
 Copy code 

Be careful ,rest Parameters cannot be followed by any other parameters ( It can only be the last parameter ), Otherwise, an error will be reported .

//  Report errors 
function f(a, ...b, c) {
  // ...
}
 Functional  `length`  attribute , barring  `rest`  Parameters .

(function(a) {}).length  // 1
(function(...a) {}).length  // 0
(function(a, ...b) {}).length  // 1
 Copy code 

3、 ... and 、 Arrow function

1. Basic usage

ES6 Allow to use “ arrow ”(=>) Defined function .

var f = v => v;

//  Equate to 
var f = function (v) {
  return v;
};
 Copy code 

If the arrow function does not require parameters or requires more than one parameter , Just use a parenthesis to represent the parameter part .

var f = () => 5;
//  Equate to 
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
//  Equate to 
var sum = function(num1, num2) {
  return num1 + num2;
};
 Copy code 

If the code block part of the arrow function is more than one statement , Just use curly braces around them , And use return Statement returns .

var sum = (num1, num2) => { return num1 + num2; }
 Copy code 

Because braces are interpreted as code blocks , So if the arrow function returns an object directly , You have to put parentheses around the object , Otherwise, an error will be reported .

//  Report errors 
let getTempItem = id => { id: id, name: "Temp" };

//  Don't complain 
let getTempItem = id => ({ id: id, name: "Temp" });
 Copy code 

Here is a special case , Although it can run , But you get the wrong results .

let foo = () => { a: 1 };
foo() // undefined
 Copy code 

In the above code , The original intention was to return an object { a: 1 }, But because the engine thinks braces are blocks of code , So a line of statements is executed a: 1. At this time ,a Can be interpreted as the label of a statement , So the statement actually executed is 1;, And then the function ends , no return value .

If the arrow function has only one line statement , And you don't need to return a value , It can be written in the following way , You don't have to write braces .

let fn = () => void doesNotReturn();
 Copy code 

Arrow function can be used in combination with variable deconstruction .

const full = ({ first, last }) => first + ' ' + last;

//  Equate to 
function full(person) {
  return person.first + ' ' + person.last;
}
 Copy code 

Arrow function makes expression more concise .

const isEven = n => n % 2 === 0;
const square = n => n * n;
 Copy code 

The above code only takes two lines , Just define two simple tool functions . If you don't use the arrow function , It may take up more than one line , And it's not as eye-catching as it is now .

One use of the arrow function is to simplify the callback function .

//  Normal function writing 
[1,2,3].map(function (x) {
  return x * x;
});

//  Arrow function writing 
[1,2,3].map(x => x * x);
 Another example is 

//  Normal function writing 
var result = values.sort(function (a, b) {
  return a - b;
});

//  Arrow function writing 
var result = values.sort((a, b) => a - b);
 Copy code 

Here is rest An example of the combination of parameters and arrow functions .

const numbers = (...nums) => nums;

numbers(1, 2, 3, 4, 5)
// [1,2,3,4,5]

const headAndTail = (head, ...tail) => [head, tail];

headAndTail(1, 2, 3, 4, 5)
// [1,[2,3,4,5]]
 Copy code 

2. Use caution

The arrow function has several points for attention .

(1) In function body this object , Is the object of definition , Instead of using the object .

(2) Can't be used as a constructor , in other words , Not available new command , Otherwise, an error will be thrown .

(3) Not available arguments object , The object does not exist inside the function . If you want to use , It can be used rest Parameters instead of .

(4) Not available yield command , So the arrow function cannot be used as Generator function .

In the four points above , The first point is particularly noteworthy .this The direction of the object is variable , But in the arrow function , It's fixed .

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

var id = 21;

foo.call({ id: 42 });
// id: 42
 Copy code 

In the above code ,setTimeout Is an arrow function , The definition of this arrow function takes effect in foo When the function is generated , And it's going to have to wait until 100 Milliseconds later . If it's a normal function , Execution time this Should point to the global object window, This should output 21. however , The arrow function causes this Always point to the object on which the function definition takes effect ( This example is {id: 42}), So the output is 42.

The arrow function allows setTimeout Inside this, The scope of the binding definition , Instead of pointing to the scope of the runtime . Here's another example .

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  //  Arrow function 
  setInterval(() => this.s1++, 1000);
  //  Ordinary function 
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0
 Copy code 

In the above code ,Timer Two timers are set inside the function , Arrow function and ordinary function are used respectively . formerly this The scope of the binding definition ( namely Timer function ), Latter this Points to the scope where the runtime is located ( I.e. global object ). therefore ,3100 In milliseconds ,timer.s1 It's been updated 3 Time , and timer.s2 Not a single update .

The arrow function allows this Directional immobilization , This feature is very useful for encapsulating callback functions . Here is an example ,DOM The callback function of the event is encapsulated in an object .

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};
 Copy code 

Code above init In the method , Using the arrow function , This leads to the... In the arrow function this, Always point to handler object . otherwise , When the callback function runs ,this.doSomething This line will report an error , Because at this time this Point to document object .

this Fixation of direction , It's not because there's a binding inside the arrow function this The mechanism of , The real reason is that the arrow function doesn't have its own this, Leading to internal this That's the outer code block this. It's because it doesn't have this, So it can't be used as a constructor .

therefore , Arrow function into ES5 The code for is as follows .

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

// ES5
function foo() {
  var _this = this;

  setTimeout(function () {
    console.log('id:', _this.id);
  }, 100);
}
 Copy code 

In the above code , Converted ES5 The version clearly states , The arrow function doesn't have its own this, It's a reference to the outer layer this.

How many of the following codes this

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1
 Copy code 

In the above code , only one this, That's the function. foo Of this, therefore t1t2t3 All output the same result . Because all the inner functions are arrow functions , They don't have their own this , Their this It's actually the outermost layer foo Functional this.

except this, The following three variables do not exist in the arrow function , Points to the corresponding variable of the outer function :argumentssupernew.target.

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

foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]
 Copy code 

In the above code , The variable inside the arrow function arguments, It's actually a function foo Of arguments Variable .

in addition , Because the arrow function does not have its own this, So of course you can't use it call()apply()bind() These ways to change this The direction of .

(function() {
  return [
    (() => this.x).bind({ x: 'inner' })()
  ];
}).call({ x: 'outer' });
// ['outer']
 Copy code 

In the above code , Arrow function does not have its own this, therefore bind method is invalid , Inside this Pointing to the outside this.

For a long time ,JavaScript Linguistic this Object has always been a headache , Use in object methods this, You have to be very careful . Arrow function ” binding ” this, To a great extent, it solves this problem .

3. Not suitable for the scene

Due to the arrow function this from “ dynamic ” become “ static state ”, Arrow functions should not be used in the following two situations .

3.1 The first case is the method of defining an object , And the method includes this.

const cat = {
  lives: 9,
  jumps: () => {
    this.lives--;
  }
}
 Copy code 

In the above code ,cat.jumps() An arrow is a function , This is wrong . call cat.jumps() when , If it's a normal function , Inside the method this Point to cat; If you write it like the arrow function above , bring this Point to global object , So you don't get the expected results . This is because objects do not form a separate scope , Lead to jumps The scope of the arrow function definition is the global scope .

3.2 The second occasion is the need for dynamic this When , You should not use the arrow function .

var button = document.getElementById('press');
button.addEventListener('click', () => {
  this.classList.toggle('on');
});
 Copy code 

When the above code runs , Click the button will report an error , because button The monitor function of is an arrow function , Lead to the this It's the global object . If you change it to a normal function ,this It will dynamically point to the clicked button object .

in addition , If the body of the function is complex , There are many lines , Or there are a lot of read and write operations inside the function , Not just to calculate the value , The arrow function should not be used at this time , It's about using ordinary functions , This can improve the readability of the code .

4. Nested arrow functions

Inside the arrow function , You can also use the arrow function . Here's a ES5 Multiple nested functions of Syntax .

function insert(value) {
  return {into: function (array) {
    return {after: function (afterValue) {
      array.splice(array.indexOf(afterValue) + 1, 0, value);
      return array;
    }};
  }};
}

insert(2).into([1, 3]).after(1); //[1, 2, 3]
 Copy code 

The function above , You can use the arrow function to rewrite .

let insert = (value) => ({into: (array) => ({after: (afterValue) => {
  array.splice(array.indexOf(afterValue) + 1, 0, value);
  return array;
}})});

insert(2).into([1, 3]).after(1); //[1, 2, 3]
 Copy code 

Here is a deployment pipeline mechanism (pipeline) Example , That is, the output of the former function is the input of the latter function .

const pipeline = (...funcs) =>
  val => funcs.reduce((a, b) => b(a), val);

const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);

addThenMult(5)
// 12
 Copy code 

If you think the above writing method is poor in readability , You can also use the following expression .

const plus1 = a => a + 1;
const mult2 = a => a * 2;

mult2(plus1(5))
// 12
 Copy code 

copyright notice
author[Xiao Qi, don't make trouble],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210827100708906k.html

Random recommended