Advanced Front-end Fundamentals (7): Functions and Functional Programming

Looking at all the key knowledge that must be mastered in JavaScript, function is one of the most easily overlooked knowledge points when we are learning. In the process of learning, there may be many people and articles telling you that object orientation is very important and prototypes are very important, but few people tell you that almost all the key points and difficulties in object orientation are closely related to functions.

Including the execution context, variable objects, closures, this, etc. introduced in my previous articles, they are all based on the details of the function.

I know that many people are in the process of learning, and they are eager to start learning object-oriented, learn modules, learn popular frameworks, and quickly become masters. But I can tell you responsibly that if you don’t understand these basic things about functions to a certain extent, then your learning progress must be difficult.

So, we must pay attention to the function!

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

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

Regarding the application of functions in actual development, it can be roughly summarized as function declarations, function expressions, anonymous functions, and self-executing functions.

function declaration

We know that in JavaScript, there are two declaration methods, one is varthe variable declaration used, and the other is functionthe function declaration used.

As I mentioned in Advanced Front-end Basics (3): Detailed Explanation of Variable Objects, in the process of creating variable objects, function declarations have a higher order of execution than variable declarations, that is, the function declarations we often mention ahead. So in the execution context, no matter where the function is declared, we can use the function directly in the same execution context.

fn();  // function

function fn() { console.log('function'); } 

function expression

Unlike function declarations, function expressions are declared using var, so when we confirm whether they can be used correctly, we must judge according to the rules of var, that is, variable declarations. We know that using var for variable declaration is actually a two-step operation.

// 变量声明
var a = 20;

// 实际执行顺序
var a = undefined; // 变量声明,初始值undefined,变量提升,提升顺序次于function声明 a = 20; // 变量赋值,该操作不会提升 

In the same way, when we use variable declarations to declare functions, it is what we often call function expressions. Function expressions are hoisted in the same way as variable declarations.

fn(); // 报错
var fn = function() { console.log('function'); } 

The execution sequence of the above example is:

