Function and functional programming

Posted by hypedupdawg on Sun, 23 Jan 2022 23:05:57 +0100

From: Micro reading

Throughout all the key knowledge that must be mastered in JavaScript, function is the most easily ignored knowledge point when we first learn. Many people and articles may tell you that object-oriented is very important and prototype is very important, but few people tell you that almost all the key and difficult points in object-oriented are closely related to functions.

Including the execution context, variable object, closure, this, etc. introduced in my previous articles, they are all expanded around the details of the function.

Many people are eager to start learning object-oriented, learning modules, learning popular frameworks and quickly become experts. But I can be very responsible to tell you that if you don't understand these basic things about functions to a certain extent, your learning progress must be difficult.

Therefore, we must pay attention to the function!

Of course, the key points and difficulties of functions have been described in the previous articles. This article mainly summarizes the basic knowledge of functions and preliminarily learns the thinking of functional programming.

1, Function declaration, function expression, anonymous function and self executing function

The application of function in practical development can be summarized as function declaration, function expression, anonymous function and self-executive function.

Function declaration

In JavaScript, there are two declaration methods, one is variable declaration using var/let/const, and the other is function declaration using function.

As I mentioned in the front-end basic advanced (III): detailed explanation of variable objects [1], in the process of creating variable objects, function declarations take precedence over variable declarations, that is, we often mention that function declarations take precedence over variable declarations. Therefore, no matter where we declare a function in the execution context, we can directly use the function in the same execution context.

fn();  // functionfunction fn() {    console.log("function");}

Function expression

Different from the function declaration, the function expression is declared with var/let/const. When we confirm whether it can be used correctly, we must judge according to the rules of var/let/const, that is, variable declaration. We know that using VaR to declare variables is actually a two-step operation.

// Variable declaration var a = 20// Actual execution order var a = undefined// Variable declaration, initial value undefined, variable promotion, promotion order is inferior to function declaration, a = 20// Variable assignment, this operation will not promote

Similarly, when we use variable declaration to declare a function, it is often called function expression. The promotion method of function expression is consistent with that of variable declaration.

fn(); // Error var FN = function() {console.log ("function");}

The execution sequence of the above example is:

var fn = undefined;   // Variable declaration promotion (FN)// Execute the error fn = function() {/ / assignment operation. At this time, assign the reference of the following function to FN console.log ("function");}

Due to the different declaration methods, there are some differences in the use of function declaration and function expression. We should pay attention to that. In addition, there is no difference in the use of these two forms of functions.

As for the above example, the assignment operation in function expression will also be frequently used in other places. We can understand the relationship.

// Add the method function person (name) {this. Name = name; this. Age = age; / / add the method this. Getage = function() {return this. Age;} inside the constructor this.}//  Add the method person. To the prototype prototype. getName = function() {    return;}//  Add method var a = {M: 20, getm: function() {return this. M;}}

Anonymous function

Anonymous function, as its name suggests, refers to a function that is not displayed for assignment. Its usage scenario is mostly passed into another function as a parameter.

var a = 10;var fn = function(bar, num) {    return bar() + num;}fn(function() {    return a;}, 20)

In the above example, the first parameter of fn is passed into an anonymous function. Although the anonymous function is not displayed for assignment, we can't reference it in the external execution context, but inside the fn function, we assign the anonymous function to the variable bar and save it in the arguments object of the fn variable object.

// The creation phase VO (fn) = {arguments: {bar: undefined, Num: undefined, length: 2}} / / the execution phase of the variable object during the execution of fn context / / the variable object becomes an active object, And complete the assignment operation and execute the executable code VO - > Aoao (fn) = {arguments: {bar: function() {return a}, Num: 20, length: 2}}

Since an anonymous function will eventually be executed in another function after it is passed into another function, we often call this anonymous function a callback function. For more information about anonymous functions, I will explain it in more detail in the next article on coritization.

The application scenario of anonymous function takes on almost all the incomprehensible knowledge points of the function, so we must understand these details clearly enough. If you don't understand the evolution process of variable object, you must go back to this article: Advanced front-end Foundation (III): detailed explanation of variable object [2]

Function self execution and block level scope

In ES5, there is no block level scope, so we often use the method of function self execution to imitate the block level scope, which provides an independent execution context. Combined with closures, it provides the basis for modularization. Function self execution is actually an application of anonymous functions.

(function() {   // ...})();

A module can often include: private variables, private methods, public variables and public methods.

According to the one-way access of the scope chain, it may be easy for the outside to know that in this independent module, the external execution environment cannot access any internal variables and methods, so we can easily create private variables and methods belonging to this module.

(function() {    // Private variable var age = 20; var name = "Tom";    //  Private method function getname() {return ` your name is ` + name;}}) ();

But what about shared methods and variables? Do you remember the features of closures we talked about earlier? Yes, using closures, we can access variables and methods inside the execution context. Therefore, we only need to create a closure according to the definition of closures and open the variables and methods you think need to be exposed.

(function() {    // Private variable var age = 20; var name = "Tom";    //  Private method function getname() {return ` your name is ` + name;}// Common method function getage() {return age;}// Save the reference in the variable of the external execution environment to form a closure to prevent the execution environment from being garbage collected window getAge = getAge;}) ();

Of course, we have emphasized the important role of closures in modules when explaining closures, but this knowledge point is really too important for us to understand and master thoroughly.

To help you further understand closures, let's take a look at how modules and closures are used in jQuery.

// Create a module (function(window, undefined) {/ / declare the jQuery constructor var jQuery = function(name) {/ / actively return a jQuery instance in the constructor return new jQuery.fn.init(name);}// Add prototype method jQuery prototype = jQuery. fn = {         constructor: jQuery,         init:function() { ... },          css: function() { ... }     }      jQuery. fn. init. prototype = jQuery. fn;    //  Change the name of jQuery to $, save the reference in the window, form a closure, and open the jQuery constructor, so that we can access all the methods mounted on the jQuery prototype jQuery = window.$ =  jQuery; }) (window);//  When using, the constructor is directly executed. Because the jQuery constructor returns an instance of jQuery through some means, we don't need to create a new instance $("#div1") every time we use it;

Here, we only need to understand the part of closures and modules. As for how the internal prototype chain is wound and why it is written like this, we will slowly analyze it for you when talking about object-oriented. The purpose of this example is to hope that we can pay attention to function. In practical development, it is everywhere.

Next, I want to share an advanced and very useful module application. As our projects become larger and larger, more and more data and states need to be saved. Therefore, we need a special module to maintain these data. At this time, a thing called state manager came into being. I think redux is the most famous state manager. Although redux is a bit unfathomable for those who are still learning, before we learn, we can let you roughly understand the implementation principle of the state manager in a simple way, so as to lay a solid foundation for our future learning.

Let's look directly at the code.

// Self executing create module (function() {/ / states structure preview / / states = {/ / A: 1, / / B: 2, / / M: 30, / / O: {} / / var states = {}// A private variable used to store status and data / / judge the data type function type (elem) {if (elem = = null) {return elem + "";} return toString. call(elem). replace(/[\[\]]/g, ""). split(" ")[1]. toLowerCase();    }    /**     * @ Param name attribute name * @ Description get the value saved in states through the attribute name * / function get (name) {return states [name]? States [name]: "";} function getStates() {        return states;    }    /*    * @ Param options {object} key value pair * @ param target {object} attribute value is the attribute of the object, which is only passed in recursively during function implementation * @ desc modifies the state tree by passing in key value pairs, The usage is similar to setStates in data or react of applet * / function set (options, target) {var keys = object.keys (options); VAR o = target? Target: States; (function (item) {if (typeof o [item] = = "undefined") {o [item] = options [item];} else {                type(o[item]) == "object" ? set(options[item], o[item]) : o[item] = options[item];            }             return item;        })    }    //  Provide external interface window get = get;     window. set = set;     window. getStates = getStates;}) () / / use the following set ({A: 20})// Save attribute aset ({B: 100})// Save attribute bset ({c: 10})// Save attribute c. / / save attribute o, whose value is an object set ({o: {M: 10, N: 20}}) / / modify the m value set ({o: {M: 1000}}) / / add a c attribute set ({o: {c: 100}}) console to object o log(getStates())

demo instance online address [3]

The reason why I say this is an advanced application is that we are likely to use this idea in single page applications. According to the knowledge we mentioned, it is actually very simple to understand this example. The difficulty of estimation lies in the processing of set method. In order to have more applicability, many adaptations have been made, using recursion and other knowledge. If you don't understand it for the time being, it doesn't matter. Just know how to use it. The above code can be directly applied to practical development. Remember, when you need to save too many states, just think of this code.

There are several other ways of function self execution, such as! function(){}(),+function(){}()

2, Function parameter transfer method: transfer by value

Remember the differences in replication between basic data types and reference data types? The basic data type is copied. The values are copied directly. Therefore, they do not affect each other after changing. However, the replication of reference data types refers to the replication of references saved in variable objects, so the two references after replication actually access the values in the same heap memory. When one is changed, the other is naturally changed. The following example.

var a = 20;var b = a;b = 10;console.log(a);  // 20var m = { a: 1, b: 2 }var n = m;n.a = 5;console.log(m.a) // 5

The same difference occurs when values are passed inside a function as arguments to the function. We know that after entering the function, the parameters of the function are actually saved in the variable object of the function. Therefore, this time is equivalent to a copy. The following example.

var a = 20;function fn(a) {    a = a + 10;    return a;}fn(a);console.log(a); // 20
var a = { m: 10, n: 20 }function fn(a) {    a.m = 20;    return a;}fn(a);console.log(a);   // { m: 20, n: 20 }

It is precisely because of this difference that many people have a lot of confusion when understanding the transfer mode of function parameters. Is it passed by value or by reference? In fact, the conclusion is still passed by value, but when we expect to pass a reference type, all we really pass is the reference of the reference type saved in the variable object. To illustrate this, let's take a look at the following example.

var person = {    name: "Nicholas",    age: 20}function setName(obj) {  // Pass in a reference obj = {}// Point the incoming reference to another value obj name = "Greg";  //  Modify the referenced name attribute}setName(person);console.log(;  // Nicholas has not been changed

In the above example, if person is passed by reference, then person will be automatically modified to point to a new object whose name attribute value is Gerg. However, we can see from the results that the person object has not changed, so the reference is only modified inside the function.

4, Functional programming

Although JavaScript is not a purely functional programming language, it uses many features of functional programming. Therefore, understanding these features can let us know more about the code we write.

When we want to use a function, we usually want to encapsulate some functions and logic. I believe you are not unfamiliar with the concept of encapsulation.

We usually do one thing through function encapsulation. For example, to calculate the sum of any three numbers, we can take these three numbers as parameters and encapsulate a simple function.

function add(a, b, c) {  return a + b + c;}

When we want to calculate the sum of three numbers, we can call this method directly.

add(1, 2, 3); // 6

Of course, when the things you want to do are relatively simple, you may not see the convenience brought by encapsulation as a function. What if we want to do something a little more complicated. For example, I want to calculate the sum of all sub items in an array.

function mergeArr(arr) {    var result = 0;    for(var i = 0; i < arr.length; i++) { result  += arr[i] }    return result;}

If we don't use function encapsulation, we have to reuse the for loop every time we want to implement this function. As a result, our code is filled with more and more duplicate code. After encapsulation, when we want to do this again, we only need one sentence.

mergeArr([1, 2, 3, 4, 5]);

Of course, I believe everyone should have a very clear understanding of the meaning of function encapsulation, but the problem we have to face is, when we want to encapsulate a function, what is the best practice?

Functional programming can give us the answer.

When we first learn, we often involuntarily use the style of imperative programming to accomplish what we want to do. Because imperative programming is simpler and more straightforward. For example, we now have an array, array = [1, 3, "h", 5, "m", "4"]. Now we want to find all the children of type number in this array. When we use imperative programming thinking, we may do so directly.

var array = [1, 3, "h", 5, "m", "4"];var res = [];for(var i = 0; i < array.length; i ++) {    if (typeof array[i] === "number") {        res.push(array[i]);    }}

In this way, we have achieved our goal in a straightforward way. The problem with this is that when we want to find all the children in another array at another time, we have to write the same logic again. As the number of occurrences increases, our code becomes worse and difficult to maintain.

The thinking of functional programming suggests that we package this function that will appear many times for calling.

function getNumbers(array) {    var res = [];    array.forEach(function(item) {        if (typeof item === "number") {            res.push(item);        }    })    return res;}// The above is our package, and the following is the function implementation var array = [1, 3, "h", 5, "m", "4"];var res = getNumbers(array);

After encapsulating the function, we only need to write one line of code to realize the same function. If future requirements change or are slightly modified, we only need to adjust the getNumbers method. And when we use it, we only need to care about what this method can do, not how it is implemented. This is also one of the differences between functional programming thinking and imperative programming thinking.

Functional programming thinking also has the following characteristics.

Function is a first-class citizen

The so-called "first class" means that functions, like other data types, are in equal status and can be assigned to other variables, passed into another function as parameters, or used as the return value of other functions. We should have seen a lot of these scenes.

var a = function foo() {}  // Assignment function fn(function() {}, num) {} / / function as parameter / / function as return value function var() {return function() {...}}

Of course, these are the basic concepts of JavaScript. But I think many people, even you who are reading, may ignore these concepts. You can use a simple example to verify it.

Let's customize such a function first.

function delay() {    console.log("5000ms The method is then executed.");}

Now, what should you do if you are required to delay the execution of the delay method by 5000ms in combination with the setTimeout method?

It's actually very simple, isn't it? Just do it.

var timer = setTimeout(function() {    delay();}, 5000);

Now the problem comes. If you have a deep understanding that the function is a first-class citizen, I think you will find that there are some problems with the above writing. So think about it. What's the problem?

Since the function can be passed into another function as a parameter, can we directly take delay as the first parameter of setTimeout without adding an additional layer of anonymous functions?

Therefore, in fact, the most correct solution should be written like this.

var timer = setTimeout(delay, 5000);

Of course, if you've thought of doing this in advance, congratulations. It shows that you are more talented in JavaScript than ordinary people. In fact, the first bad way is used by many people, including people with many years of work experience. And they don't even know what their problem is.

In future practice, you will encounter more similar scenes. In order to verify the readers' understanding, we might as well think about how to optimize the following code.

function getUser(path, callback) {    return $.get(path, function(info) {        return callback(info);    })}getUser("/api/user", function(resp) {    // Resp is the data console returned after the successful request log(resp);})

The principle of optimization is as like as two peas in setTimeout. I sell a pass here, and I do not intend to tell you the conclusion. Only one hint is that after getUser optimization, there is only one code. It's time to test everyone's learning achievements.

Use only "expression" instead of "statement"

"expression" is a simple operation process, which always has a return value; A statement is an operation that does not return a value. Functional programming requires only expressions, not statements. In other words, each step is a simple operation and has a return value.

If we need to change the background color of an element in many places in our project. So we can package it like this.

var ele = document.querySelector(".test");function setBackgroundColor(color) { = color;}// Setbackgroundcolor ("red") is used in many places; setBackgroundColor("#ccc");

We can clearly feel that setBackgroundColor encapsulates only one statement. This is not the ideal effect. Functional programming expects a function to have both input and output. Therefore, good habits should be done as follows.

function setBackgroundColor(ele, color) { = color;    return color;}// Var ele = document. Is used in multiple places querySelector(".test"); setBackgroundColor(ele, "red"); setBackgroundColor(ele, "#ccc");

Knowing this can help us form a good habit when encapsulating functions.

Pure function

The same input will always get the same output, and the function that will not produce side effects is a pure function.

The so-called "side effect" refers to the interaction between internal and external functions (the most typical case is to modify the value of global variables) to produce other results other than operations.

Functional programming emphasizes that there are no "side effects", which means that the function should remain independent. All functions are to return a new value without other behaviors. In particular, the value of external variables must not be modified.

That is, as long as the same parameters are passed in, the returned results must be equal.

For example, we expect to encapsulate a function to get the last item of the passed in array. Then it can be realized in the following two ways.

function getLast(arr) {    return arr[arr.length];}function getLast_(arr) {    return arr.pop();}var source = [1, 2, 3, 4];var last = getLast(source); // Return result 4 the original array remains unchanged var last_= getLast_ (source); //  Return result 4: the last item of the original data is deleted

getLast and getLast_ Although the last value of the array can also be obtained, getLast_ Changed the original array. When the original array is changed, the result will be different when we call the method again. Such unpredictable packaging is very bad in our view. It will mess up our data. Among the data methods supported by JavaScript natively, there are many impure methods. We need to be very vigilant when using them. We should clearly know whether the change of original data will leave hidden dangers.

var source = [1, 2, 3, 4, 5];source.slice(1, 3); // Pure function returns [2, 3] source unchanged source splice(1, 3); //  Impure return [2, 3, 4] source is changed pop(); //  Impure source push(6); //  Impure source shift(); //  Impure source unshift(1); //  Impure source reverse(); //  Impure / / I can't know what the source has changed in a short time. Just reschedule the source = [1, 2, 3, 4, 5];source.concat([6, 7]); //  Pure function returns [1, 2, 3, 4, 5, 6, 7] source unchanged source join("-"); //  Pure function returns 1-2-3-4-5 source unchanged

Topics: Javascript Front-end Programming