会笑的芒果精灵 發表於 2019-6-11 23:52:00

TypeScript 装饰器的执行原理

<table class="d-block">
<tbody class="d-block">
    <tr class="d-block">
      <td class="d-block comment-body markdown-bodyjs-comment-body">
<p>装饰器本质上提供了对被装饰对象 Property​ Descriptor 的操作,在运行时被调用。</p>
<p>因为对于同一对象来说,可同时运用多个装饰器,然后装饰器中又可对被装饰对象进行任意的修改甚至是替换掉实现,直观感觉会有一些主观认知上的错觉,需要通过代码来验证一下。</p>
<p>比如,假若每个装饰器都对被装饰对象的有替换,其结果会怎样?</p>
<h2>多个装饰器的应用</h2>
<p>通过编译运行以下示例代码并查看其结果可以得到一些直观感受:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">function</span> f() {
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>f(): evaluated<span class="pl-pds">"</span></span>);
<span class="pl-k">return</span> <span class="pl-k">function</span>(<span class="pl-v">_target</span><span class="pl-k">:</span> <span class="pl-c1">any</span>, <span class="pl-v">key</span><span class="pl-k">:</span> <span class="pl-c1">string</span>, <span class="pl-v">descriptor</span><span class="pl-k">:</span> <span class="pl-en">PropertyDescriptor</span>) {
    <span class="pl-k"><span class="pl-k">const</span></span> original <span class="pl-k">=</span> <span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span>;
    <span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span> <span class="pl-k">=</span> <span class="pl-k">function</span>(<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>[]) {
      <span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">`</span>before ${<span class="pl-smi">key</span>} called<span class="pl-pds">`</span></span>, <span class="pl-smi">args</span>);
      <span class="pl-k"><span class="pl-k">const</span></span> result <span class="pl-k">=</span> <span class="pl-smi">original</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, <span class="pl-smi">args</span>);
      <span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">`</span>after ${<span class="pl-smi">key</span>} called<span class="pl-pds">`</span></span>);
      <span class="pl-k">return</span> <span class="pl-smi">result</span>;
    };
    <span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>f(): called<span class="pl-pds">"</span></span>);
    <span class="pl-k">return</span> <span class="pl-smi">descriptor</span>;
};
}
<p><span class="pl-k">function</span> g() {<br>
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>g(): evaluated<span class="pl-pds">"</span></span>);<br>
<span class="pl-k">return</span> <span class="pl-k">function</span>(<span class="pl-v">_target</span><span class="pl-k">:</span> <span class="pl-c1">any</span>, <span class="pl-v">key</span><span class="pl-k">:</span> <span class="pl-c1">string</span>, <span class="pl-v">descriptor</span><span class="pl-k">:</span> <span class="pl-en">PropertyDescriptor</span>) {<br>
<span class="pl-k"><span class="pl-k">const</span></span> original <span class="pl-k">=</span> <span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span>;<br>
<span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span> <span class="pl-k">=</span> <span class="pl-k">function</span>(<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>[]) {<br>
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds"><code>&lt;/span&gt;before ${&lt;span class="pl-smi"&gt;key&lt;/span&gt;} called&lt;span class="pl-pds"&gt;</code></span></span>, <span class="pl-smi">args</span>);<br>
<span class="pl-k"><span class="pl-k">const</span></span> result <span class="pl-k">=</span> <span class="pl-smi">original</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, <span class="pl-smi">args</span>);<br>
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds"><code>&lt;/span&gt;after ${&lt;span class="pl-smi"&gt;key&lt;/span&gt;} called&lt;span class="pl-pds"&gt;</code></span></span>);<br>
<span class="pl-k">return</span> <span class="pl-smi">result</span>;<br>
};<br>
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>g(): called<span class="pl-pds">"</span></span>);<br>
<span class="pl-k">return</span> <span class="pl-smi">descriptor</span>;<br>
};<br>
}</p>
<p><span class="pl-k">class</span> <span class="pl-en">C</span> {<br>
@<span class="pl-en">f</span>()<br>
@<span class="pl-en">g</span>()<br>
foo(<span class="pl-v">count</span><span class="pl-k">:</span> <span class="pl-c1">number</span>) {<br>
<span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds"><code>&lt;/span&gt;foo called ${&lt;span class="pl-smi"&gt;count&lt;/span&gt;}&lt;span class="pl-pds"&gt;</code></span></span>);<br>
}<br>
}</p>
<p><span class="pl-k"><span class="pl-k">const</span></span> c <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">C</span>();<br>
<span class="pl-smi">c</span>.<span class="pl-en">foo</span>(<span class="pl-c1">0</span>);<br>
<span class="pl-smi">c</span>.<span class="pl-en">foo</span>(<span class="pl-c1">1</span>);</p></pre></div><p></p>
<p>先放出执行结果:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-en">f</span>(): evaluated
<span class="pl-en">g</span>(): evaluated
<span class="pl-en">g</span>(): called
<span class="pl-en">f</span>(): called
before foo called [ 0 ]
before foo called [ 0 ]
foo called 0
after foo called [ 0 ]
after foo called [ 0 ]
before foo called [ 1 ]
before foo called [ 1 ]
foo called 1
after foo called [ 1 ]
after foo called [ 1 ]</pre></div>
<p>下面来详细分析。</p>
<h2>编译后的装饰器代码</h2>
<p>首页看看编译后变成 JavaScript 的代码,毕竟这是实际运行的代码:</p>
<details>
<summary>
编译后的代码
</summary>
<div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> __decorate <span class="pl-k">=</span> (<span class="pl-c1">this</span> <span class="pl-k">&amp;&amp;</span> <span class="pl-c1">this</span>.<span class="pl-smi">__decorate</span>) <span class="pl-k">||</span> <span class="pl-k">function</span> (<span class="pl-smi">decorators</span>, <span class="pl-smi">target</span>, <span class="pl-smi">key</span>, <span class="pl-smi">desc</span>) {
    <span class="pl-k">var</span> c <span class="pl-k">=</span> <span class="pl-c1">arguments</span>.<span class="pl-c1">length</span>, r <span class="pl-k">=</span> c <span class="pl-k">&lt;</span> <span class="pl-c1">3</span> <span class="pl-k">?</span> target <span class="pl-k">:</span> desc <span class="pl-k">===</span> <span class="pl-c1">null</span> <span class="pl-k">?</span> desc <span class="pl-k">=</span> <span class="pl-c1">Object</span>.<span class="pl-en">getOwnPropertyDescriptor</span>(target, key) <span class="pl-k">:</span> desc, d;
    <span class="pl-k">if</span> (<span class="pl-k">typeof</span> <span class="pl-c1">Reflect</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>object<span class="pl-pds">"</span></span> <span class="pl-k">&amp;&amp;</span> <span class="pl-k">typeof</span> <span class="pl-c1">Reflect</span>.<span class="pl-smi">decorate</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>function<span class="pl-pds">"</span></span>) r <span class="pl-k">=</span> <span class="pl-c1">Reflect</span>.<span class="pl-en">decorate</span>(decorators, target, key, desc);
    <span class="pl-k">else</span> <span class="pl-k">for</span> (<span class="pl-k">var</span> i <span class="pl-k">=</span> <span class="pl-smi">decorators</span>.<span class="pl-c1">length</span> <span class="pl-k">-</span> <span class="pl-c1">1</span>; i <span class="pl-k">&gt;=</span> <span class="pl-c1">0</span>; i<span class="pl-k">--</span>) <span class="pl-k">if</span> (d <span class="pl-k">=</span> decorators) r <span class="pl-k">=</span> (c <span class="pl-k">&lt;</span> <span class="pl-c1">3</span> <span class="pl-k">?</span> <span class="pl-en">d</span>(r) <span class="pl-k">:</span> c <span class="pl-k">&gt;</span> <span class="pl-c1">3</span> <span class="pl-k">?</span> <span class="pl-en">d</span>(target, key, r) <span class="pl-k">:</span> <span class="pl-en">d</span>(target, key)) <span class="pl-k">||</span> r;
    <span class="pl-k">return</span> c <span class="pl-k">&gt;</span> <span class="pl-c1">3</span> <span class="pl-k">&amp;&amp;</span> r <span class="pl-k">&amp;&amp;</span> <span class="pl-c1">Object</span>.<span class="pl-en">defineProperty</span>(target, key, r), r;
};
<span class="pl-k">var</span> __metadata <span class="pl-k">=</span> (<span class="pl-c1">this</span> <span class="pl-k">&amp;&amp;</span> <span class="pl-c1">this</span>.<span class="pl-smi">__metadata</span>) <span class="pl-k">||</span> <span class="pl-k">function</span> (<span class="pl-smi">k</span>, <span class="pl-smi">v</span>) {
    <span class="pl-k">if</span> (<span class="pl-k">typeof</span> <span class="pl-c1">Reflect</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>object<span class="pl-pds">"</span></span> <span class="pl-k">&amp;&amp;</span> <span class="pl-k">typeof</span> <span class="pl-c1">Reflect</span>.<span class="pl-smi">metadata</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>function<span class="pl-pds">"</span></span>) <span class="pl-k">return</span> <span class="pl-c1">Reflect</span>.<span class="pl-en">metadata</span>(k, v);
};
<span class="pl-k">function</span> <span class="pl-en">f</span>() {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>f(): evaluated<span class="pl-pds">"</span></span>);
    <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">_target</span>, <span class="pl-smi">key</span>, <span class="pl-smi">descriptor</span>) {
      <span class="pl-k">var</span> original <span class="pl-k">=</span> <span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span>;
      <span class="pl-smi">descriptor</span>.<span class="pl-en">value</span> <span class="pl-k">=</span> <span class="pl-k">function</span> () {
            <span class="pl-k">var</span> args <span class="pl-k">=</span> [];
            <span class="pl-k">for</span> (<span class="pl-k">var</span> _i <span class="pl-k">=</span> <span class="pl-c1">0</span>; _i <span class="pl-k">&lt;</span> <span class="pl-c1">arguments</span>.<span class="pl-c1">length</span>; _i<span class="pl-k">++</span>) {
                args <span class="pl-k">=</span> <span class="pl-c1">arguments</span>;
            }
            <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>before <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);
            <span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">original</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, args);
            <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>after <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);
            <span class="pl-k">return</span> result;
      };
      <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>f(): called<span class="pl-pds">"</span></span>);
      <span class="pl-k">return</span> descriptor;
    };
}
<span class="pl-k">function</span> <span class="pl-en">g</span>() {
    <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>g(): evaluated<span class="pl-pds">"</span></span>);
    <span class="pl-k">return</span> <span class="pl-k">function</span> (<span class="pl-smi">_target</span>, <span class="pl-smi">key</span>, <span class="pl-smi">descriptor</span>) {
      <span class="pl-k">var</span> original <span class="pl-k">=</span> <span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span>;
      <span class="pl-smi">descriptor</span>.<span class="pl-en">value</span> <span class="pl-k">=</span> <span class="pl-k">function</span> () {
            <span class="pl-k">var</span> args <span class="pl-k">=</span> [];
            <span class="pl-k">for</span> (<span class="pl-k">var</span> _i <span class="pl-k">=</span> <span class="pl-c1">0</span>; _i <span class="pl-k">&lt;</span> <span class="pl-c1">arguments</span>.<span class="pl-c1">length</span>; _i<span class="pl-k">++</span>) {
                args <span class="pl-k">=</span> <span class="pl-c1">arguments</span>;
            }
            <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>before <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);
            <span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">original</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, args);
            <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>after <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);
            <span class="pl-k">return</span> result;
      };
      <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>g(): called<span class="pl-pds">"</span></span>);
      <span class="pl-k">return</span> descriptor;
    };
}
<span class="pl-k">var</span> <span class="pl-c1">C</span> <span class="pl-k">=</span> <span class="pl-c"><span class="pl-c">/**</span> <span class="pl-k">@class</span> <span class="pl-c">*/</span></span> (<span class="pl-k">function</span> () {
    <span class="pl-k">function</span> <span class="pl-en">C</span>() {
    }
    <span class="pl-c1">C</span>.<span class="pl-c1">prototype</span>.<span class="pl-en">foo</span> <span class="pl-k">=</span> <span class="pl-k">function</span> (<span class="pl-smi">count</span>) {
      <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>foo called <span class="pl-pds">"</span></span> <span class="pl-k">+</span> count);
    };
    <span class="pl-en">__decorate</span>([
      <span class="pl-en">f</span>(),
      <span class="pl-en">g</span>(),
      <span class="pl-en">__metadata</span>(<span class="pl-s"><span class="pl-pds">"</span>design:type<span class="pl-pds">"</span></span>, <span class="pl-c1">Function</span>),
      <span class="pl-en">__metadata</span>(<span class="pl-s"><span class="pl-pds">"</span>design:paramtypes<span class="pl-pds">"</span></span>, [<span class="pl-c1">Number</span>]),
      <span class="pl-en">__metadata</span>(<span class="pl-s"><span class="pl-pds">"</span>design:returntype<span class="pl-pds">"</span></span>, <span class="pl-k">void</span> <span class="pl-c1">0</span>)
    ], <span class="pl-c1">C</span>.<span class="pl-c1">prototype</span>, <span class="pl-s"><span class="pl-pds">"</span>foo<span class="pl-pds">"</span></span>, <span class="pl-c1">null</span>);
    <span class="pl-k">return</span> <span class="pl-c1">C</span>;
}());
<span class="pl-k">var</span> c <span class="pl-k">=</span> <span class="pl-k">new</span> <span class="pl-en">C</span>();
<span class="pl-smi">c</span>.<span class="pl-en">foo</span>(<span class="pl-c1">0</span>);
<span class="pl-smi">c</span>.<span class="pl-en">foo</span>(<span class="pl-c1">1</span>);</pre></div>
</details>
<p>先看经过 TypeScript 编译后的代码,重点看这一部分:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> <span class="pl-c1">C</span> <span class="pl-k">=</span> <span class="pl-c"><span class="pl-c">/**</span> <span class="pl-k">@class</span> <span class="pl-c">*/</span></span> (<span class="pl-k">function</span> () {
    <span class="pl-k">function</span> <span class="pl-en">C</span>() {
    }
    <span class="pl-c1">C</span>.<span class="pl-c1">prototype</span>.<span class="pl-en">foo</span> <span class="pl-k">=</span> <span class="pl-k">function</span> (<span class="pl-smi">count</span>) {
      <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>foo called <span class="pl-pds">"</span></span> <span class="pl-k">+</span> count);
    };
    <span class="pl-en">__decorate</span>([
      <span class="pl-en">f</span>(),
      <span class="pl-en">g</span>(),
      <span class="pl-en">__metadata</span>(<span class="pl-s"><span class="pl-pds">"</span>design:type<span class="pl-pds">"</span></span>, <span class="pl-c1">Function</span>),
      <span class="pl-en">__metadata</span>(<span class="pl-s"><span class="pl-pds">"</span>design:paramtypes<span class="pl-pds">"</span></span>, [<span class="pl-c1">Number</span>]),
      <span class="pl-en">__metadata</span>(<span class="pl-s"><span class="pl-pds">"</span>design:returntype<span class="pl-pds">"</span></span>, <span class="pl-k">void</span> <span class="pl-c1">0</span>)
    ], <span class="pl-c1">C</span>.<span class="pl-c1">prototype</span>, <span class="pl-s"><span class="pl-pds">"</span>foo<span class="pl-pds">"</span></span>, <span class="pl-c1">null</span>);
    <span class="pl-k">return</span> <span class="pl-c1">C</span>;
}());</pre></div>
<h2>tslib 中装饰器的实现</h2>
<p>其中 <code>__decorate</code> 为 TypeScript 经 tslib 提供的 Decorator 实现,其源码为:</p>
<p>tslib/tslib.js(经过格式化)</p>
<div class="highlight highlight-source-js"><pre><span class="pl-k">var</span> __decorate <span class="pl-k">=</span>
(<span class="pl-c1">this</span> <span class="pl-k">&amp;&amp;</span> <span class="pl-c1">this</span>.<span class="pl-smi">__decorate</span>) <span class="pl-k">||</span>
<span class="pl-k">function</span>(<span class="pl-smi">decorators</span>, <span class="pl-smi">target</span>, <span class="pl-smi">key</span>, <span class="pl-smi">desc</span>) {
    <span class="pl-k">var</span> c <span class="pl-k">=</span> <span class="pl-c1">arguments</span>.<span class="pl-c1">length</span>,
      r <span class="pl-k">=</span>
      c <span class="pl-k">&lt;</span> <span class="pl-c1">3</span>
          <span class="pl-k">?</span> target
          <span class="pl-k">:</span> desc <span class="pl-k">===</span> <span class="pl-c1">null</span>
          <span class="pl-k">?</span> (desc <span class="pl-k">=</span> <span class="pl-c1">Object</span>.<span class="pl-en">getOwnPropertyDescriptor</span>(target, key))
          <span class="pl-k">:</span> desc,
      d;
    <span class="pl-k">if</span> (<span class="pl-k">typeof</span> <span class="pl-c1">Reflect</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>object<span class="pl-pds">"</span></span> <span class="pl-k">&amp;&amp;</span> <span class="pl-k">typeof</span> <span class="pl-c1">Reflect</span>.<span class="pl-smi">decorate</span> <span class="pl-k">===</span> <span class="pl-s"><span class="pl-pds">"</span>function<span class="pl-pds">"</span></span>)
      r <span class="pl-k">=</span> <span class="pl-c1">Reflect</span>.<span class="pl-en">decorate</span>(decorators, target, key, desc);
    <span class="pl-k">else</span>
      <span class="pl-k">for</span> (<span class="pl-k">var</span> i <span class="pl-k">=</span> <span class="pl-smi">decorators</span>.<span class="pl-c1">length</span> <span class="pl-k">-</span> <span class="pl-c1">1</span>; i <span class="pl-k">&gt;=</span> <span class="pl-c1">0</span>; i<span class="pl-k">--</span>)
      <span class="pl-k">if</span> ((d <span class="pl-k">=</span> decorators))
          r <span class="pl-k">=</span> (c <span class="pl-k">&lt;</span> <span class="pl-c1">3</span> <span class="pl-k">?</span> <span class="pl-en">d</span>(r) <span class="pl-k">:</span> c <span class="pl-k">&gt;</span> <span class="pl-c1">3</span> <span class="pl-k">?</span> <span class="pl-en">d</span>(target, key, r) <span class="pl-k">:</span> <span class="pl-en">d</span>(target, key)) <span class="pl-k">||</span> r;
    <span class="pl-k">return</span> c <span class="pl-k">&gt;</span> <span class="pl-c1">3</span> <span class="pl-k">&amp;&amp;</span> r <span class="pl-k">&amp;&amp;</span> <span class="pl-c1">Object</span>.<span class="pl-en">defineProperty</span>(target, key, r), r;
};</pre></div>
<h2>装饰器的执行顺序</h2>
<p>配合编译后代码和这里装饰器的实现来看,进一步之前了解到的关于装饰器被求值和执行的顺序,</p>
<p>源码中应用装饰器的地方:</p>
<div class="highlight highlight-source-ts"><pre>@<span class="pl-en">f</span>()
@<span class="pl-en">g</span>()
<span class="pl-en">foo</span>(<span class="pl-smi">count</span>: <span class="pl-smi">number</span>) {
    <span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">`</span>foo called ${<span class="pl-smi">count</span>}<span class="pl-pds">`</span></span>);
}</pre></div>
<p>然后这里的 <code>@f() @g()</code> 按照该顺序传递给了 <code>__decorate</code> 函数,</p>
<div class="highlight highlight-source-diff"><pre>__decorate(
    [
<span class="pl-mi1"><span class="pl-mi1">+</span>      f(),</span>
<span class="pl-mi1"><span class="pl-mi1">+</span>      g(),</span>
      __metadata("design:type", Function),
      __metadata("design:paramtypes", ),
      __metadata("design:returntype", void 0)
    ],
    C.prototype,
    "foo",
    null
);</pre></div>
<p>然后在 <code>__decorate</code> 函数体中,对传入的 <code>decorators</code> 从数据最后开始,取出装饰器函数顺次执行,</p>
<div class="highlight highlight-source-diff"><pre>var __decorate =
(this &amp;&amp; this.__decorate) ||
function(decorators, target, key, desc) {
    var c = arguments.length,
      r =
      c &lt; 3
          ? target
          : desc === null
          ? (desc = Object.getOwnPropertyDescriptor(target, key))
          : desc,
      d;
    if (typeof Reflect === "object" &amp;&amp; typeof Reflect.decorate === "function")
      r = Reflect.decorate(decorators, target, key, desc);
    else
<span class="pl-mi1"><span class="pl-mi1">+</span>      for (var i = decorators.length - 1; i &gt;= 0; i--)</span>
      if ((d = decorators))
          r = (c &lt; 3 ? d(r) : c &gt; 3 ? d(target, key, r) : d(target, key)) || r;
    return c &gt; 3 &amp;&amp; r &amp;&amp; Object.defineProperty(target, key, r), r;
};</pre></div>
<p>其中 <code>r</code> 便是装成器的返回,会被当作被装饰对象的新的属性描述器(Property Descriptor)来重新定义被装饰的对象:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-c1">Object</span>.<span class="pl-en">defineProperty</span>(target, key, r)</pre></div>
<p>所以,像示例代码中多个装饰器均对被装饰对象有修改,原则上和多次调用 <code>Object.defineProperty()</code> 相当。</p>
<h2><code>Object.defineProperty()</code></h2>
<p>而调用 <code>Object.defineProperty()</code> 的结果是后面的会覆盖前面的,比如来看这里一个简单的示例:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-k">const</span> <span class="pl-c1">obj</span> <span class="pl-k">=</span> {};
<p><span class="pl-c1">Object</span>.<span class="pl-en">defineProperty</span>(obj, <span class="pl-s"><span class="pl-pds">"</span>foo<span class="pl-pds">"</span></span>, {<br>
configurable<span class="pl-k">:</span> <span class="pl-c1">true</span>,<br>
<span class="pl-en">value</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {<br>
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>1<span class="pl-pds">"</span></span>);<br>
}<br>
});</p>
<p><span class="pl-c1">Object</span>.<span class="pl-en">defineProperty</span>(obj, <span class="pl-s"><span class="pl-pds">"</span>foo<span class="pl-pds">"</span></span>, {<br>
<span class="pl-en">value</span><span class="pl-k">:</span> <span class="pl-k">function</span>() {<br>
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>2<span class="pl-pds">"</span></span>);<br>
}<br>
});</p>
<p><span class="pl-smi">obj</span>.<span class="pl-en">foo</span>(); <span class="pl-c"><span class="pl-c">//</span> 2</span></p></pre></div><p></p>
<p><strong>注意:</strong> 根据 MDN 对 <code>defineProperty</code> 的描述,<code>configurable</code> 在缺省时为 <code>false</code>,所以如果要重复定义同一个 <code>key</code>,需要显式将其置为 <code>true</code>。</p>
<blockquote>
<p><code>configurable</code></p>
<p><code>true</code> if and only if the type of this property descriptor may be changed and if the &gt; property may be deleted from the corresponding object.<br>
Defaults to <code>false</code>.</p>
</blockquote>
<p>回到本文开头的示例,为了进一步验证,可通过将运用装饰之后的属性描述器打印出来:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-c1">Object</span>.<span class="pl-en">getOwnPropertyDescriptor</span>(<span class="pl-c1">C</span>.<span class="pl-c1">prototype</span>, <span class="pl-s"><span class="pl-pds">"</span>foo<span class="pl-pds">"</span></span>).<span class="pl-c1">value</span>.<span class="pl-c1">toString</span>());</pre></div>
<p>输出结果为:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-k">function</span> <span class="pl-en">()</span> {
            var args = []<span class="pl-k">;</span>
            <span class="pl-k">for</span> (var _i = 0<span class="pl-k">;</span> _i <span class="pl-k">&lt;</span> arguments.length<span class="pl-k">;</span> _i++) {
                args = arguments<span class="pl-k">;</span>
            }
            console.log(<span class="pl-s"><span class="pl-pds">"</span>before <span class="pl-pds">"</span></span> + key + <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args)<span class="pl-k">;</span>
            var result = original.apply(this, args)<span class="pl-k">;</span>
            console.log(<span class="pl-s"><span class="pl-pds">"</span>after <span class="pl-pds">"</span></span> + key + <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args)<span class="pl-k">;</span>
            <span class="pl-k">return</span> result<span class="pl-k">;</span>
      }</pre></div>
