📜 ⬆️ ⬇️

JavaScript: Many Faces Functions

If you are developing JavaScript, no matter what platform you are talking about, this means that you are able to appreciate the value of functions. The way they work, the possibilities that they give to the programmer, make them a truly universal and indispensable tool. Test262 developers, the official test suite that is designed to test JavaScript engines for compatibility with the EcmaScript standard, think so.



In this material they give an overview of the syntactic forms of the definition of functions. In particular, it will focus on what exists in JS from the day it appeared, what emerged in it over the years of development, and what should be expected in the future.

Traditional approaches


â–ŤFunction declaration and function expression


The most famous and widely used methods for defining functions in JS, in addition, are the oldest. This is a function declaration (Function Declaration) and a functional expression (Function Expression). The first method was part of the original version of the language since 1995 and was reflected in the first edition of the specification, in 1997. The second is presented in the third edition , in 1999.
')
If you look at these ways of defining functions, you can see three options for using them:

//   function BindingIdentifier() {} 

 //    // (BindingIdentifier     ) (function BindingIdentifier() {}); 

 //    (function() {}); 

Here, however, it is worth considering that an anonymous functional expression may still have a “name”. Here is some good stuff about function names.

FunctionConstructor Function


If we talk about the "API functions" in JavaScript, then this conversation should start with the Function constructor. Taking into account the initial approach to the design of the language, by analogy with other constructions, the above function declaration can be interpreted as a “literal” to the API of the Function constructor.

The Function constructor provides the means to define functions by specifying the parameters and the body of the function through string arguments. The last of these arguments is the function body:

 new Function('x', 'y', 'return x ** y;'); 

It is important to note that when defining functions using constructors, we are forced to resort to dynamic code execution, which is fraught with security problems.
In practice, the Function constructor is used very rarely, although it is present in the language from the first edition of EcmaScript. In most cases, it has much more convenient alternatives.

New approaches


The ES2015 standard introduced several new syntactic forms for defining functions. They have a huge number of options.

â–ŤNot such an anonymous function declaration.


If you have experience with ES modules, you should be familiar with the new form of anonymous function declaration. Although it is very similar to an anonymous functional expression, such a function actually has an associated name "*default*" . As a result, the function is not so anonymous.

 //        export default function() {} 

By the way, such a “name” is not a valid identifier and a binding is not created here.

â–Ť Methods for determining methods of objects


