Aop is also called aspect-oriented programming, in which "notification" is the specific implementation of aspects, which is divided into before (pre-notification), after (post-notification), and around (surround notification). Students who have used spring must be familiar with it. Very familiar, but in js, AOP is a seriously ignored technical point. However, using aop can effectively improve js code logic. For example, in front-end frameworks dojo and yui3, AOP is promoted to an internal mechanism of custom events, which can be seen everywhere in the source code. Thanks to this abstraction, Dojo's custom events are extremely powerful and flexible. The implementation of aop in dojo is in the dojo/aspect module. There are three main methods: before, after, and around. This article will lead you step by step to implement the around method. Subsequent articles will provide an in-depth analysis of the structural system of the dojo/aspect module.
To implement surround notification in js, the simplest and most thought-provoking way is to use callback
advice = function(originalFunc){ console.log("before function"); originalFunc(); console.log("after function"); } var obj = { foo: function(){ console.log('foo'); } } advice(obj.foo)
Result:
before function
foo
after function
Haha, it’s too simple. Can you go back to sleep? . . .
But, isn’t it a bit too rough? . . . The promised surroundings. . . . At least the next call to obj.foo should have this result, instead of a dry "foo"; for this we need to make some changes and use closures
advice = function(originalFunc){ return function() { console.log("before function"); originalFunc(); console.log("after function"); } } var obj = { foo: function(){ console.log(this.name); }, name: "obj" } obj.foo = advice(obj.foo) obj.foo()
Output:
before function
after function
It seems that the surround effect has been achieved, but where has the promised name gone? . . .
In the closure returned by advice, we also need to deal with scope issues
advice = function(originalFunc){ return function() { console.log("before function"); originalFunc(); console.log("after function"); } } var obj = { foo: function(){ console.log(this.name); }, name: "obj" } keepContext = function() { return obj['foo'].call(obj); } obj.foo = advice(keepContext);
It seems that the scope problem is solved by using call. Let’s run it and see:
Damn it, is this the legendary endless loop? . . .
It seems that we still need to make some changes and use an intermediate variable to eliminate the infinite loop
advice = function(originalFunc){ return function() { console.log("before function"); originalFunc(); console.log("after function"); } } var obj = { foo: function(){ console.log(this.name); }, name: "obj" } var exist = obj.foo; keepContext = function() { return exist.call(obj); } obj.foo = advice(keepContext); obj.foo();
Output:
before function
obj
after function
Haha, the world suddenly became a beautiful place. . . .
But does this bunch of code seem too low? Should we come up with some high-level abstractions? Well, I think so too
function around(obj, prop, advice){ var exist = obj[prop]; var advised = advice(function(){ return exist.call(obj, arguments); }); obj[prop] = advised; } advice = function(originalFunc){ return function() { console.log("before function"); originalFunc(); console.log("after function"); } } var obj = { foo: function(){ console.log(this.name); }, name: "obj" } around(obj, 'foo', advice); obj.foo();
The around method decouples the processing process from the specific object; as long as advice is written in the following format, the around effect can be achieved
advice = function(originalFunc){ return function() { //before originalFunc(); //after } }
Haha, you are so tall and cool in an instant, so cool. . . .
Then the question comes: What should I do if I accidentally call the around method one more time? . . . Forehead. . . . This is a question. Should we let around return a handle with a remove method to eliminate the binding, just like binding/removing events.
What remove means is that the next time the function is executed, it will no longer execute the corresponding around method, but only run the originalFunc method
function around(obj, prop, advice){ var exist = obj[prop]; var previous = function(){ return exist.call(obj, arguments); }; var advised = advice(previous); obj[prop] = advised; return { remove: function(){ obj[prop] = exist; advice = null; previous = null; exist = null; obj = null; } } } var count = 1; advice = function(originalFunc){ var current = count++; return function() { console.log("before function " + current); originalFunc(arguments); console.log("after function " + current); } } var obj = { foo: function(arg){ console.log(this.name + " and " + arg); }, name: "obj" } h1 = around(obj, 'foo', advice); h2 = around(obj, 'foo', advice); obj.foo(); h1.remove(); obj.foo(); h2.remove(); obj.foo();
Output:
before function 2 before function 1 obj and [object Arguments] after function 1 after function 2 obj and undefined before function 1
This. . Not only does it turn out to be a bit messy. . . Also reported an error. . . . Yes, it's bearable, but my uncle can't bear it. My uncle can't bear it, but my sister-in-law can't bear it!
Ah, closure. . . Please give me strength!
function around(obj, prop, advice){ var exist = obj[prop]; var previous = function(){ return exist.apply(obj, arguments); }; var advised = advice(previous); obj[prop] = function(){ //当调用remove后,advised为空 //利用闭包的作用域链中可以访问到advised跟previous变量,根据advised是否为空可以来决定调用谁 return advised ? advised.apply(obj, arguments) : previous.apply(obj, arguments); }; return { remove: function(){ //利用闭包的作用域链,在remove时将advised置空,这样执行过程中不会进入本次around //这几个不能删 //obj[prop] = exist; advised = null; advice = null; //previous = null; //exist = null; //obj = null; } } } var count = 1; advice = function(originalFunc){ var current = count++; return function() { console.log("before function " + current); originalFunc.apply(this, arguments); console.log("after function " + current); } } var obj = { foo: function(arg){ console.log(this.name + " and " + arg); }, name: "obj" } h1 = around(obj, 'foo', advice); h2 = around(obj, 'foo', advice); obj.foo('hello world'); h1.remove(); obj.foo('hello world'); h2.remove(); obj.foo('hello world');
Output:
before function 2 before function 1 obj and hello world after function 1 after function 2 before function 2 obj and hello world after function 2 obj and hello world
After the fight, call it a day!
The first time I stayed up all night to blog, I was also drunk. At two o'clock, I heard the fuck me next door. At four o'clock, I heard the crow of crows. There was also an unknown bird chirping. At five o'clock, there were a lot of people. Birds chirp. . . .
Reference article:
Use AOP to improve javascript code
AOP (aspect-oriented programming) and OOP (object-oriented programming) of yui3