<p>那么这里引出另一个问题,通过装饰器重复定义同一属性时,并没有显式返回一个 <code>configurable:true</code> 的对象,那为何在运用多个装饰器重复定义时没报错。</p>
<h2>装饰器入参中的 <code>descriptor</code></h2>
<p>答案就只有一个,那就是装饰器传入的 <code>descriptor</code> 已经是 <code>configurable</code> 为 <code>true</code> 的状态。</p>
<p>为了验证,只需要在 <code>@f()</code> 或 <code>@g()</code> 任意一个装饰器中将 <code>descriptor</code> 打印出来即可。</p>
<div class="highlight highlight-source-diff"><pre>function g() {
console.log("g(): evaluated");
return function(_target: any, key: string, descriptor: PropertyDescriptor) {
<span class="pl-mi1"><span class="pl-mi1">+</span>      console.log(descriptor)</span>
    const original = descriptor.value;
    descriptor.value = function(...args: any[]) {
      console.log(`before ${key} called`, args);
      const result = original.apply(this, args);
      console.log(`after ${key} called`, args);
      return result;
    };
    console.log("g(): called");
    return descriptor;
};
}</pre></div>
<p>输出的 <code>descriptor</code>:</p>
<div class="highlight highlight-source-shell"><pre>{
value: ,
writable: true,
enumerable: true,
configurable: <span class="pl-c1">true</span>
}</pre></div>
<p>这便是最终运行时会执行的 <code>foo</code> 方法真身。</p>
<p>可以看到确实是最后生效的装饰器确实是后运用的 <code>@f()</code>。因此你确实可以这么理解多个装饰器的重叠应用为,那一切都还说得通,就是 后运用的装饰器中 对被装饰对象的替换 会覆盖掉 先运用的装饰器 对被装饰对象的替换。</p>
<p>But,</p>
<p>这解释不了它的输出结果:</p>
<div class="highlight highlight-source-shell"><pre><span class="pl-en">f</span>(): evaluated
<span class="pl-en">g</span>(): evaluated
<span class="pl-en">g</span>(): called
<span class="pl-en">f</span>(): called
before foo called [ 0 ]
before foo called [ 0 ]
foo called 0
after foo called
after foo called
before foo called [ 1 ]
before foo called [ 1 ]
foo called 1
after foo called
after foo called</pre></div>
<h2>装饰器嵌套</h2>
<p>原因就在于这句代码:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">original</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, <span class="pl-smi">args</span>);</pre></div>
<p>因为这句,<code>@f()</code> 和 <code>@g()</code> 便不是简单的覆盖关系,而是形成了嵌套关系。</p>
<p>这里 <code>original</code> 为 <code>descriptor.value</code>,即装饰器传入的 <code>descriptor</code> 的一个副本。我们在进行覆盖前保存了一下原方法的副本,</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-c"><span class="pl-c">//</span> 保存原始的被装饰对象</span>
<span class="pl-k"><span class="pl-k">const</span></span> original <span class="pl-k">=</span> <span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span>;
<p><span class="pl-c"><span class="pl-c">//</span> 替换被装饰对象</span><br>
<span class="pl-smi">descriptor</span>.<span class="pl-c1">value</span> <span class="pl-k">=</span> <span class="pl-k">function</span>(<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-c1">any</span>[]) {<br>
<span class="pl-c"><span class="pl-c">//</span> ...</span><br>
}</p></pre></div><p></p>
<p>因为装饰器的目的只是<strong>对已有的对象进行修饰加强</strong>,所以你不能粗暴地将原始的对象直接替换成新的实现(当然你确实可以那样粗暴的),那样并不符合大多数应用场景。所以在进行替换时,先保存原始对象(这里原始对象是 <code>foo</code> 方法),然后在新的实现中对原始对象再进行调用,这样来实现了对原始对象进行修饰,添加新的特性。</p>
<div class="highlight highlight-source-diff"><pre>descriptor.value = function(...args: any[]) {
    console.log(`before ${key} called`, args);
<span class="pl-mi1"><span class="pl-mi1">+</span>    const result = original.apply(this, args);</span>
    console.log(`after ${key} called`, args);
    return result;
};</pre></div>
<p>通过这种方式,多个装饰器对被装饰对象的修改可以层层传递下去,而不至于丢失。</p>
<p>下面把每个装饰器接收到的属性描述器打印出来:</p>
<div class="highlight highlight-source-diff"><pre>function f() {
console.log("f(): evaluated");
return function(_target: any, key: string, descriptor: PropertyDescriptor) {
    const original = descriptor.value;
<span class="pl-mi1"><span class="pl-mi1">+</span>    console.log(" receive descriptor:", original.toString());</span>
    descriptor.value = function(...args: any[]) {
      console.log(`before ${key} called`, args);
      const result = original.apply(this, args);
      console.log(`after ${key} called`, args);
      return result;
    };
    console.log("f(): called");
    return descriptor;
};
}
<p>function g() {<br>
console.log("g(): evaluated");<br>
return function(_target: any, key: string, descriptor: PropertyDescriptor) {<br>
const original = descriptor.value;<br>
<span class="pl-mi1"><span class="pl-mi1">+</span>    console.log(" receive descriptor:", original.toString());</span><br>
descriptor.value = function(...args: any[]) {<br>
console.log(<code>before ${key} called</code>, args);<br>
const result = original.apply(this, args);<br>
console.log(<code>after ${key} called</code>, args);<br>
return result;<br>
};<br>
console.log("g(): called");<br>
return descriptor;<br>
};<br>
}</p></pre></div><p></p>
<p>输出结果:</p>
<div class="highlight highlight-source-shell"><pre> receive descriptor:
<span class="pl-k">function</span> <span class="pl-en">(count)</span> {
      console.log(<span class="pl-s"><span class="pl-pds">"</span>foo called <span class="pl-pds">"</span></span> + count)<span class="pl-k">;</span>
    }