var fn = undefined;   // 变量声明提升
fn();    // 执行报错
fn = function() { // 赋值操作,此时将后边函数的引用赋值给fn console.log('function'); } 

Therefore, due to the different declaration methods, there are some differences in the use of function declarations and function expressions that we need to pay attention to. Apart from that, there is no difference in the use of these two forms of functions.

Regarding the above example, the assignment operation in the function expression is often used in other places, and we only need to know the relationship.

在构造函数中添加方法
function Person(name) {
    this.name = name; this.age = age; // 在构造函数内部中添加方法 this.getAge = function() { return this.age; } this. } // 给原型添加方法 Person.prototype.getName = function() { return this.name; } // 在对象中添加方法 var a = { m: 20, getM: function() { return this.m; } } 

anonymous function

In the above we have roughly described the assignment operation in the function expression. An anonymous function, as the name suggests, refers to a function that is not explicitly assigned an assignment. Its usage scenarios are mostly passed as a parameter to another function.

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 in an anonymous function. Although the anonymous function is not explicitly assigned, we have no way to 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 .

// 变量对象在fn上下文执行过程中的创建阶段
VO(fn) = {
    arguments: {
        bar: undefined,
        num: undefined,
        length: 2 } } // 变量对象在fn上下文执行过程中的执行阶段 // 变量对象变为活动对象,并完成赋值操作与执行可执行代码 VO -> AO AO(fn) = { arguments: { bar: function() { return a }, num: 20, length: 2 } } 

Since an anonymous function is passed into another function, it will eventually be executed in another function, so we often call this anonymous function a callback function. More about anonymous functions, which I'll cover in more detail in my next in-depth article on currying.

This application scenario of anonymous functions bears almost all the incomprehensible knowledge points of functions, so we must understand these details of it clearly enough. If you still don’t understand the evolution process of variable objects, you must go back. Go to this article: Advanced Front-end Basics (3): Detailed Explanation of Variable Objects

Function self-execution and block scope

In ES5, there is no block-level scope, so we often use function self-execution to imitate block-level scope, which provides an independent execution context, which, combined with closures, provides a basis for modularity. The 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 outsiders 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 private variables belonging to this module. method.

(function() {
    // 私有变量
    var age = 20; var name = 'Tom'; // 私有方法 function getName() { return `your name is ` + name; } })(); 

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

If you don't know enough about closures, advanced front-end basics (4): Illustrating scope chains and closures in detail should help you.

(function() {
    // 私有变量
    var age = 20; var name = 'Tom'; // 私有方法 function getName() { return `your name is ` + name; } // 共有方法 function getAge() { return age; } // 将引用保存在外部执行环境的变量中,形成闭包,防止该执行环境被垃圾回收 window.getAge = getAge; })(); 

Of course, the important role of closures in modules has already been emphasized when we explained closures, but this knowledge point is really too important, and we need to repeatedly understand and master them thoroughly. Therefore, in order to help you further understand closures, we Let's take a look at how jQuery uses our modules and closures.

// 使用函数自执行的方式创建模块
(function(window, undefined) {

    // 声明jQuery构造函数 var jQuery = function(name) { // 主动在构造函数中,返回一个jQuery实例 return new jQuery.fn.init(name); } // 添加原型方法 jQuery.prototype = jQuery.fn = { constructor: jQuery, init:function() { ... }, css: function() { ... } } jQuery.fn.init.prototype = jQuery.fn; // 将jQuery改名为$,并将引用保存在window上,形成闭包,对外开发jQuery构造函数,这样我们就可以访问所有挂载在jQuery原型上的方法了 window.jQuery = window.$ = jQuery; })(window); // 在使用时,我们直接执行了构造函数,因为在jQuery的构造函数中通过一些手段,返回的是jQuery的实例,所以我们就不用再每次用的时候在自己new了 $('#div1'); 

Here, we only need to understand the parts of closures and modules. As for how the internal prototype chain goes around and why it is written like this, I will analyze it for everyone when I talk about object-oriented. The purpose of this example is to hope that everyone can pay attention to functions, because in actual development, it is everywhere.

Next I want to share an application of an advanced, very useful module. When our project gets bigger and bigger, more and more data and states need to be saved. Therefore, we need a special module to maintain these data. At this time, something called a state manager came into being. . For state managers, the most famous one, I think, is redux. Although for everyone who is still learning, redux is a bit unpredictable, but before we learn, we can use a simple way to let everyone have a general understanding of the implementation principle of the state manager for our future learning. Lay a solid foundation.

Let's look directly at the code first.

// 自执行创建模块
(function() {
    // states 结构预览 // states = { // a: 1, // b: 2, // m: 30, // o: {} // } var states = {}; // 私有变量,用来存储状态与数据 // 判断数据类型 function type(elem) { if(elem == null) { return elem + ''; } return toString.call(elem).replace(/[\[\]]/g, '').split(' ')[1].toLowerCase(); } /** * @Param name 属性名 * @Description 通过属性名获取保存在states中的值 */ function get(name) { return states[name] ? states[name] : ''; } function getStates() { return states; } /* * @param options {object} 键值对 * @param target {object} 属性值为对象的属性,只在函数实现时递归中传入 * @desc 通过传入键值对的方式修改state树,使用方式与小程序的data或者react中的setStates类似 */ function set(options, target) { var keys = Object.keys(options); var o = target ? target : states; keys.map(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; }) } // 对外提供接口 window.get = get; window.set = set; window.getStates = getStates; })() // 具体使用如下 set({ a: 20 }); // 保存 属性a set({ b: 100 }); // 保存属性b set({ c: 10 }); // 保存属性c // 保存属性o, 它的值为一个对象 set({ o: { m: 10, n: 20 } }) // 修改对象o 的m值 set({ o: { m: 1000 } }) // 给对象o中增加一个c属性 set({ o: { c: 100 } }) console.log(getStates()) 

demo instance online address

The reason why I say this is an advanced application is because in a single page application, we are likely to use this kind of thinking. According to the knowledge we mentioned, understanding this example is actually very simple. The difficulty is estimated to be in the processing of the set method, because in order to have more applicability, a lot of adaptations have been made, and knowledge such as recursion has been used. If you don't understand it temporarily, it doesn't matter, just know how to use it. The above code can be directly applied to actual development. Remember, when you need to save too much state, you can think of this piece of code.

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

Second, the function parameter passing method: pass by value

Remember the difference in replication between primitive and reference data types? The basic data type is copied, and the direct value is copied, so after the change, they do not affect each other. However, the copying of the reference data type is that the references stored in the variable object are copied, so the two references after copying actually access the value in the same heap memory. When one is changed, the other is naturally changed as well. Example below.

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

The same difference applies when values ​​are passed inside functions as arguments to functions. We know that after entering the function, the parameters of the function are actually stored in the variable object of the function, so this time is equivalent to a copy. Example below.

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 they understand the way of passing function parameters. Is it pass-by-value or pass-by-reference? In fact, the conclusion is still passed by value, but when we expect to pass a reference type, what is really passed is only the reference stored in the variable object by the reference type. To illustrate this point, let's look at the following example.

var person = {
    name: 'Nicholas',
    age: 20
}

function setName(obj) { // 传入一个引用 obj = {}; // 将传入的引用指向另外的值 obj.name = 'Greg'; // 修改引用的name属性 } setName(person); console.log(person.name); // Nicholas 未被改变 

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

4. Functional programming

Although JavaScript is not a purely functional programming language, it uses many features of functional programming. So knowing these features can give us a better understanding of the code we write.

When we want to use a function, we usually want to encapsulate some functions, logic, etc. I believe that everyone is no stranger to the concept of encapsulation.

We usually accomplish one thing through function encapsulation. For example, if I want to calculate the sum of any three numbers, we can wrap a simple function with these three numbers as parameters.

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 what we want to do is relatively simple, we may not see the convenience brought by encapsulation as functions. What if we wanted to do something a little more complicated. For example I want to calculate the sum of all subitems 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 encapsulate the function, then every time we want to implement this function, we have to use the for loop again. The consequence of this is that our code is filled with more and more repetitive 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 that everyone should have a very clear understanding of the meaning of function encapsulation, but the question 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 tend to involuntarily use the imperative programming style 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'], and we want to find all the children of type number in this array. When we use imperative programming thinking, it may be straightforward to do so.

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 implementation, we achieve our goal in a straightforward manner. 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. When there are more occurrences, our code also becomes worse and harder to maintain.

The functional programming thinking suggests that we encapsulate such functions that will appear many times for use.

function getNumbers(array) {
    var res = []; array.forEach(function(item) { if (typeof item === 'number') { res.push(item); } }) return res; } // 以上是我们的封装,以下是功能实现 var array = [1, 3, 'h', 5, 'm', '4']; var res = getNumbers(array); 

Therefore, when we encapsulate the function, we only need to write one line of code when we implement the same function. And if the future demand changes, or slightly modified, we only need to adjust the getNumbers method. And when we use it, we only need to care what this method can do, not how it is implemented. This is one of the places where functional programming thinking differs from imperative.

Functional programming thinking also has the following characteristics.

Functions are first class citizens

The so-called "first class" (first class) means that functions are on an equal footing with other data types, and can be assigned to other variables, passed as parameters, passed into another function, or returned by other functions. value. We should have seen many of these scenes.

var a = function foo() {} // 赋值 function fn(function() {}, num) {} // 函数作为参数 // 函数作为返回值 function var() { return function() { ... ... } } 

Of course, these are all basic concepts of JavaScript. But I think a lot of people, even yourself reading this, might ignore these concepts. It can be verified with a simple example.

Let's define such a function first.

function delay() {
    console.log('5000ms之后执行该方法.'); } 

What to do now is, if you are required to combine the setTimeout method to delay the execution of the delay method by 5000ms, what should you do?

It's actually very simple, right? Just do it directly.

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

So now the problem comes, if you have a deep understanding of the function as a first-class citizen, I think you will find that there are actually some problems with the above way of writing. So think about it, where is the problem?

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

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

var timer = setTimeout(delay, 5000);

Of course, if you have thought of doing this in advance, then congratulations, you are more talented than ordinary people in JavaScript. In fact, the first bad method is used by many people, including those with many years of work experience, and they have not completely avoided it. And they don't even know what's wrong with them.

In future practice, you will encounter more similar scenarios. In order to verify the understanding of readers and friends, let's 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为成功请求之后返回的数据 console.log(resp); }) 

The principle of optimization is exactly the same as the example of setTimeout. I will sell it here. I don't intend to tell you the conclusion. Just a reminder. After getUser is optimized, there is only one line of code. It's time to test everyone's learning achievements ^ ^.

Use only "expressions", not "statements"

"Expression" (expression) is a simple operation process, always have a return value; "statement" (statement) is to perform some operation, no return value. Functional programming requires only expressions, not statements. That is, each step is a pure operation and has a return value.

Suppose that in our project, the background color of an element needs to be changed in many places. So we can encapsulate it like this.

var ele = document.querySelector('.test');
function setBackgroundColor(color) { ele.style.backgroundColor = color; } // 多处使用 setBackgroundColor('red'); setBackgroundColor('#ccc'); 

We can clearly feel that setBackgroundColor encapsulates just a statement. This is not the desired effect. Functional programming expects a function to have inputs and outputs. So good habits should be done as follows.

function setBackgroundColor(ele, color) {
    ele.style.backgroundColor = color;
    return color; } // 多处使用 var ele = document.querySelector('.test'); setBackgroundColor(ele, 'red'); setBackgroundColor(ele, '#ccc'); 

Knowing this allows us to develop good habits when wrapping functions.

pure function

A function that always produces the same output with the same input and has no side effects is a pure function.

The so-called "side effect" refers to the interaction between the inside and the outside of the function (the most typical case is to modify the value of the global variable), resulting in other results than the operation.

Functional programming emphasizes that there are no "side effects", which means that the function must remain independent, all functions are to return a new value, and there is no other behavior, especially the value of external variables must not be modified.

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

For example, we want to encapsulate a function that can get the last item of the passed array. Then it can be achieved 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); // 返回结果4 原数组不变 var last_ = getLast_(source); // 返回结果4 原数据最后一项被删除 

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

var source = [1, 2, 3, 4, 5]; source.slice(1, 3); // 纯函数 返回[2, 3] source不变 source.splice(1, 3); // 不纯的 返回[2, 3, 4] source被改变 source.pop(); // 不纯的 source.push(6); // 不纯的 source.shift(); // 不纯的 source.unshift(1); // 不纯的 source.reverse(); // 不纯的 // 我也不能短时间知道现在source被改变成了什么样子,干脆重新约定一下 source = [1, 2, 3, 4, 5]; source.concat([6, 7]); // 纯函数 返回[1, 2, 3, 4, 5, 6, 7] source不变 source.join('-'); // 纯函数 返回1-2-3-4-5 source不变 

Closure

Closures are an important feature of functional programming languages, and I've said a lot about them in the previous articles. I won't go into details here.

Currying

slightly

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324834853&siteId=291194637