Section-oriented programming

Here’s a wiki definition:

Aspect-oriented programming (AOP) is a programming idea in computer science, which aims to further separate crosscutting concerns from business entities to improve the modularity of program code. You can uniformly manage and decorate blocks of code declared as “pointcuts” by adding additional Advice to existing code, such as “adding background logs to all methods whose names start with ‘set*'”. This idea allows developers to add features that are less closely related to the code’s core business logic, such as logging capabilities, without reducing the readability of the business code. The idea of section-oriented programming is also the foundation of section-oriented software development.

Separation of concerns

Separating business code from data statistics code (non-business code) is one of the classic uses of AOP in any language. Separating crosscutting concerns from core concerns is a core concept of AOP. Simply put, consider using AOP if you need to insert some non-business code before or after your core business logic. Among the common requirements on the front end, There are some businesses you can use AOP to separate from your core concerns – Node.js logging – burying points, data reporting – performance analysis, counting function execution times – dynamically adding parameters to Ajax requests, dynamically changing function parameters – separating form requests and validation – shaking and throttling

The basic instance

A. before b. after C. before D. after

AOP inserts non-business logic in the process of function execution on the basis of not changing the logic in the original function. Since the original function logic is not changed, the low coupling between business logic and non-business logic can be achieved. In JS, AOP is dependent on advanced functions. Here are a few simple examples. For simplicity, add functions directly by manipulating the Function prototype chain: this example requires that you have some knowledge of Function prototypes and this Pointers

// AOP pre-notification
Function.prototype._before = function(beforeFn) {
	let self = this;
	return function() {
		// Execute before
		beforeFn.apply(self, arguments)
		// Execute the original function
		return self.apply(self, arguments)}}// AOP post-notification
Function.prototype._after = function(afterFn) {
	let self = this
	return function() {
		let ret = self.apply(self, arguments)
		afterFn.apply(self, arguments)
		return ret
	}
}
Copy the code

This is a basic higher-order function, passing in a function, constructing a new function, and executing the original function in the new function in the same order as passing in the tangent function, so that you can add non-business logic without affecting the original function

  • Statistics function execution time

