Functions are actually objects, and each Function is an instance of Function, which also has properties and methods, just like any other reference type.

Method of function definition

A function name is a pointer to a function object and is not necessarily bound to the function itself.

Function declaration [usually]

function sum(num1, num2){
	return num1+num2;
}
Copy the code

The function definition does not end with a semicolon.

Functional expression

let sum = function(num1, num2){
	return num1+num2;
};
Copy the code

There is no name after the function keyword, and the function is referred to by the sum variable. The function ends with a semicolon, as with any variable initialization statement.

“Arrow function” mode

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

Using the Function constructor [not recommended]

let sum = new Funtion("num1", "num2", "return num1+num2");
Copy the code

Multiple arguments can be accepted, the last function is always the body of the function, and the preceding arguments are all arguments of the function.

Arrow function

ES6 added the ability to define function expressions using the fat arrow (=>) syntax. Arrow functions can be used anywhere a function expression is used.

Ideal for scenarios where functions are embedded

Let ints = [1, 2, 3]; console.log(ints.map(function(i){ return i+1; })); / / (2 and 4] the console. The log (ints. The map ((I) = > {return I + 1; })); / / / 2 and 4Copy the code

If you have only one argument, you can omit the parentheses. If there are no arguments or multiple arguments, use parentheses.

Arrow functions can’t use arguments, super, and new.target. They can’t be used as constructors, and they don’t have a Prototype attribute.

The function name

A function can have more than one name, because function names are Pointers to functions and therefore behave like any other variable that contains Pointers to objects.

function sum(num1, num2){ return num1+num2; } console.log(sum(10, 10)); // 20 let anothor = sum; console.log(another(10, 10)); // 20 sum = null; console.log(sum(10, 10)); / / 20Copy the code

Using a function name without parentheses accesses the function pointer, but does not execute the function. Setting sum to null disconnects it from the function, but another () can be called as usual.

All function objects in ES6 expose a read-only name attribute, which in most cases holds either a function identifier or a string-like variable name. Even if the function has no name, it is faithfully displayed as an empty string. If it is created using the Function constructor, it will be identified as “anonymous”.

Function f1() {} let f2 = function(){}; let f3 = () => {}; console.log(f1.name); // f1 console.log(f2.name); // f2 console.log(f3.name); // f3 console.log((() => {}).name); // (empty string) console.log((new Function()).name); // anonymousCopy the code

If the function is a set function, a get function, or an instantiation using bind (), the identifier is prefixed:

function foo(){}
console.log(foo.bind(null).name);	// bound foo

let dog = {
	years : 1,
	get age(){
		return this.years;
	},
	set age(newAge){
		this.years = newAge;
	}
}
let pd = Object.getOwnPropertyDescriptor(dog, 'age');
console.log(pd.get.name);	// get age
console.log(pd.set.name);	// set age
Copy the code

Understand the parameters

Unlike most other languages, ES does not care about either the number of arguments passed or the data type of the argument.

This is mainly because the arguments to the ES function are internally represented as an array. In fact, when you use function to define (non-arrow) functions, you can access the Arguments object inside the function and get the value of each argument passed in from it.

The arguments object is an array-like object (but not an instance of Array) whose elements can be accessed using brackets (for example, arguments[0] as the first argument). To determine the number of arguments passed in, access the arguments.length property.

We can also rewrite the function to declare no arguments:

function sayHi(){
	console.log("Hello "+ arguments[0] +","+ argments[1]);
}
Copy the code

You don’t have to write the arguments to the ES function. Unlike other languages, named parameters in ES do not create function signatures that subsequent calls must match, because there is no mechanism for validating named parameters.

Arguments objects can be used with named arguments:

function doAdd(num1, num2){ if(arguments.length === 1){ console.log(num1 + 10); // The named argument num1 holds the same value as arguments[0]. }else if(arguments.length === 2){ console.log(arguments[0] + num2); }}Copy the code

The values of the Arguments object are synchronized with the corresponding named arguments.

function doAdd(num1, num2){
	arguments[1] = 10;
	console.log(arguments[0] + num2);
}
Copy the code

In the example above, changing arguments[1] will also change the value of num2. It does not mean accessing the same memory address, they are still separate in memory, just in sync.

If you pass only one argument and then set arguments[1] to a value, that value is not reflected in the second named argument because the length of the arguments object is determined by the number of arguments passed in, not by the number of named arguments used to define the function.

For named parameters, the value is undefined if the parameter is not passed when the function is called. In strict mode, assigning arguments[1] does not affect the value of num2. Trying to override the Arguments object in a function results in a syntax error. (Code doesn’t execute either)

Arguments in arrow functions cannot be accessed using the arguments keyword, but only through defined named arguments.

We can provide it to the arrow function in the wrapper function:

function foo(){
	let bar = () =>{
		console.log(arguments[0]);	// 5
	};
	bar();
}
foo(5);
Copy the code

All arguments in ES are passed by value. It is not possible to pass parameters by reference. If an object is passed as a parameter, the value passed is a reference to the object.

There is no overload

The ES function has no signature because the arguments are represented by an array of zero or more values. There is no function signature and no overloading.

If two functions with the same name are defined in ES, the latter will override the first. Using function names as Pointers helps to understand why ES has no function overloading.

let addNum = function(num){ return num+100; }; addNum = function(num){ return num-100; }; let result = addNum(200); / / 100Copy the code

Default Parameter Value

Prior to ES5.1 and earlier, a common way to implement default parameters was to check whether a parameter was equal to undefined. If it is, it means that the parameter is not passed, and a value is assigned to it.

After ES6, you can explicitly define default parameters. You can assign a default value to a parameter simply by following it with = in a function definition.

function makeKing(name = 'Henry'){
	return 'King $(name) VIII';
}
Copy the code

Passing undefined to a parameter is equivalent to passing no value at all, but it makes use of multiple independent defaults.

When using default arguments, the values of the Arguments object do not reflect the default values of the arguments, only the arguments passed to the function. As with ES5 strict mode, changing the named arguments does not affect the Arguments object, which always takes the value passed in when the function is called.

function makeKing(name = 'Henry'){
	name = 'CLN';
	return 'King $(arguments[0])';
}
console.log(makeKing());	// 'King undefined'
console.log(makeKing('JK'));	// 'King JK'
Copy the code

Default parameter values are not limited to the original value or object type, but can also use the value returned by the calling function.

function makeKing(name = 'Henry', numerals = getNumerals()){
	return 'King $(name) $(numerals)';
}
Copy the code

The default arguments to a function are evaluated only when the function is called, not when the function is defined. A function that evaluates to a default value is called only if the function is called without passing an argument.

Arrow functions can also use default arguments, except that parentheses must be used when there is only one argument.

let makeKing = (name = ‘jk’) => ‘return $(name)’;

The initialization order of parameters follows the “temporary dead zone” rule, which means that parameters defined earlier cannot reference those defined later. Parameters also exist in their own scope; they cannot refer to the scope of the function body.

Function makeKing(name = 'CLN ', numerals = defaultNum){let defaultNum =' FFZF '; return 'King $(name) $(numerals)'; }Copy the code

Parameter expansion and collection

ES6 has a new extension operator, which can be used to pass arguments when calling a function or to define function parameters.

Extension parameters

Use the extension operator to pass arrays directly to functions:

getSum(... values);Copy the code

Since the array length is known, we can pass additional arguments before or after the extension operator:

countArg(-1, ... values); countArg(-1, ... values, 5); countArg(-1, ... values, ... ,8,1 [5]);Copy the code

The Arguments object is just one way to consume extension operators, which can also be used to name arguments in normal and arrow functions, or to use default arguments.

Collect parameters

You can use the extension operator to combine separate arguments of different lengths into an array.

If a named parameter is preceded by a collection parameter, only the remaining parameters are collected. If you don’t, you get an empty array. Because the result of the collection parameter is variable, you can only use the collection parameter as the last parameter.

function collect(firstValue, ... values){ console.log(values); } collect(); // [] collect(1); / / [] collect (1, 2); / / [2] collect (1, 2, 3); / / [2, 3]Copy the code

The arrow function supports the way the collection parameters are defined.

let getSum = (.. values) => { return values.reduce((x,y) => x+y, 0); } the console. The log (getSum (1, 2, 3)); / / 6Copy the code

Using collection arguments does not affect the Arguments object, which still reflects the arguments passed to the function when called.

function getSum(... values){ console.log(arguments.length); // 3 console.log(arguments); / / [1, 2, 3]. The console log (values); . / / [1, 2, 3]} the console log (getSum (1, 2, 3));Copy the code

Function declarations and function expressions

In fact, the JavaScript engine treats them differently when loading data. Before any code executes, the JavaScript engine reads the function declaration and generates the function definition in the execution context.

The console. The log (sum (10, 10)); // 20 function sum (num1, num2){ return num1+num2; }Copy the code

Function declarations are read and added to the execution context before any code is executed, a process called function declaration promotion.

A function expression, on the other hand, must wait until the code executes to its line before generating the function definition in the execution context.

The console. The log (sum (10, 10)); Let sum =function(num1, num2){return num1+num2; };Copy the code

The above code fails because the function definition is contained in a variable initialization statement, not in the function declaration. If this statement is not executed, there is no function definition in the execution context. The same problem occurs with the var keyword.

Function as value

Because function names are variables in ES, functions can be used anywhere variables can be used. This means you can pass a function as an argument to another function, and you can return another function in one function.

function callFunc(someFunc, someArg){ return somFunc(someArg); } function add10(num){ return num+10; } let result = callFunc(add10, 10); console.log(result); / / 20Copy the code

The callFunc function is generic; the first argument is passed to any function and always returns the result of calling the function passed as the first argument. If you are accessing a function instead of calling it, you must do so without parentheses. So the pass to callFunc() must be add10, not the result of its execution.

It is also possible to return from one function to another.

function compareFunc(propertyName){ return function(obj1, obj2){ let value1 = obj1[propertyName]; let value2 = obj2[propertyName]; if(value1 > value2){ return -1; }else if(value1 < value2){ return 1; }else{ return 0; }}; } let data = { {name : "jk", age : 24}, {name : "cln", age : 21} }; data.sort(compareFunc("name")); console.log(data[0].name); // cln data.sort(compareFunc("age")); console.log(data[0].name); // clnCopy the code

Function of the internal

In ES5, there are two special objects inside functions: Arguments and this. The new. Target attribute has been added to ES6.

arguments

Is an array-like object that contains all the arguments passed in when the parameters are called. This object is only available when functions are defined with the function keyword (as opposed to functions created using arrow syntax). There is also a property called Callee, which is a pointer to the function of the Arguments object.

Function factorial(num){if(num <= 1){return 1; }else{// return num * factorial(num-1); Return num * arguments.callee(num-1); // Function logic can be decoupled from function name}}Copy the code

Arguments. callee has been replaced with hard-coded factorial. A function can be referred to correctly regardless of its name.

let trueFactoral = factorial; factorial = function(){ return 0; }; console.log(trueFactorial(5)); // 120 console.log(factorial(5)); / / 0Copy the code

this

It has different behavior in standard and arrow functions.

In standard functions, this refers to the context object that calls the function as a method, usually referred to as this value (this points to Windows when the function is called in the global context of a web page).

window.color = "red"; let o = { color : "blue" }; function sayColor(){ console.log(this.color); } sayColor(); // 'red', called in the global context, this refers to window o.saycolor = sayColor; o.sayColor(); // 'blue', this points to oCopy the code

In arrow functions, this refers to the context in which the arrow function is defined.

window.color = "red"; let o = { color : "blue" }; let sayColor = () => console.log(this.color); sayColor(); // 'red' o.sayColor = sayColor; o.sayColor(); // 'red',, arrow function defined in the window context, this points to windowCopy the code

When a function is called in an event or timed callback, the this value points to an unwanted object. Writing the callback as an arrow function solves the problem, because the this in the arrow function preserves the context in which the function was defined.

function King(){ this.royaltyName = 'Henry'; // this references the King instance setTimeout(() => console.log(this.royaltyname), 1000); } new King(); // HenryCopy the code

The function name is just the variable that holds the pointer, so the globally defined sayColor() function and o.saycolor () are the same function, but executed in different contexts.

caller

ES5 also adds an attribute to function objects: Caller, referring to a function that calls the current function, or null in the case of a global scope call.

function outer(){ inner(); } function inner(){ // console.log(inner.caller); // inner. Caller points to outer() console.log(arguments.callee.caller); } outer();Copy the code

Accessing arguments.callee in strict mode causes an error.

As ES5 also defines arguments. Caller, an error is reported when accessing a function in strict mode. At the same time, you cannot assign a value to the function caller attribute, otherwise an error will occur. It is always undefined in non-strict mode.

new.target

A new. Target attribute that detects whether a function is called using the new keyword has been added to ES6.

If the function is called normally, the value of new.target is undefined;

If called with the new keyword, new.target will refer to the constructor being called.

function King(){ if(! new.target){ throw 'King must be instantiated using "new"'; }else{ console.log('King instantiated using "new"'); } } new King(); // King instantiated using "new" King(); // Error : King must be instantiated using "new"Copy the code

Function properties and methods

Each function has two attributes: length and prototype. The length attribute holds the number of named parameters defined by the function.

Prototype is where all instances of a reference type are stored. In ES5, the Prototype property is not enumerable, so a for-in loop does not return this property.

A function has two methods: apply () and call (), which call the function with the specified this value. That is, it sets the value of this object inside the function when the function is called.

Apply () takes two arguments: the this value in the function body and an Array of arguments. The second argument can be an instance of Array or arguments object.

sum.apply(this, arguments);
sum.apply(this, [num1, num2]);
Copy the code

In strict mode, if no context object is specified when a function is called, the this value does not point to the window. Unless you use apply () or call () to assign a function to an object, this becomes undefined.

Call () works the same as apply (), except that it is passed in a different way. The first argument is the same as apply (), the value of this, and the remaining arguments to be passed to the called function are passed one by one.

sum.call(this, num1, num2);

The real power of both methods is the ability to control the context of the function call, the this value inside the function.

window.color = 'red'; let o = { color : 'blue' }; function sayColor(){ console.log(this.color); } sayColor(); // red, called in the global context, this refers to window sayColor(this); // red sayColor(window); // red sayColor(o); // blueCopy the code

The advantage of using apply () or call () is that you can set any object to the scope of any function. In the previous example, you need to assign sayColor() directly to o before calling it. In the modified version, this step is not required.

ES5 defines bind (), which creates a new function instance whose this value is bound to the object passed to Bing ().

window.color = 'red';
let o = { color : 'blue' };
function sayColor(){
	console.log(this.color);
}
let objSayColor = sayColor.bind(o);
objSayColor();	// blue
Copy the code

For functions, the inherited methods toLocaleString () and toString () always return the code of the function, while valueof () returns the function itself.

Functional expression

Let functionName = function(arg0, arg1, arg2) {function body};

The function created by creating a function and assigning it to a variable is called an anonymous function (also known as a Lambda function) because there is no identifier after the function keyword.

The key to understanding the difference between a function declaration and a function expression is to understand promotion.

if(condition){ function sayHi(){ console.log('Hi! '); } }else{ function sayHi(){ console.log('Yo! '); }}Copy the code

The above code looks normal, but this writing is not valid in ES, and the JS engine will try to correct it to the appropriate declaration. But some browsers are inconsistent in how they correct this problem. So this is dangerous, don’t use it.

let sayHi(): if(condition){ sayHi = function(){ console.log('Hi! '); } }else{ sayHi = function(){ console.log('Yo! '); }}Copy the code

Written as a function expression, the variable sayHi is assigned a function based on the value of condition, as expected.

recursive

When writing recursive functions, arguments.callee is the preferred reference to the current function.

function factorial(num){ if(num <= 1){ return 1; }else{ return num * arguments.callee(num-1); }}Copy the code

Code running in strict mode cannot access arguments.callee because accessing it will cause an error. At this point, you can use named function expressions to do the trick.

const factorial = (function f(num) { if(num <= 1){ return 1; }else{ return num * f(num-1); }})Copy the code

Even if a function is assigned to another variable, the name f of the function expression remains the same, so recursive calls are not a problem. This mode can be used in both strict and non-strict modes.

Tail-call optimization

The ES6 specification has a new memory management optimization mechanism that allows the JS engine to reuse stack frames when conditions are met. This optimization is perfect for “tail calls,” where the return value of an external function is the return value of an internal function.

The final call optimizes the condition

  1. Code executes in strict mode;
  2. The return value of an external function is a call to the tail-calling function;
  3. No additional logic needs to be performed after the tail-calling function returns;
  4. Tail-calling functions are not closures that reference free variables in the scope of an external function.

The following example satisfies the tail-call optimization criteria:

"use strict"; Function otherFunc(a,b){return innerFunc(a+b); Function otherFunc(a,b){if(a<b){return a; } return innerFunc(a+b); Function otherFunc(condition){return condition? innerFuncA() : innerFuncB(); }Copy the code

The tail calls the optimized code

Use two nested functions, with the outer function as the base frame and the inner function performing recursion:

Function fib(n){return fibImpl(0,1,n); } function fibImpl(a,b,n){if(n === 0){return a; } return fibImpl(b, a+b, n-1); }Copy the code

After the code is refactured and the tail-call optimization condition is satisfied, the call to FIB (1000) is no longer a threat to the browser.

closure

Functions that refer to variables in the scope of another function, usually implemented in nested functions.

CompareFunc (propertyName) let value1= obj1[propertyName]; let value2= obj2[propertyName];Copy the code

When a function is called, an execution context is created for the function call and a chain of scopes is created. Arguments and other named arguments are then used to initialize the function’s live object. The active object of the outer function is the second object in the scope chain of the inner function. This chain of scopes extends all active objects that contain functions until the global execution context terminates.

function compare(value1, value2){ if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; }} let result = compare(5,10);Copy the code

Compare () is called in the global context, and the first time this function is called, an active object with Arguments, Value1, and Value2 is created for it, the first object in its scope chain. The variable object of the global context is the second object in the compare () scope chain, which contains this, result, and compare.

When a function executes, there is an object containing the variables in each execution context. The objects in the global context are called variable objects and persist throughout the execution of the code. The local context of a function is called an active object and only exists during the execution of the function. An action chain is actually a list of Pointers, each pointing to a variable object, but not physically containing the corresponding object.

The closure’s scope chain contains one of its own variable objects, followed by variable objects containing functions, up to the variable objects of the global context.

It is recommended to use closures with caution and only when absolutely necessary. Closures take up more memory than other functions because they retain their scope containing Han Suhua.

Normally, the function scope and all variables in it are destroyed after the function completes execution. After a closure is returned by a function, its scope is kept in memory until the closure is destroyed.

This object

If called from a global function, this equals window in non-strict mode and undefined in strict mode.

window.identity = 'The Window'; let obj = { identity : 'My Obj', getIdentity(){ let that = this; return function(){ that.identity; }; }}; console.log(obj.getIdentity()()); // 'My Obj'Copy the code

Both this and arguments cannot be accessed directly from an inner function. If you want to access arguments from the include scope, you also need to save their references to another variable accessible to the closure.

A function expression that is invoked immediately

An immediately called anonymous function is also called an immediately called function expression. If you assign the return value to a variable outside the include scope, all the contained variables are destroyed.

(function(){// block-level scope}) ();Copy the code

Use ES block-level scoped variables to show the correct index every time you click <div>.

let divs = document.querySelectorAll('div'); for(let i = 0; i< divs.length; ++i){ divs[i].addEventListener('click',function(){ console.log(i); }); }Copy the code

In ES6, if you use the block-level scoped variable keyword for the for loop, in this case let, the loop creates a separate variable for each loop, allowing each click-handler to reference a specific index.

Private variables

Any variable defined in a function or block can be considered private.

Private variables include function parameters, local variables, and other functions defined inside the function.

A privileged method is a public method that can access a function’s private variables (and private functions). There are two ways to create privileged methods on objects:

  1. Implemented in the constructor
function MyObj(){ let privateVariable = 30; function privateFunc(){ return false; This.privatemethod = function(){privateVariable++; return privateFunc(); }}Copy the code

Static private variable

  1. This is done by defining private variables and functions in private scopes, using the stereotype pattern implemented in custom types
(function(){ let privateVariable = 10; function privateFunc(){ return false; } MyObj = function(){}; / / constructor MyObj. Prototype. PublicMethod = function () {/ / public and privilege method, defined in the prototype privateVariable++; return privateFunc(); }})Copy the code

Variables that are not declared using keywords are created in the global scope. MyObj becomes a global variable. Note that assigning values to undeclared variables in strict mode causes errors.

Using closures and private variables causes the scope chain to become longer, and the longer the scope chain, the more time it takes to find the variable.

The module pattern

The same isolation and encapsulation is implemented on a singleton object. A singleton is an object that has only one instance. JS creates a singleton from an object literal:

Let singleton = {name: value, method(){// method code}};Copy the code

The modular pattern extends a singleton to associate private variables and privileged methods through a chain of scopes. The boilerplate code is as follows:

let singleton = { let privateVariable = 10; function privateFunc(){ return false; Return {publicProperty: true, publicMethod(){privateVariable++; return privateFunc(); }}; } ();Copy the code

In essence, object literals define the common interface of a singleton. If a singleton needs some kind of initialization and needs access to private variables, you can use this pattern:

let application = function(){ let components = new Array(); // Private variables and private functions components.push(new BaseComponent()); Return {getComponentCount(){return component.length; }, registerComponent(component){ if(typeof component == 'object'){ components.push(component); }}}; } ();Copy the code

In module mode, a singleton, as a module, can be initialized to contain some private data that can be accessed through its exposed public methods. Each singleton created this way is an instance of Object, because the final singleton is represented by an Object literal.

Module enhancement mode

Enhance an object before returning it. Suitable for scenarios where a singleton object needs to be an instance of a particular type, but additional properties or methods must be added to it.

let singleton = function(){ let privateVariable = 10; function privateFunc(){ return false; } let obj = new CustomType(); // Create object obj.publicProperty = true; Obj. publicMethod = function(){privateVariable++; return privateFunc(); }; return obj; // Return object}();Copy the code

If the application object from the previous section must be an instance of BaseComponent, you can create it using the following code:

let application = function(){ let components = new Array(); // Private variables and private functions components.push(new BaseComponent()); // Initialize let app = new BasComponent(); // Public interface app.getComponentCount(){return component.length; }; app.registerComponent(component){ if(typeof component == 'object'){ components.push(component); }}; return app; // Return instance}();Copy the code