In the properties of objects that represent functions, you can easily recognize functional expressions, named and anonymous. Please note that these constructions are not some special syntactic forms for defining functions. These are the functional expressions already discussed above that are used in object initializers. These designs were introduced in ES3.

 let object = { propertyName: function() {}, }; let object = { // (BindingIdentifier     ) propertyName: function BindingIdentifier() {}, }; 

Here are the property accessor declarations introduced in ES5:

 let object = { get propertyName() {}, set propertyName(value) {}, }; 

In ES2015, an abbreviated syntax has appeared for defining object methods, which can be used both in the format of a regular property name and with square brackets that enclose a string representation of the name. The same applies to property accessors:

 let object = { propertyName() {}, ["computedName"]() {}, get ["computedAccessorName"]() {}, set ["computedAccessorName"](value) {}, }; 

A similar approach can be used to define prototype methods in class declarations (Class Declarations) and in class expressions (Class Expressions):

 //   class C { methodName() {} ["computedName"]() {} get ["computedAccessorName"]() {} set ["computedAccessorName"](value) {} } 

 //   let C = class { methodName() {} ["computedName"]() {} get ["computedAccessorName"]() {} set ["computedAccessorName"](value) {} }; 

The same applies to static class methods:

 //   class C { static methodName() {} static ["computedName"]() {} static get ["computedAccessorName"]() {} static set ["computedAccessorName"](value) {} } 

 //   let C = class { static methodName() {} static ["computedName"]() {} static get ["computedAccessorName"]() {} static set ["computedAccessorName"](value) {} }; 

â–ŤFireout functions


The arrow functions, which appeared in ES2015, made a lot of noise, however, as a result they gained wide popularity and popularity. There are two forms of switch functions. The first is the short form (Concise Body), which does not provide for the presence of curly brackets after the arrow, there is only an assignment expression (Assignment Expression). The second is a block form. Here, after the arrow, comes the function body in curly brackets, which can be empty, or contain a number of expressions.

If the switch function has no arguments, or more than one, then what is in front of the arrow should be enclosed in parentheses. If such a function has only one argument, the use of parentheses is optional.

In practice, the above means the presence of a variety of options for determining the arrow functions:

 //      (() => 2 ** 2); 

 //       (x => x ** 2); 

 //        (x => { return x ** 2; }); 

 //         ((x, y) => x ** y); 

The last part of the example shows a set of parameters of the arrow function in brackets (covered parameters). This approach allows you to work with a list of parameters, for example, allowing you to use restructuring patterns:

 ({ x }) => x 

The parameter without brackets (uncovered parameter), as already mentioned, allows you to specify a switch function that has only one argument. Before this single argument, you can use the keywords await or yield — if the switch function is defined inside an asynchronous function or generator, but that’s where the possibilities of this syntax end.

Arrow functions can be used in object initializers and when setting their properties. Arrow Function Expression is used here:

 let foo = x => x ** 2; 

 let object = { propertyName: x => x ** 2 }; 

â–ŤGenerators


Generators have a special syntax. It consists in adding an asterisk to the definitions of functions, with the exception of the switch functions and the declarations of getters and setters. The result is a declaration of functions and methods, functional expressions, and even constructors. Take a look at all this in the following example:

 //   function *BindingIdentifer() {} 

 //        export default function *() {} 

 //   // (BindingIdentifier      ) (function *BindingIdentifier() {}); 

 //    (function *() {}); 

 //   let object = { *methodName() {}, *["computedName"]() {}, }; 

 //      class C { *methodName() {} *["computedName"]() {} } 

 //       class C { static *methodName() {} static *["computedName"]() {} } 

 //      let C = class { *methodName() {} *["computedName"]() {} }; 

 //       let C = class { static *methodName() {} static *["computedName"]() {} }; 

ES2017


â–Ť Asynchronous functions


In June 2017, the ES2017 standard was published, in which, after several years of development, asynchronous functions (Async Functions) were presented. Despite the fact that the standard literally just “left the typography”, many developers already use asynchronous functions thanks to Babel .

Asynchronous functions allow you to conveniently describe asynchronous operations. Thanks to their use, the code is clean and uniform. When an asynchronous function is called, a promise will be returned, which will be resolved after the asynchronous function returns the result of its work. If an expression with the await keyword is encountered in an asynchronous function, it may pause, and wait for the expression to complete, for example, return its results.

The syntax of asynchronous functions is not particularly different from what has already been considered. Their main feature is the async prefix:

 //    async function BindingIdentifier() { /**/ } 

 //          export default async function() { /**/ } 

 //     // (BindingIdentifier     ) (async function BindingIdentifier() {}); 

 //     (async function() {}); 

 //   let object = { async methodName() {}, async ["computedName"]() {}, }; 

 //      class C { async methodName() {} async ["computedName"]() {} } 

 //       class C { static async methodName() {} static async ["computedName"]() {} } 

 //      let C = class { async methodName() {} async ["computedName"]() {} }; 

 //       let C = class { static async methodName() {} static async ["computedName"]() {} }; 

â–Ť Asynchronous switch functions


The keywords async and await can be used not only with traditional function declarations and functional expressions. They are compatible with switch functions:

 //       (async x => x ** 2); 

 //    ,      (async x => { return x ** 2; }); 

 //         (async (x, y) => x ** y); 

 //    ,      (async (x, y) => { return x ** y; }); 

Look into the future


â–Ť Asynchronous Generators


In future versions of the JavaScript specification, the use of the async and await keywords will be extended to generators. The progress of the implementation of this functionality can be seen here . As you probably guessed, we are talking about a combination of async/await keywords and existing forms for defining generators - through declarations and expressions.

The asynchronous generator, when called, returns an iterator, whose method next() returns a promise that will be resolved by the object, which is what the iterator normally returns. Under normal conditions, calling this method will directly return the result.

Asynchronous generators can be found where there are already ordinary generator functions.

 //    async function *BindingIdentifier() { /**/ } 

 //         export default async function *() {} 

 //    // (BindingIdentifier      ) (async function *BindingIdentifier() {}); 

 //    (async function *() {}); 

 //   let object = { async *propertyName() {}, async *["computedName"]() {}, }; 

 //       class C { async *propertyName() {} async *["computedName"]() {} } 

 //       let C = class { async *propertyName() {} async *["computedName"]() {} }; //       class C { static async *propertyName() {} static async *["computedName"]() {} } 

 //       let C = class { static async *propertyName() {} static async *["computedName"]() {} }; 

Results About JavaScript engines, tests and functions


Imagine the path of a new way of defining a function from idea to working code. In simplified form, it looks like this. First, the idea becomes a proposal to the standard, then it enters the standard, then its implementation in JS engines goes on, then - the study by programmers and practical application.

The contribution of those who work on Test262 to this process is to, after sorting out the standards, prepare tests that test new language constructs taking into account the existing ones. Such an approach means a tremendous job of creating tests that is not rational to impose on a person. For example, checking the arguments by default should be carried out with all forms of functions, in such things it cannot be limited to, say, a simple form of a function declaration. All this led to the development of tools for creating tests, which suggests that almost everything that can be tested is subjected to tests.

Now the project contains a set of files with source code , which consist of different test scripts and templates .

For example, here you can see how the property of the arguments function is checked; here , tests of various forms of functions. Of course, there are a lot more in Test262. Say, here and here - tests related to destructuring. In the process of working on tests, rather big pull requests are obtained, errors are detected and corrected. All this leads to a constant increase in the quality of Test262, which means to improved checks of JS engines for compliance with the EcmaScript specification. This has a direct impact on the JavaScript industry. The more software constructs will be identified and covered with tests, the easier it will be for engine developers to implement new features, the more stable and more reliable, as a result, JavaScript programs will work.

Dear readers! What methods of defining functions in JavaScript do you use most often?

Source: https://habr.com/ru/post/332020/


All Articles