A higher-order function is one that satisfies at least one of the following conditions: (1) it can be passed as an argument; (2) it can be output as a return value
Functions in JavaScript language obviously meet the conditions of higher-order functions. Let's explore the charm of JavaScript higher-order functions together.Copy the code
Higher-order functions implement AOP
The primary role of AOP(aspect oriented programming) is to extract functionality unrelated to the core business logic module and then “dynamically weave” it into the business module. These functions generally include log statistics, security control, exception handling, and so on. AOP is at the heart of the Java Spring architecture. Let’s explore how AOP is implemented in Javascript
In JavaScript to implement AOP, all means to “dynamically weave” a Function into another Function, there are many specific implementation techniques, we use function.prototype to do this. The following code
* @param {*} fn */
Function.prototype.aopBefore = function(fn){
console.log(this)
// Step 1: Save the reference to the original function
const _this = this
// Step 4: Return the "proxy" function that includes the original function and the new function
return function() {
// Step 2: Execute a new function to fix this
fn.apply(this.arguments)
// Execute the original function
return _this.apply(this.arguments)}}* @param {*} fn */
Function.prototype.aopAfter = function (fn) {
const _this = this
return function () {
let current = _this.apply(this.arguments)// Save the original function
fn.apply(this.arguments) // Execute the new function first
return current
}
}
/** * uses the function */
let aopFunc = function() {
console.log('aop')}// Register the section
aopFunc = aopFunc.aopBefore((a)= > {
console.log('aop before')
}).aopAfter((a)= > {
console.log('aop after')})// The real call
aopFunc()
Copy the code
Currying
The first thing we’re going to talk about with curring is what is a function Called a Currization.
Curring is also called partial evaluation. A curring function first takes a few arguments. After receiving these arguments, the function does not immediately evaluate. Instead, it continues to return another function, and the arguments passed are stored in the closure formed by the function. When the function is actually asked for a value, all the arguments passed in before are used to evaluate it at once.
It’s kind of hard to visualize, but let’s do an example where we need a function to calculate consumption over 12 months of the year, and at the end of each month we’re going to calculate how much money we’ve consumed. The normal code is as follows
// Uncurrified function computation overhead
let totalCost = 0
const cost = function(amount, mounth = ' ') {
console.log(The first `${mounth}Monthly expenses are${amount}`)
totalCost += amount
console.log('Current total consumption:${totalCost}`)
}
cost(1000.1) // First month's expenses
cost(2000.2) // Monthly expenses
// ...
cost(3000.12) // 12th month expenditure
Copy the code
In summary, if we want to calculate the total cost of a year, we don’t have to do it 12 times. You just have to do one calculation at the end of the year, and then we’re going to do a partial Curryization of this function to help us understand it
// Partial Currie functions
const curringPartCost = (function() {
// Parameter list
let args = []
return function (){
/** * differentiate the evaluation of the case * with arguments * no arguments */
if (arguments.length === 0) {
let totalCost = 0
args.forEach(item= > {
totalCost += item[0]})console.log('Total consumption:${totalCost}`)
return totalCost
} else {
// Argumens is not an array; it is an array-like object
let currentArgs = Array.from(arguments)
args.push(currentArgs)
console.log(` stagingThe ${arguments[1]?arguments[1] : ' ' }Amount of month,The ${arguments[0]}`)
}
}
})()
curringPartCost(1000.1)
curringPartCost(100.2)
curringPartCost()
Copy the code
Next we write a generic curring and a function that will be curring. The following code
// The generic curring function
const curring = function(fn) {
let args = []
return function () {
if (arguments.length === 0) {
console.log('Curring completed to calculate the total value')
return fn.apply(this, args)
} else {
let currentArgs = Array.from(arguments) [0]
console.log(` stagingThe ${arguments[1]?arguments[1] : ' ' }Amount of month,The ${arguments[0]}`)
args.push(currentArgs)
// Returns the Function object being executed, that is, the body of the specified Function object. This facilitates recursion of anonymous functions or ensures encapsulation of functions
return arguments.callee
}
}
}
// Evaluate the function
let costCurring = (function() {
let totalCost = 0
return function () {
for (let i = 0; i < arguments.length; i++) {
totalCost += arguments[i]
}
console.log('Total consumption:${totalCost}`)
return totalCost
}
})()
// Perform curring
costCurring = curring(costCurring)
costCurring(2000.1)
costCurring(2000.2)
costCurring(9000.12)
costCurring()
Copy the code
Function of the throttle
Most functions in JavaScript are triggered by the user, usually with no performance issues, but in some special cases are not directly controlled by the user. Easy to make a large number of calls causing performance problems. After all, DOM manipulation is very expensive. Here are some of these scenarios:
window.resise
Events.mouse, input
Such events.- Upload progress
- .
Now we implement function throttling by means of higher-order functions
/ * * * * @ throttle function param *} {fn * @ param interval * / {*}
const throttle = function (fn, interval = 500) {
let timer = null./ / timer
isFirst = true // is the first call
return function () {
let args = arguments, _me = this
// The first call to direct pass
if (isFirst) {
fn.apply(_me, args)
return isFirst = false
}
// If there is a timer, intercept it
if (timer) {
return false
}
/ / set the timer
timer = setTimeout(function (){
console.log(timer)
window.clearTimeout(timer)
timer = null
fn.apply(_me, args)
}, interval)
}
}
// Use throttling
window.onresize = throttle(function() {
console.log('throttle')},600)
Copy the code
Time-sharing function
The throttling function provides us with a solution to limit how often the function can be called. Next, we will encounter another problem. Some functions are called by the user actively, but for some objective reasons, these operations will seriously affect the page performance. In this case, we need to adopt another way to solve the problem.
If we need to insert a large number of DOM nodes in a short period of time, it will obviously be too much for the browser. May cause the browser to freeze, so we need to do a time-sharing function, batch inserts.
List * @param {list * @param {list * @param {list * @param {number of nodes per batch} count */
const timeChunk = function(list, fn, count = 1){
let insertList = [], // Data that needs to be inserted temporarily
timer = null / / timer
const start = function(){
// Call the executing functions one by one
for (let i = 0; i < Math.min(count, list.length); i++) {
insertList = list.shift()
fn(insertList)
}
}
return function(){
timer = setInterval((a)= > {
if (list.length === 0) {
return window.clearInterval(timer)
}
start()
},200)}}// Test the time-sharing function
const arr = []
for (let i = 0; i < 94; i++) {
arr.push(i)
}
const renderList = timeChunk(arr, function(data){
let div =document.createElement('div')
div.innerHTML = data + 1
document.body.appendChild(div)
}, 20)
renderList()
Copy the code
Lazy loading function
In Web development, some sniffing is inevitable because of differences in some browsers.
Because of browser differences, we often do a variety of compatibility, a very simple and common example: event binding functions that are common across browsers.
It is usually written like this:
// Common event compatibility
const addEvent = function(el, type, handler) {
if (window.addEventListener) {
return el.addEventListener(type, handler, false)}// for IE
if (window.attachEvent) {
return el.attachEvent(`on${type}`, handler)
}
}
Copy the code
One disadvantage of this function is that it executes the if conditional branch every time it is executed. Although it is not expensive, it is obviously redundant, so let’s optimize it to advance the sniffing process:
const addEventOptimization = (function() {
if (window.addEventListener) {
return (el, type, handler) = > {
el.addEventListener(type, handler, false)}}// for IE
if (window.attachEvent) {
return (el, type, handler) = > {
el.attachEvent(`on${type}`, handler)
}
}
})()
Copy the code
This allows us to do a sniff before the code loads and return a function. But if we put it in a public library and don’t use it, it’s a bit redundant. Let’s use an inert function to solve this problem:
// Lazy loading function
let addEventLazy = function(el, type, handler) {
if (window.addEventListener) {
// Once in the branch, the implementation of the function is modified inside the function
addEventLazy = function(el, type, handler) {
el.addEventListener(type, handler, false)}}else if (window.attachEvent) {
addEventLazy = function(el, type, handler) {
el.attachEvent(`on${type}`, handler)
}
}
addEventLazy(el, type, handler)
}
addEventLazy(document.getElementById('eventLazy'), 'click'.function() {
console.log('lazy ')})Copy the code
Once we enter the branch, we change the implementation of the function inside the function. After rewriting, the function is the desired function. The next time we enter the function, there is no conditional branch statement.
conclusion
This article is a summary of Javascript Design Patterns.
The code address
Original address if feel useful words to ⭐ bar