function

A function with default values for arguments

JS functions are unique in that they can take any number of arguments, regardless of the number of arguments at the function declaration. This allows defined functions to be called with a different number of arguments, often with default values for arguments not provided when called.

Emulated parameter defaults in ES5

We may often see the following way to create default values for parameters.

    function request(url, timeout, callback) {
        timeout = 3000

        callback = callback || function() {}

        console.log(timeout)
        // ...
    }
Copy the code

In the above request function, timeout and callback are optional arguments, because they both use default values if the parameters are not passed. Mainly because of the logical or operator (| |) on the left side has a value of false cases will always return to the right of the operator. However, the above method is buggy and will cause the value to be replaced if we pass 0, NULL, or false.

    request('/'.0) / / 3000
    request('/'.null) / / 3000
    request('/'.false) / / 3000
Copy the code

In this case, you can use typeof to detect the typeof the parameter.

    function request(url, timeout, callback) {
        timeout = (typeoftimeout ! = ="undefined")? timeout :3000

        callback = (typeofcallback ! = ="undefined")? callback :function() {}

        console.log(timeout)
        // ...
    }

    request('/') / / 3000
    request('/'.0) / / 0
    request('/'.null) // null
    request('/'.false) // false
Copy the code

This is because the default is undefined when a named parameter of a function is not supplied with a value. While this approach is more secure, it still requires a lot of code for a very simple requirement. ES6 gives us a much easier way to provide default values for parameters.

Default values of ES6 parameters

ES6 makes it easier to provide default values for parameters, using a form of initialization that can be used when parameters are not formally passed in.

    function request(url, timeout = 3000, callback = function() {{})// ...
    }
Copy the code

This function also requires that only the first argument is always passed, while the other two arguments have their own default values. But this is much simpler than ES5, because we don’t need to check whether the argument is passed.

    function request(url, timeout = 3000, callback = function() {{})console.log(timeout)
        // ...
    }

    request('/') / / 3000
    request('/'.undefined) / / 3000
    request('/'.0) / / 0
    request('/'.null) // null
    request('/'.false) // false
Copy the code

? > you can see that when we pass a value with a second argument that is not undefined, the default value is not applied.

It is possible to specify a default value for any parameter in a function declaration, even if it precedes an parameter that does not specify a default value. As follows:

    function request(url, timeout = 3000, callback) {
        console.log(timeout)
        // ...
    }

    request('/') / / 3000
    request('/'.undefined) / / 3000
    request('/'.0) / / 0
    request('/'.null) // null
    request('/'.false) // false
Copy the code
How do parameter defaults affectargumentsobject

The Arguments object behaves differently when using the parameter default values. In ES5’s non-strict mode, the Arguments object reflects changes to the argument name. As follows:

    function mixArgs(first, second) {
        console.log(first === arguments[0])  // true
        console.log(second === arguments[1]) // true
        first = 'c'
        second = 'd'
        console.log(first === arguments[0])  // true
        console.log(second === arguments[1]) // true
    }

    mixArgs('a'.'b')
Copy the code

? As you can see, arguments are always updated to reflect changes to the name arguments in non-strict mode. So arguments[0] and arguments[1] are updated when we change the values of first and second.

In ES5’s strict mode, this confusion about arguments objects is eliminated, and it no longer reflects changes to arguments.

    function mixArgs(first, second) {
        'use strict'

        console.log(first === arguments[0])  // true
        console.log(second === arguments[1]) // true
        first = 'c'
        second = 'd'
        console.log(first === arguments[0])  // false
        console.log(second === arguments[1]) // false
    }

    mixArgs('a'.'b')
Copy the code

? > When we change the values of first and second again in strict mode, we can see that arguments are no longer affected.

! > In ES6, the arguments object always behaves in ES5 strict mode in functions that use parameter defaults, whether or not the function is explicitly running in strict mode at the time. The existence of a default value for arguments triggers the separation of arguments objects from named arguments.

Parameter default value expression

Default values for function arguments are not required to be primitive. We can also pass in a function at a time to generate the default values of the arguments. As follows:

    function getValue() {
        return 1
    }

    function add(num1, num2 = getValue()) {
        return num1 + num2
    }

    console.log(add(1.10)) / / 11
    console.log(add(1)) / / 2
Copy the code

We can also use the previous parameter as the default value for the later parameter. As follows:

    function add(num1, num2 = num1) {
        return num1 + num2
    }

    console.log(add(1)) / / 2
    console.log(add(1.1)) / / 2
Copy the code

? > In the code above, we set the default values for both parameters, meaning that passing only the first parameter gives both parameters the same value, so add(1) and add(1, 1) both return 2. This means that we can actually pass the previously declared parameter as the default value to the later parameter.

! > Note that only the previous parameter can be quoted as the default value of another parameter. Therefore, the previous parameter cannot be used as the default value of the later parameter. For details, see the temporary dead zone generated by let and const.

The remaining parameters

The rest parameter consists of three points (…) And a named argument, which will be an array containing the remaining arguments passed to the function.

    function pick(num1, ... arr) {
        console.log(num1) / / 1
        console.log(arr)  // [2, 3, 4, 5, 6]
    }

    pick(1.2.3.4.5.6)
Copy the code

? > In the above function arr is a residual argument that contains the argument after num1. It differs from arguments that include all arguments, which do not include the first argument.

Note that the remaining parameters have two limitations:

  1. A function can have only one remaining argument, and it must be placed last.
    Rest parameter must be last formal parameter
    function pick(num1, ... arr, num2) {
        // ...
    }

    pick(1.2.3.4.5.6) 
Copy the code
  1. The remaining parameters cannot be in the object literalsetterProperty. This is because of object literalssetterIs restricted to using only a single parameter; The remaining parameters are not limited by definition, so they cannot be used here.
    const object = {
        Setfunction argument must not be a rest parameterset name(... value) { } }Copy the code

Function constructor enhancement

The Function constructor allows you to create a new Function on the fly. The following

    const add = new Function('num1'.'num2'.'return num1 + num2')

    console.log(add(1.2)) / / 3
Copy the code

ES6 enhances the Function constructor, allowing us to use default parameters as well as remaining parameters. As follows:

    const add = new Function('num1 = 1'.'num2 = 2'.'return num1 + num2')

    console.log(add()) / / 3
Copy the code
    const add = new Function('... num'.'return num[0] + num[1]')

    console.log(add(1.2)) / / 3
Copy the code

Extended operator

Residual arguments allow us to combine multiple independent arguments into an array, while extension operators allow us to split an array and pass items as separate arguments to a function. As follows:

    const values = [1.2.3.4.5]

    // Equivalent to console.log(math.max (1, 2, 3, 4, 5))
    console.log(Math.max(... values))/ / 5
Copy the code

ES6 name attribute

In ES6, all functions have their own name attribute value.

In the case of a function declaration, the name attribute returns the name of a function declaration

    function doSomething() {}
    console.log(doSomething.name) // "doSomething" 
Copy the code

In the case of an anonymous function expression, the name attribute is the name of the variable assigned to the function

    const doAnotherThing = function() {}
    console.log(doAnotherThing.name) // doAnotherThing
Copy the code

! The important thing to note here is that when a function expression assigns a declaration function, since the function expression has its own name, the name of the function expression declaration will take precedence over the variable name of the assignment target.

    const doAnotherThing = function doSomething() {}
    console.log(doAnotherThing.name) // "doSomething" 
Copy the code

Functions created using bind will have bound appended to the function name

    function foo() {}
    console.log(foo.bind(null).name) // "bound foo"
Copy the code

When properties are accessed through get and set accessors, “get” or “set” appears before the function name.

    const o = { 
        get foo() {},
        set foo(x) {}
    }

    const descriptor = Object.getOwnPropertyDescriptor(o, "foo")
    console.log(descriptor.get.name) // "get foo"
    console.log(descriptor.set.name) // "set foo"
Copy the code

! > getter and setter functions must use Object. GetOwnPropertyDescriptor () to retrieve.

Make it clear that functions do double duty

In ES5 and earlier, functions have dual purposes depending on whether or not they are called using new. When new is used, this inside the function is a new object and is returned by the function.

JS provides two different internal methods for functions: [[Call]] and [[Construct]]. When a function is not called with new, the [[Call]] method is executed, running the body of the function shown in the code. When a function is called with new, the [[Construct]] method is executed, creating a new object called a new target and executing the function body using the new target as this. A function that has a [[Construct]] method is called a constructor.

! > Not all functions have a [[Construct]] method, so not all functions can be called with new. In the case of the arrow function, the arrow function does not have this method.

The new target yuan properties

ES6 introduced the new.target meta-attribute. A meta-attribute is an attribute on a “non-object” (such as new) and provides additional information associated with its target. When a function’s [[Construct]] method is called, new.target is filled with the target of the new operator, which is usually the constructor of the newly created object instance and becomes the this value inside the function body. If [[Call]] is executed, the value of new.target will be undefined.

This new meta property allows you to safely determine if the function was called using new by checking if new.target is defined.

    function Person(name) {
        if(typeof new.target ! = ='undefined') {
            this.name = name
        }else {
            throw new Error("You must use new with Person.")}}const person = new Person('roll')
    const notAPrson = Person.call(person, 'roll') / / an error
Copy the code

With new.target, the Person constructor throws an error if the new call is not used.

We can also check if new.target is called with a particular constructor.

    function Person(name) {
        if(new.target ! == Person) {this.name = name
        }else {
            throw new Error("You must use new with Person.")}}function AnotherPerson(name) {
        Person.call(this, name)
    }

    const person = new Person("Roll")
    const anotherPerson = new AnotherPerson("Roll") / / an error
Copy the code

Arrow function

Arrow functions are defined using an “arrow” (=>) as their name suggests, but their behavior differs from that of traditional JS functions in important ways.

  • There is nothis,super,argumentsAnd there is nonew.targetBinding:this,super,arguments, and inside the functionnew.targetThe value of is determined by the nearest non-arrow function.
  • Can’t be usednewCall: Arrow function does not[[Construct]]Method, therefore cannot be used as a constructor, is usednewCalling the arrow function throws an error.
  • No prototype: since you can’t use it with arrow functionsnew, then it doesn’t need a prototype, i.e., noneprototypeProperties.
  • Can’t changethis:thisThe value of, cannot be modified inside a function and remains constant throughout the life of the function.
  • There is noargumentsObject: Since the arrow function does notargumentsBinding, we must rely on named or residual arguments to access the function’s arguments.
  • Duplicate named arguments are not allowed: arrow functions are not allowed to have duplicate named arguments, whether in strict mode or not; Traditional functions, by contrast, prohibit this repetition only in strict mode.
Arrow function syntax

When accepting a single parameter.

    const fun = value= > value
    
    / / equivalent to the
    const fun = function(value) {
        return value
    }
Copy the code

When passing multiple arguments

    const fun = (num1, num2) = > num1 + num2
    
    / / equivalent to the
    const fun = function(num1, num2) {
        return num1 + num2
    }
Copy the code

When multiple statements are included, we need to wrap the function body in a pair of curly braces and explicitly define a return value.

    const fun = (num1, num2) = > {
        const num = num1 + num2
        return num
    }

    / / equivalent to the
    const fun = function(num1, num2) {
        const num = num1 + num2
        return num
    }
Copy the code
Creates an immediate call function expression

A popular way to use functions in JS is to create the Immediate-invoked Function Expression (IIFE). IIFE allows you to define an anonymous function and call it immediately without saving a reference.

You’ll probably see this in ES5 and earlier versions.

    (function(name) {
        return 'My name is.' + name
    }('zzzhim'))

    / / or

    !function(name) {
        return 'My name is.' + name
    }('zzzhim')
Copy the code

? > In the above code, when the program executes to the above function, the function will execute itself and return a string.

We can use the arrow function to achieve the same effect

    (name= > 'My name is.' + name)('zzzhim')
Copy the code

? > < p style = “max-width: 100%; clear: both; min-height: 1em

| when using traditional function (function () {/ function body /}) () and (function () {/ function body /} (), these two approaches are feasible.

| but if use the arrow function, only the following writing is effective: (() = > {/ function body /}) ()

Tail-call optimization

Perhaps the most interesting change to functions in ES6 is an engine optimization that changes the system for tail-calling. This call means that the statement calling a function is the last statement of another function. As follows:

    function doSomething() {
        return doSomethingElse(); / / end call
    }
Copy the code

ES6 tries to reduce the stack size for specific tail calls in strict mode (tail calls in non-strict mode remain the same). Tail-call optimization clears the current stack frame and reuses it instead of creating a new stack frame for tail-call when:

  1. Tail calls cannot refer to variables in the current stack frame (meaning the function cannot be a closure);
  2. A function that makes a tail call cannot do additional operations after the tail call returns the result;
  3. The result of the last call is the return value of the current function.

conclusion

The default arguments to a function make it easier to specify the values to use when a particular argument is passed in. Prior to ES6, this required some extra code detection inside the function to implement the default arguments.

Remaining arguments allow us to put all remaining arguments into the specified array. Using real arrays and letting us specify which arguments need to be included makes the remaining arguments a more flexible solution than arguments.

The extension operator is a good companion to the remaining arguments, allowing us to deconstruct the array into separate arguments when calling a function. Before ES6, when we wanted to pass array elements to functions as separate arguments, there were only two ways to do it: manually specify it or use the apply() method.

The new name attribute makes it easier to identify functions for debugging and execution.

In ES6, the behavior of functions is defined by the [[Call]] and [[Construct]] methods, the former corresponding to normal function execution and the latter to calls using new. We can use the new.target meta-attribute to determine if the function was called with new.

The biggest change to ES6 functions is the addition of arrow functions. The arrow function is designed to replace anonymous function expressions with a cleaner syntax, lexical-level this binding, and no arguments objects. In addition, arrow functions cannot modify their this binding and therefore cannot be used as constructors.

Tail-call optimization allows calls to certain functions to be optimized to keep the call stack smaller, use less memory, and prevent heap overflow. It is automatically applied by the engine when security optimizations can be made.

6 Reading notes.

  1. ES6 block-level binding, let, const declaration
  2. Strings and Regular expressions (RegExp)
  3. Functions
  4. Extended object functionality
  5. Deconstruction assignment
  6. Symbol and Symbol attribute

Refer to the article

  • MDN
  • Understanding ECMAScript 6
  • Understanding ECMAScript 6

githubaddress