Originally introduced in LISP, anonymous functions have evolved into the domain of not only functional languages, but also increasingly used in interpreted and compiled languages, perhaps with the more fashionable name lambda expressions.

Closures are usually implemented using anonymous functions that refer to external variables in an anonymous function, and that anonymous function forms a closure. Closures and anonymous functions are often confused because of their inextricable relationship. In fact, in Js anonymous function, closure, self-executing function, callback function, arrow function, these concepts seem to be the same, but also different, please readers to understand.

Anonymous functions have dynamically programmable execution. Smart use can make your code simple yet elegant, flexible yet constrained. Okay, let’s get down to the business of this article and refactor code using anonymous functions. As is customary with refactoring, point out the Bad smells in your code:

  • Define long, repetitive configurations
  • Set filtering with variable conditions
  • A no-nonsense method call

Define long, repetitive configurations

When writing configuration code, you often encounter a lot of repeated configuration. If you need to change something, you have to change everything, which is really tiring and easy to miss. Such as:

{
  html: `
    <label><input type="checkbox" name="apple" /> Apple</label>
    <label><input type="checkbox" name="banana" /> Banana</label>
    <label><input type="checkbox" name="orange" /> Orange</label>
  `
}
Copy the code

There are three options, and the structure of the three options is exactly the same. If you want to add a title to all the options, you have to repeat the same thing three times.

{
  html: `
    <label><input type="checkbox" name="apple" title="Apple" /> Apple</label>
    <label><input type="checkbox" name="banana" title="Banana" /> Banana</label>
    <label><input type="checkbox" name="orange" title="Orange" /> Orange</label>
  `
}
Copy the code

Programming apes are a strange breed of humans who would rather do five different things than do the same thing three times. So how can it be tolerated to extract the different contents of the configuration:

{
  html: (function(fruits) {
    return fruits.map(it => `<label><input type="checkbox" name="${it.name}" title="${it.title}" /> ${it.title}</label>`).join('');
  }([
    {name: 'apple', title: 'Apple'},
    {name: 'banana', title: 'Banana'},
    {name: 'orange', title: 'Orange'}
  ]))
}
Copy the code

Such changes are completely transparent to the callers of the configuration, but for the maintainers of the configuration, separating the structure from the data, changing the executing method body if the structure needs to be changed, and changing the parameters passed into the executing method if the data needs to be changed, greatly reduces the risk of making mistakes and avoids mindless copy-and-paste.

While writing code logic in a configuration is not particularly recommended, it is nothing compared to code maintainability.

Set filtering with variable conditions

Set filtering is a very common requirement. Suppose you have a set of students:

let students = [  
  {name: 'Lucy', age: 20, sex: 'female'},
  {name: 'LiLei', age: 21, sex: 'male'},
  {name: 'Jim', age: 18, sex: 'male'}
]
Copy the code

Now filter out students aged 20:

function filterByAge(list, age) {  
  let filtered = [];
  for (let i = 0; i < list.length; i++) {
    if (list[i].age === age) {
      filtered.push(list[i]);
    }
  }
  return filtered;
}
Copy the code

Select * from student whose name is LiLei:

function filterByName(list, name) {  
  let filtered = [];
  for (let i = 0; i < list.length; i++) {
    if (list[i].name === name) {
      filtered.push(list[i]);
    }
  }
  return filtered;
}
Copy the code

Also filter out those who are male:

function filterBySex(list, sex) {  
  let filtered = [];
  for (let i = 0; i < list.length; i++) {
    if (list[i].sex === sex) {
      filtered.push(list[i]);
    }
  }
  return filtered;
}
Copy the code

In you feel accomplished, can stand up bending stretch of the moment, suddenly by a powerful force back to the seat, help me find out the name with “L” at the beginning of the children’s shoes. Even though your heart is MMP ~, what else can you do? Write, so you add the following method:

function filterByNameStart(list, nameStart) {  
  let filtered = [];
  for (let i = 0; i < list.length; i++) {
    if (list[i].name.indexOf(nameStart) === 0) {
      filtered.push(list[i]);
    }
  }
  return filtered;
}
Copy the code

As a result, this call method:

filterByName(filterBySex(filterByAge(students, 21), 'male'), 'LiLei');  
Copy the code

It can be interpreted as finding the male LiLei whose age is 21. Well, the readability is not too bad.

FilterByAge = filterByNameStart = filterByNameStart = filterByAge = filterByNameStart And there’s no flexibility, you have to add methods when the caller changes the requirements.

Let’s now use anonymous functions to pull out the different parts and let the caller filter them out however he wants. The main logic of the filter method only cares if the current element is to be added to the result set. The other logic is left to the anonymous fn function:

function filter(list, fn) {  
  let filtered = [];
  for (let i = 0; i < list.length; i++) {
    if (fn(list[i], i) === true) {
      filtered.push(list[i]);
    }
  }
  return filtered;
}
Copy the code

The above example would be written like this:

filter(students, function(member) {  
  return member.name === 'LiLei' && member.age === 21 && member.sex === 'male';
});
Copy the code

Using the arrow method can be more concise:

filter(students, member => member.name === 'LiLei' &&  
    member.age === 21 && 
    member.sex === 'male');
Copy the code

Now the caller will mention some abnormal filtering methods, you can return a look, write yourself.

A no-nonsense method call

Suppose there is an Api interface that doubles the number passed in and returns it. This interface supports both single and batch methods.

The incoming 5

  • Execution Success{status: 'success', data: 10}
  • Execution failure{status: 'failed', error: 'xxx'}

The incoming [2, 3]

  • Execution Success{status: 'success', data: [{status: 'success', data: 4}, {status: 'success', data: 6}]}
  • Execution failure{status: 'success', data: [{status: 'failed', error: 'xxx'}, {status: 'failed', error: 'xxx'}]}

That is, a single input will be output in a single format, and a batch input will be output in a batch format. Three under five divided by two, to achieve the following version:

Function multiple(inNum) {if (array.isarray (inNum)) {return {status: 'success', data: inNum.map(it => { if (isNaN(parseFloat(it))) { return { status: 'failed', error: 'The input is not a number' }; } return { status: 'success', data: it * 2 } }) }; } else {// handle a single case if (isNaN(parseFloat(inNum))) {return {status: 'failed', error: 'The input is not a number'}; } return { status: 'success', data: inNum * 2 }; }}Copy the code

There are two ways of single and batch except input and output format, other logic is exactly the same. If you want to change times 2 to times 3, you have to change both places. Let’s see how anonymous functions can be used to avoid two changes:

Function execute(data, fn) {// let single => {try {return {status: 'success', data: fn(it)}; } catch (e) { return { status: 'failed', error: e.toString() } } }; if (Array.isArray(data)) { return { status: 'success', data: data.map(single) } } else { return single(data); } } function multiple(inNum) { return execute(inNum, it => { if (isNaN(parseFloat(it))) { throw new Error('The input is not a number'); } return it * 2; }); }Copy the code

The execute method now does all the dirty work, just formatting input and output and error handling. The Multiple method only cares about the implementation of the business and does not care whether the input is a single element or an array. If you want to multiply by 3, just change the last return of the multiple method. Execute can also be reused by other Api methods.

conclusion

The purpose of this article is to throw light on the bad taste of anonymous function refactoring in code, and not just in Js. As long as you think more and do more, the quality of your code will go up day by day

One more thing I’d like to say about refactoring is that it should be done incrementally. The first time you do it it might be ok, but the second time you have to figure out if there’s a better way to do it. It is best if the changes are transparent to the caller, but if not, it is worth the time trade-off. Your boss is watching you 😡

Happy Code 😁