<p> receive descriptor:<br>
<span class="pl-k">function</span> <span class="pl-en">()</span> {<br>
var args = []<span class="pl-k">;</span><br>
<span class="pl-k">for</span> (var _i = 0<span class="pl-k">;</span> _i <span class="pl-k">&lt;</span> arguments.length<span class="pl-k">;</span> _i++) {<br>
args = arguments<span class="pl-k">;</span><br>
}<br>
console.log(<span class="pl-s"><span class="pl-pds">"</span>before <span class="pl-pds">"</span></span> + key + <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args)<span class="pl-k">;</span><br>
var result = original.apply(this, args)<span class="pl-k">;</span><br>
console.log(<span class="pl-s"><span class="pl-pds">"</span>after <span class="pl-pds">"</span></span> + key + <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args)<span class="pl-k">;</span><br>
<span class="pl-k">return</span> result<span class="pl-k">;</span><br>
}</p></pre></div><p></p>
<p>这里的示例中,先是 <code>@g()</code> 被调用,它接收到的 <code>descriptor</code> 就是原始的 <code>foo</code> 方法的属性描述器,打印出其值便是原始的 <code>foo</code> 方法的方法体,</p>
<div class="highlight highlight-source-js"><pre><span class="pl-k">function</span> (<span class="pl-smi">count</span>) {
      <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>foo called <span class="pl-pds">"</span></span> <span class="pl-k">+</span> count);
    }</pre></div>
