Aop はアスペクト指向プログラミングとも呼ばれます。「通知」はアスペクトの特定の実装であり、前 (通知前)、後 (通知後)、および周囲 (サラウンド通知) に分かれています。 Spring を使用したことがある人はよく知っているはずですが、JS では AOP は技術的な点として無視されています。ただし、aop を使用すると、js コード ロジックを効果的に改善できます。たとえば、フロントエンド フレームワーク dojo および yui3 では、AOP がカスタム イベントの内部メカニズムに昇格され、ソース コードのあらゆる場所で確認できます。この抽象化のおかげで、Dojo のカスタム イベントは非常に強力で柔軟です。 dojo の aop の実装は、dojo/aspect モジュールにあります。before、after、around という 3 つの主要なメソッドがあります。この記事では、around メソッドの実装について段階的に説明します。 dojo/アスペクトモジュールの構造システムの。
js でサラウンド通知を実装するための最も簡単で最も考えさせられる方法は、コールバックを使用することです
advice = function(originalFunc){ console.log("before function"); originalFunc(); console.log("after function"); } var obj = { foo: function(){ console.log('foo'); } } advice(obj.foo)
結果:
関数の前
ふー
関数後
ハハ、簡単すぎるよ、もう寝てもいいですか? 。 。 。
でも、ちょっと乱暴すぎませんか? 。 。 。約束された環境。 。 。 。少なくとも、obj.foo への次の呼び出しでは、ドライな "foo" の代わりにこの結果が得られるはずです。これには、いくつかの変更を加えてクロージャ
を使用する必要があります。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()
出力:
関数の前
関数後
サラウンド効果は得られたようですが、お約束の名前はどこへ行ったのでしょうか? 。 。 。
アドバイスによって返されたクロージャーでは、スコープの問題にも対処する必要があります
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);
call を使用するとスコープの問題が解決されるようです。実行して見てみましょう:
くそー、これが伝説の無限ループか? 。 。 。
無限ループを排除するには、まだいくつかの変更を加え、中間変数を使用する必要があるようです
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();
出力:
関数の前
オブジェクト
関数後
はは、世界は突然美しい場所になりました。 。 。 。
しかし、このコード群は低すぎるように思えますか? いくつかの高レベルの抽象化を考え出す必要がありますか?
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();
around メソッドは、アドバイスが次の形式で記述されている限り、処理プロセスを特定のオブジェクトから切り離します。
advice = function(originalFunc){ return function() { //before originalFunc(); //after } }
ははは、あっという間に背が高くなって、かっこよかったです。 。 。 。
そこで疑問が生じます。誤って around メソッドをもう一度呼び出してしまった場合はどうすればよいでしょうか? 。 。 。 額。 。 。 。これは質問です。イベントのバインド/削除と同様に、バインディングを削除するために、削除メソッドを使用してハンドルを返すようにすべきでしょうか。
削除とは、次回関数が実行されるときに、対応する around メソッドは実行されなくなり、originalFunc メソッドのみが実行されることを意味します
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();
出力:
before function 2 before function 1 obj and [object Arguments] after function 1 after function 2 obj and undefined before function 1
これ。 。少し乱雑になるだけではありません。 。 。エラーも報告されました。 。 。 。はい、我慢できますが、叔父は我慢できませんが、義妹は我慢できません。
ああ、終わりました。 。 。力を貸してください!
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');
出力:
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
戦いが終わったら、もう終わりにしましょう!
初めてブログを書くために徹夜したときも、2時に隣からカラスの鳴き声が聞こえました。未知の鳥のさえずり 5時になると、たくさんの鳥が鳴いていました。 。 。 。
参考記事: