Thursday, November 5, 2020

Function Arguments and Parameters

Function parameters are defined in the function signature; function arguments are values provided every time the function is invoked

  • NOTE: function invocations work with fewer or with more arguments than declared parameters

1 Optional Parameters and Defaults

Invoking a function with fewer arguments

  • a function can be invoked with fewer arguments than declared parameters
  • the missing parameters are set to the undefined value
  • you can write a function where some arguments are optional

Writing functions with optional parameters

  • in traditional JavaScript, you test whether a parameter is undefined
  • as alternate solution, you can use ES6 parameter defaults syntax
  • note: in functions with optional parameters, the optional ones should always be the last of the argument list

Writing functions with optional arguments, using traditional JavaScript syntax

Example: the sum function with not undefined testing

const sum = (x, /*optional*/ y) => {
    // test for undefined argument
    y = y === undefined ? x : y;
    return x + y;
}
console.log(sum(2, 2)); // 4 
console.log(sum(2)); // 4

Writing functions with optional arguments using ES6 parameter defaults syntax

  • ES6 allows to define default values for each function parameter directly in the function signature
  • just follow the parameter name with an equal sign and the default value
  • note: parameter default expressions are evaluated when the function is invoked, not when the function is defined
  • note: parameter defaults can be a constants, variables or function invocations

Example: the sum functions with default arguments

// 1. the sum function has parameter y with default argument
const sum = (x, y = x) => {
  return x + y;
}
console.log(sum(2, 2)); // 4 
console.log(sum(2)); // 4

// 2. the sum2 function has multiple default arguments
const sum2 = (x = 0, y = x) => {
  return x + y;
}
console.log(sum2(2, 2)); // 4 
console.log(sum2(2)); // 4
console.log(sum2());  // 0 

2 Rest Parameters

Invoking functions with more arguments

  • if you call a function with more arguments than declared parameters, the extra is ignored
console.log(sum(2, 2, 4)); // 4 

Writing variadic function with ECMAScript 6 rest parameters

  • ES6 rest parameters syntax allows to write function that can be invoked with more arguments than declared parameters
  • functions that accept any number of arguments are named variadic functions, variable arity functions or varargs functions

Example: the sum function with rest parameters

const sum = (first = 0, ...rest) => {
  let sum = first 
  for (const value of rest) { 
    sum += value 
  }
  return sum;
}
console.log(sum(1, 2, 3, 4, 5)) // 15
console.log(sum(6, 7, 8, 9, 10)) // 40

Function's rest parameters syntax

  • a rest parameter is preceded by three periods
  • a rest parameter must be the last parameter in the function signature

How do rest parameters work?

  • the arguments passed in the function invocation are assigned to non-rest parameters
  • the remaining arguments (the rest of the aguments) are stored in a array which is the value of the rest parameter
  • in the function body, the rest parameter has array type
  • in the function body, the value of rest parameter can be an empty array, but cannot be undefined

3 The Arguments Object

In traditional JavaScript, you could write variadic functions using the arguments object

The arguments object:

  • it is defined in the function body
  • it contains all the arguments values passed to the function for that invocation
  • it is an array-like object, with length property; which means that argument values are looped over in a for loop

Note: in modern javaScript: you should not be using the Arguments object anymore

  • in strict mode arguments is treated as a reserved keyword
  • try to refactor any function using the arguments object and replace it with ...args rest parameters

4 The Spread Operator for Function Calls

What is the use of the spread syntax?

  • the spread syntax (...) is used to extract the values contained in an iterable object

When using the spread syntax?

  • use the spread syntax in situations where multiple simple values are expected
  • for example, when creating array and object literals or when invoking functions

Example: Invoking Math.max with an array argument

// Math.max function can take any number of arguments
console.log(Math.max(11, 17, 13)) // 17
let numbers = [1, 7, 5, 9]
// passing an array will not work
console.log(Math.max(numbers)) // NaN
// you need to spread out the contents into individual arguments
console.log(Math.max(...numbers)) // 9

Comparing the spread syntax and the rest syntax

  • in a function invocation, the three dots are called the spread syntax as they separates the content of an iterable object into multiple single values
  • in a function definition, the three dots are called the rest parameter as they gather multiple arguments into an array

5 Destructuring Function Arguments into Parameters

When a function is invoked, argument values are assigned to the parameters declared in the function signature

  • parameter assignment is like variable assignment and therefore destructuring can be used in function parameter assignment

How to destructure function arguments into parameters?

  • in function signature definitions:
    • put parameter names in square brackets to destructure array arguments
    • put parameter names in curly braces to destructure object arguments
  • in function invocation expression
    • pass an array value for each pair of brackets in the function signature
    • pass an object value for each pair of curly braces in the function signature
  • when function invocation expression is evaluated
    • arguments value are extracted and assigned to the array elements or the object properties

Example: the vectorSum([x1,y1], [x2,y2]) function adds 2D vectors

  • this function destructures the components of 2D vectors in two parameters [x,y]
function vectorSum([x1,y1], [x2,y2]) { 
    return [x1+x2, y1+y2];
}
vectorSum([1,2], [3,4]) // [4,6]    

When is it convenient to destructure object arguments into function parameters?

  • use argument destructuring, if function parameter lists are long or contains many optional parameters

Example: a function with optional parameter written in traditional JavaScript

  • check whether each argument is undefined and provide a default value
// 1. in traditional JavaScript, check whether argument is not undefined
function objectToString(object, config) {
  if (config === undefined) config = {}; 
  if (config.nameValueSeparator === undefined) config.nameValueSeparator = ' = ';
  if (config.propSeparator === undefined) config.propSeparator = ', ';
  ...
}

Example: a function with optional parameter using ES6 parameter destructuring

  • this function turns an object into a string
  • this function is customizable, because the caller specifies some configuration parameters: the name-value separator, the property separator, etc.
  • instead of using parameters, this function uses an object literal
  • the function takes as second parameter an object, which declares the destructured parameter variables
  • the destructured parameters are: leftDelimiter, nameValueSeparator, propSeparator, rightDelimiter
  • each destructured parameter has a default value
  • in case the caller does not provide any argument for the config object, there is also a default blank object {}
// 2. in ECMAScript 6, use destructuring and assign default value to destructured parameters
function objectToString(object, {leftDelimiter = '{', 
                                 nameValueSeparator = '=', 
                                 propSeparator = ', ', 
                                 rightDelimiter = '}'} = {} ) {
    let props = Object.entries(object)
    .map( ([k,v]) => { return [k, typeof(v)==="string"?`"${v}"`:typeof(v)==="function"?"Function":v] })
    .map( item => { return `${item.join(nameValueSeparator)}` })
    .join(propSeparator)
    return `${object.constructor.name}: ${leftDelimiter}${props}${rightDelimiter}`;
}

// actress object
const actress = { 
    id: 100, 
    firstName: 'Uma', 
    middleName: 'Karuna', 
    lastName: 'Thurman', 
    age(){ return } 
};

// invoke without config argument
console.log(objectToString(actress)); 
// Object: {id=100, firstName="Uma", middleName="Karuna", lastName="Thurman", age=Function}

// invoke with config argument
const options = { 
    nameValueSeparator : '\u279C',
    propSeparator : ' \u2E3A ',
    rightDelimiter : ')', 
    leftDelimiter : '('
};
console.log(objectToString(actress, options)); 
// Object: (id➜100 ⸺ firstName➜"Uma" ⸺ middleName➜"Karuna" ⸺ lastName➜"Thurman" ⸺ age➜Function)

No comments:

Post a Comment