<p>经过 <code>@g()</code> 处理后的属性描述器传递给了下一个装饰器 <code>@f()</code>,所以后者接收到的是经过处理后新的属性描述器,即 <code>@g()</code> 返回的那个:</p>
<div class="highlight highlight-source-js"><pre> <span class="pl-k">function</span> () {
            <span class="pl-k">var</span> args <span class="pl-k">=</span> [];
            <span class="pl-k">for</span> (<span class="pl-k">var</span> _i <span class="pl-k">=</span> <span class="pl-c1">0</span>; _i <span class="pl-k">&lt;</span> <span class="pl-c1">arguments</span>.<span class="pl-c1">length</span>; _i<span class="pl-k">++</span>) {
                args <span class="pl-k">=</span> <span class="pl-c1">arguments</span>;
            }
            <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>before <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);
            <span class="pl-k">var</span> result <span class="pl-k">=</span> <span class="pl-smi">original</span>.<span class="pl-c1">apply</span>(<span class="pl-c1">this</span>, args);
            <span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>after <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);
            <span class="pl-k">return</span> result;
      }</pre></div>
<p>然后将 <code>@f()</code> 中 <code>original</code> 替换成上述代码便是最终 <code>@f()</code> 返回的最终 <code>foo</code> 的样子,大致是这样的:</p>
<div class="highlight highlight-source-js"><pre><span class="pl-smi">descriptor</span>.<span class="pl-en">value</span> <span class="pl-k">=</span> <span class="pl-k">function</span>(<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span> <span class="pl-smi">any</span>[]) {
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">`</span>before <span class="pl-s1"><span class="pl-pse">${</span>key<span class="pl-pse">}</span></span> called<span class="pl-pds">`</span></span>, args);
<p><span class="pl-c"><span class="pl-c">//</span> g 开始</span><br>
<span class="pl-k">var</span> args <span class="pl-k">=</span> [];<br>
<span class="pl-k">for</span> (<span class="pl-k">var</span> _i <span class="pl-k">=</span> <span class="pl-c1">0</span>; _i <span class="pl-k">&lt;</span> <span class="pl-c1">arguments</span>.<span class="pl-c1">length</span>; _i<span class="pl-k">++</span>) {<br>
args <span class="pl-k">=</span> <span class="pl-c1">arguments</span>;<br>
}<br>
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>before <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);</p>
<p><span class="pl-c"><span class="pl-c">//</span> foo 开始</span><br>
<span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds"><code>&lt;/span&gt;foo called &lt;span class="pl-s1"&gt;&lt;span class="pl-pse"&gt;${&lt;/span&gt;count&lt;span class="pl-pse"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class="pl-pds"&gt;</code></span></span>);<br>
<span class="pl-c"><span class="pl-c">//</span> foo 结束</span></p>
<p><span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds">"</span>after <span class="pl-pds">"</span></span> <span class="pl-k">+</span> key <span class="pl-k">+</span> <span class="pl-s"><span class="pl-pds">"</span> called<span class="pl-pds">"</span></span>, args);<br>
<span class="pl-c"><span class="pl-c">//</span> g 结束</span></p>
<p><span class="pl-en">console</span>.<span class="pl-c1">log</span>(<span class="pl-s"><span class="pl-pds"><code>&lt;/span&gt;after &lt;span class="pl-s1"&gt;&lt;span class="pl-pse"&gt;${&lt;/span&gt;key&lt;span class="pl-pse"&gt;}&lt;/span&gt;&lt;/span&gt; called&lt;span class="pl-pds"&gt;</code></span></span>, args);<br>
<span class="pl-k">return</span> result;<br>
};</p></pre></div><p></p>
<p>所以最终的 <code>foo</code> 方法其实是 <code>f(g(x))</code> 两者嵌套组合的结果,像数学上的函数调用一样。</p>
<h2>总结</h2>
<p>多个装饰器运用于同一对象时,其求值和执行顺序是相反的,</p>
<p>对于类似这样的调用:</p>
<div class="highlight highlight-source-ts"><pre>@<span class="pl-smi">f</span>
@<span class="pl-smi">g</span>
<span class="pl-smi">x</span></pre></div>
<ul>
<li>求值顺序是由上往下</li>
<li>执行顺序是由下往上</li>
</ul>
<p>通常情况下我们只关心执行顺序,除非是在编写复杂的装饰器工厂方法时。同时需要注意到,这里所指的装饰器<strong>执行顺序</strong> 是装饰器本身被调用的顺序,如果是装饰方法,这和 <code>descriptor.value</code> 被执行的顺序是两码事,后者的执行是层层嵌套的方式,联想 Koa 中间件的<strong>洋葱圈</strong>模型。</p>
<p>如果多个装饰器中都对被装饰对象有所修改,注意嵌套过程中修改被覆盖的问题,如果不想要产生覆盖,装饰器中应该有对被装饰对象保存副本并且调用,方法通过 <code>fn.apply()</code>,类则可通过返回一个新的但继承自被装饰对象的新类来实现,比如:</p>
<div class="highlight highlight-source-ts"><pre><span class="pl-k">function</span> classDecorator&lt;<span class="pl-en">T</span> <span class="pl-k">extends</span> {<span class="pl-k">new</span>(<span class="pl-k">...</span><span class="pl-v">args</span><span class="pl-k">:</span><span class="pl-c1">any</span>[])<span class="pl-k">:</span>{}}&gt;(<span class="pl-v">constructor</span><span class="pl-k">:</span><span class="pl-en">T</span>) {
    <span class="pl-k">return</span> <span class="pl-k">class</span> <span class="pl-k">extends</span> <span class="pl-e">constructor</span> {
      newProperty <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>new property<span class="pl-pds">"</span></span>;
      hello <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>override<span class="pl-pds">"</span></span>;
    }
}
<p>@<span class="pl-smi">classDecorator</span><br>
<span class="pl-k">class</span> <span class="pl-en">Greeter</span> {<br>
property <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>property<span class="pl-pds">"</span></span>;<br>
hello<span class="pl-k">:</span> <span class="pl-c1">string</span>;<br>
<span class="pl-k">constructor</span>(<span class="pl-v">m</span><span class="pl-k">:</span> <span class="pl-c1">string</span>) {<br>
<span class="pl-c1">this</span>.<span class="pl-smi">hello</span> <span class="pl-k">=</span> <span class="pl-smi">m</span>;<br>
}<br>
}</p>
<p><span class="pl-c1">console</span>.<span class="pl-c1">log</span>(<span class="pl-k">new</span> <span class="pl-en">Greeter</span>(<span class="pl-s"><span class="pl-pds">"</span>world<span class="pl-pds">"</span></span>));</p></pre></div><p></p>
<p>这里覆盖了被装饰类的构造器,但其他未修改的部分仍是原来类中的样子,因为这里返回的是一个 <code>extends</code> 后的新类。</p>
      </td>
    </tr>
</tbody>
</table>

</div>
<div id="MySignature" role="contentinfo">
    <div>
<img src="https://licensebuttons.net/l/by-nc-sa/3.0/88x31.png" style="vertical-align: middle">
<strong>CC BY-NC-SA 署名-非商业性使用-相同方式共享</strong>
</div><br><br>
来源:https://www.cnblogs.com/Wayou/p/typescript_decorator_under_the_hook.html
頁: [1]
查看完整版本: TypeScript 装饰器的执行原理