You can add two sections before and after the function is executed to calculate the execution time. The advantage of this is that there is no need to add non-business logic to the original logic code. If you want to delete the time statistics later, you can simply delete this code without any impact on the original function. If you write non-business logic in the main function, be careful not to change the business logic code by mistake.

	let t1 , t2
	function doSth() {
		// Some synchronous complex operations
		let i=0
		while(i < 10000) i ++
	}
	// If you want to delete the statistical time function, delete the following code
	doSth = doSth._before(() = > {
		t1 = +new Date(a)console.log('Before function execution')
	})._after(() = > {
		t2 = +new Date(a)console.log('Function execution completed, time consuming${t2 - t1}ms`)})Copy the code

Of course, we need to modify _before and _after with async/await to take into account that the target function is an asynchronous function:

// AOP pre-notification
Function.prototype._before = function (beforeFn) {
    let self = this;
    return async function () {
        // Execute before
        beforeFn.apply(self, arguments);
        // Execute the original function
        return await self.apply(self, arguments);
    };
};
// AOP post-notification
Function.prototype._after = function (afterFn) {
    let self = this;
    return async function () {
        let ret = await self.apply(self, arguments);
        afterFn.apply(self, arguments);
        return ret;
    };
};

// Asynchronous functions
function doSthSync() {
    return new Promise((resolve) = > {
        setTimeout(() = > {
            resolve();
            console.log("The function executes ing");
        }, 3000);
    });
}
// Front and back sections
let t1, t2;
doSthSync = doSthSync
    ._before(() = > {
        t1 = +new Date(a);console.log("Before the function executes");
    })
    ._after(() = > {
        t2 = +new Date(a);console.log("Function completed: Time" + (t2 - t1) + "ms");
    });

doSthSync();

Copy the code

Print result:

In this way, the need for a function execution time statistics with a complete separation of business logic and non-business logic can be realized.

Conclusion: Using higher-order functions to encapsulate the original function and extend the function function outside the original function to achieve the decoupling of business logic and non-business logic, this is the benefit that AOP brings us

Notice around

In fact, the above implementation is a bit complicated for the need to count time. The time count is a function point, but it is called in _before and _after functions, which is not friendly. Before and after insert sections before and after the function, respectively. Is there a way to operate before and after the target function at the same time

Function.prototype._around = function (aroundFn) {
    let self = this;
    function JoinPoint(args) {
        // Get control of the original function
        this.invoke = function () {
            self.apply(self, args);
        };
    }

    return function () {
        let args = [new JoinPoint(arguments)];
        aroundFn.apply(self, args);
    };
};

function doSth() {
    console.log("doSth");
}

doSth = doSth._around((descriptor) = > {
    let fn = descriptor.invoke;
    let t1 = +new Date(a);// Execute the original function
    fn();
    console.log("Function execution time:, +new Date() - t1);
});

doSth();

Copy the code

Results: [image: D30BA0A6 – ABB3-41 c8-8 f81 de39a1fb78d 0-44166-0000 fada1ffb9b87/3 abafe09 – A449-436 – f – A944 – BF09A6BA00F5. PNG]

  • Form validation

Separate submission and validation, decouple code

	function validate() {
		/ / verification
	}

	function submit() {}

	submit = submit._around((descriptor) = > {
		let fn = descriptor.invoke
		if(validate()) {
			fn()
		} else {
			// Verification failed
		}
	})

	submit()
Copy the code

Decorator ()

Decorator is a scheme proposed in ES6, which makes AOP programming more convenient. It can be said that its design idea itself is section-oriented. JS decorator can be used to “decorate” three types of objects:

  • Attributes of a class
  • Methods of a class
  • The class itself

The syntax of a Decorator is simple. You can just prefix the @Decorator to the attribute/method of a class and the class itself. The name of the Decorator can be customized

  • Introduction to decorators

Class decorator The class decorator takes a taget parameter whose value is an instance of the current class

// Class decorator
@classDecorator
class Modal {}

// Define the decorator
function classDecorator(target) {
    target.sayMyName = "I am the name assigned by the decorator!";
    return target; // We need to return the class itself
}

/ / test
console.log(Modal.sayMyName);
Copy the code

The method decorator of the method decorator class takes three parameters target, name, and Descriptor

  1. target: The prototype of the class that decorates the method, in this caseModal.prototype
  2. name: The name of the decorated method, in this caseshow
  3. descriptor: Property descriptor for the method, that is:value,writable,enumerable,configurableAs well asgetandsetAccessors, for those of you who don’t know, you can go and review themObject.definePropertyThis function
class Modal {
	@funcDecorator
	show() {
		console.log('Popover has been opened! ')}}function funcDecorator(target, name, descriptor) {
	const originalFn = descriptor.value  // The value of the function
	const self = this  // Keep this reference
	descriptor.value = function() {
		console.log('Pop up! ')
		return originalFn.apply(self, arguments)  // Return the result of the original function to ensure that the external call logic is not changed}}/ / test
const modal = new Modal()
modal.show()
/ / print:
// Open popover!
// Popovers have been opened!
Copy the code
  • Use decorators to count the time
// utils.js
export function measure(target, name, descriptor) {
	let oldFn = descriptor.value;
	descriptor.value = async function() {
            let startTime = Date.now()
            // Execute the decorated function
            let ret = await oldFn.apply(this.arguments)
            console.log(` function${name}The execution time of isThe ${Date.now() - startTime}`)
            return ret
	}
}
Copy the code

Once the decorator is defined, it only needs to be referenced in the class that needs time statistics:

import {measure} from './utils.js'

class Prod {.../ / a decorator
	@measure
	showDialog() {
		// Business logic}... }Copy the code
  • Defines the global class error handling decorator

Just put in the code and make sense of it

	// Global class error handling decorator
const asyncClass = (errHandler? : (err:Error) = >void) = > (target: any) = > {
  Object.getOwnPropertyNames(target.prototype).forEach((key) = > {
    let fn = target.prototype[key];
    target.prototype[key] = async(... args: any) => {try {
        await fn.apply(this. args); }catch(err) { errHandler && errHandler(err); }}; });return target;
};
Copy the code

Use:

// Perform exception handling on all methods in class B, and print err when exceptions occur
@asyncClass((err: Error) = > {
  console.log('Global interception:', err);
})
class B {
  someFunc() {
    throw new Error("A mistake"); }}Copy the code

Print result: