TypeScript声明合并
<h3>介绍</h3><p>TypeScript中有些独特的概念可以在类型层面上描述JavaScript对象的模型。 这其中尤其独特的一个例子是“声明合并”的概念。 理解了这个概念,将有助于操作现有的JavaScript代码。 同时,也会有助于理解更多高级抽象的概念。</p>
<p>对本文件来讲,“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被合并;不局限于两个声明。</p>
<h1 id="基础概念">基础概念</h1>
<p>TypeScript中的声明会创建以下三种实体之一:命名空间,类型或值。 创建命名空间的声明会新建一个命名空间,它包含了用(.)符号来访问时使用的名字。 创建类型的声明是:用声明的模型创建一个类型并绑定到给定的名字上。 最后,创建值的声明会创建在JavaScript输出中看到的值。</p>
<table>
<thead>
<tr><th>Declaration Type</th><th>Namespace</th><th>Type</th><th>Value</th></tr>
</thead>
<tbody>
<tr>
<td>Namespace</td>
<td>X</td>
<td> </td>
<td>X</td>
</tr>
<tr>
<td>Class</td>
<td> </td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td>Enum</td>
<td> </td>
<td>X</td>
<td>X</td>
</tr>
<tr>
<td>Interface</td>
<td> </td>
<td>X</td>
<td> </td>
</tr>
<tr>
<td>Type Alias</td>
<td> </td>
<td>X</td>
<td> </td>
</tr>
<tr>
<td>Function</td>
<td> </td>
<td> </td>
<td>X</td>
</tr>
<tr>
<td>Variable</td>
<td> </td>
<td> </td>
<td>X</td>
</tr>
</tbody>
</table>
<p>理解每个声明创建了什么,有助于理解当声明合并时有哪些东西被合并了。</p>
<h1 id="合并接口">合并接口</h1>
<p>最简单也最常见的声明合并类型是接口合并。 从根本上说,合并的机制是把双方的成员放到一个同名的接口里。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Box <span class="p">{
<span class="nl">height<span class="p">: <span class="kr">number<span class="p">;
<span class="nl">width<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Box <span class="p">{
<span class="nl">scale<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kd">let <span class="nx">box<span class="err">: <span class="nx">Box <span class="o">= <span class="p">{<span class="na">height<span class="p">: <span class="mi">5<span class="p">, <span class="na">width<span class="p">: <span class="mi">6<span class="p">, <span class="na">scale<span class="p">: <span class="mi">10<span class="p">};
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>接口的非函数的成员应该是唯一的。 如果它们不是唯一的,那么它们必须是相同的类型。 如果两个接口中同时声明了同名的非函数成员且它们的类型不同,则编译器会报错。</p>
<p>对于函数成员,每个同名函数声明都会被当成这个函数的一个重载。 同时需要注意,当接口<code class="highlighter-rouge">A</code>与后来的接口<code class="highlighter-rouge">A</code>合并时,后面的接口具有更高的优先级。</p>
<p>如下例所示:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Cloner <span class="p">{
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Animal<span class="p">)<span class="err">: <span class="nx">Animal<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Cloner <span class="p">{
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Sheep<span class="p">)<span class="err">: <span class="nx">Sheep<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Cloner <span class="p">{
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Dog<span class="p">)<span class="err">: <span class="nx">Dog<span class="p">;
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Cat<span class="p">)<span class="err">: <span class="nx">Cat<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>这三个接口合并成一个声明:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Cloner <span class="p">{
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Dog<span class="p">)<span class="err">: <span class="nx">Dog<span class="p">;
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Cat<span class="p">)<span class="err">: <span class="nx">Cat<span class="p">;
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Sheep<span class="p">)<span class="err">: <span class="nx">Sheep<span class="p">;
<span class="nx">clone<span class="p">(<span class="nx">animal<span class="err">: <span class="nx">Animal<span class="p">)<span class="err">: <span class="nx">Animal<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>注意每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。</p>
<p>这个规则有一个例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是<em>单一</em>的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端。</p>
<p>比如,下面的接口会合并到一起:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Document <span class="p">{
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="kr">any<span class="p">)<span class="err">: <span class="nx">Element<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Document <span class="p">{
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"div"<span class="p">)<span class="err">: <span class="nx">HTMLDivElement<span class="p">;
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"span"<span class="p">)<span class="err">: <span class="nx">HTMLSpanElement<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Document <span class="p">{
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="kr">string<span class="p">)<span class="err">: <span class="nx">HTMLElement<span class="p">;
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"canvas"<span class="p">)<span class="err">: <span class="nx">HTMLCanvasElement<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>合并后的<code class="highlighter-rouge">Document</code>将会像下面这样:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Document <span class="p">{
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"canvas"<span class="p">)<span class="err">: <span class="nx">HTMLCanvasElement<span class="p">;
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"div"<span class="p">)<span class="err">: <span class="nx">HTMLDivElement<span class="p">;
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"span"<span class="p">)<span class="err">: <span class="nx">HTMLSpanElement<span class="p">;
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="kr">string<span class="p">)<span class="err">: <span class="nx">HTMLElement<span class="p">;
<span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="kr">any<span class="p">)<span class="err">: <span class="nx">Element<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h1 id="合并命名空间">合并命名空间</h1>
<p>与接口相似,同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值,我们需要知道这两者都是怎么合并的。</p>
<p>对于命名空间的合并,模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。</p>
<p>对于命名空间里值的合并,如果当前已经存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块里。</p>
<p><code class="highlighter-rouge">Animals</code>声明合并示例:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="k">namespace <span class="nx">Animals <span class="p">{
<span class="k">export <span class="kr">class <span class="nx">Zebra <span class="p">{ <span class="p">}
<span class="p">}
<span class="k">namespace <span class="nx">Animals <span class="p">{
<span class="k">export <span class="kr">interface <span class="nx">Legged <span class="p">{ <span class="nl">numberOfLegs<span class="p">: <span class="kr">number<span class="p">; <span class="p">}
<span class="k">export <span class="kr">class <span class="nx">Dog <span class="p">{ <span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>等同于:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="k">namespace <span class="nx">Animals <span class="p">{
<span class="k">export <span class="kr">interface <span class="nx">Legged <span class="p">{ <span class="nl">numberOfLegs<span class="p">: <span class="kr">number<span class="p">; <span class="p">}
<span class="k">export <span class="kr">class <span class="nx">Zebra <span class="p">{ <span class="p">}
<span class="k">export <span class="kr">class <span class="nx">Dog <span class="p">{ <span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>除了这些合并外,你还需要了解非导出成员是如何处理的。 非导出成员仅在其原有的(合并前的)命名空间内可见。这就是说合并之后,从其它命名空间合并进来的成员无法访问非导出成员。</p>
<p>下例提供了更清晰的说明:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="k">namespace <span class="nx">Animal <span class="p">{
<span class="kd">let <span class="nx">haveMuscles <span class="o">= <span class="kc">true<span class="p">;
<span class="k">export <span class="kd">function <span class="nx">animalsHaveMuscles<span class="p">() <span class="p">{
<span class="k">return <span class="nx">haveMuscles<span class="p">;
<span class="p">}
<span class="p">}
<span class="k">namespace <span class="nx">Animal <span class="p">{
<span class="k">export <span class="kd">function <span class="nx">doAnimalsHaveMuscles<span class="p">() <span class="p">{
<span class="k">return <span class="nx">haveMuscles<span class="p">;<span class="c1">// <-- error, haveMuscles is not visible here
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>因为<code class="highlighter-rouge">haveMuscles</code>并没有导出,只有<code class="highlighter-rouge">animalsHaveMuscles</code>函数共享了原始未合并的命名空间可以访问这个变量。 <code class="highlighter-rouge">doAnimalsHaveMuscles</code>函数虽是合并命名空间的一部分,但是访问不了未导出的成员。</p>
<h1 id="命名空间与类和函数和枚举类型合并">命名空间与类和函数和枚举类型合并</h1>
<p>命名空间可以与其它类型的声明进行合并。 只要命名空间的定义符合将要合并类型的定义。合并结果包含两者的声明类型。 TypeScript使用这个功能去实现一些JavaScript里的设计模式。</p>
<h2 id="合并命名空间和类">合并命名空间和类</h2>
<p>这让我们可以表示内部类。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">class <span class="nx">Album <span class="p">{
<span class="nl">label<span class="p">: <span class="nx">Album<span class="p">.<span class="nx">AlbumLabel<span class="p">;
<span class="p">}
<span class="k">namespace <span class="nx">Album <span class="p">{
<span class="k">export <span class="kr">class <span class="nx">AlbumLabel <span class="p">{ <span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>合并规则与上面<code class="highlighter-rouge">合并命名空间</code>小节里讲的规则一致,我们必须导出<code class="highlighter-rouge">AlbumLabel</code>类,好让合并的类能访问。 合并结果是一个类并带有一个内部类。 你也可以使用命名空间为类增加一些静态属性。</p>
<p>除了内部类的模式,你在JavaScript里,创建一个函数稍后扩展它增加一些属性也是很常见的。 TypeScript使用声明合并来达到这个目的并保证类型安全。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">buildLabel<span class="p">(<span class="nx">name<span class="err">: <span class="kr">string<span class="p">)<span class="err">: <span class="kr">string <span class="p">{
<span class="k">return <span class="nx">buildLabel<span class="p">.<span class="nx">prefix <span class="o">+ <span class="nx">name <span class="o">+ <span class="nx">buildLabel<span class="p">.<span class="nx">suffix<span class="p">;
<span class="p">}
<span class="k">namespace <span class="nx">buildLabel <span class="p">{
<span class="k">export <span class="kd">let <span class="nx">suffix <span class="o">= <span class="s2">""<span class="p">;
<span class="k">export <span class="kd">let <span class="nx">prefix <span class="o">= <span class="s2">"Hello, "<span class="p">;
<span class="p">}
<span class="nx">alert<span class="p">(<span class="nx">buildLabel<span class="p">(<span class="s2">"Sam Smith"<span class="p">));
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>相似的,命名空间可以用来扩展枚举型:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">enum <span class="nx">Color <span class="p">{
<span class="nx">red <span class="o">= <span class="mi">1<span class="p">,
<span class="nx">green <span class="o">= <span class="mi">2<span class="p">,
<span class="nx">blue <span class="o">= <span class="mi">4
<span class="p">}
<span class="k">namespace <span class="nx">Color <span class="p">{
<span class="k">export <span class="kd">function <span class="nx">mixColor<span class="p">(<span class="nx">colorName<span class="err">: <span class="kr">string<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="nx">colorName <span class="o">== <span class="s2">"yellow"<span class="p">) <span class="p">{
<span class="k">return <span class="nx">Color<span class="p">.<span class="nx">red <span class="o">+ <span class="nx">Color<span class="p">.<span class="nx">green<span class="p">;
<span class="p">}
<span class="k">else <span class="k">if <span class="p">(<span class="nx">colorName <span class="o">== <span class="s2">"white"<span class="p">) <span class="p">{
<span class="k">return <span class="nx">Color<span class="p">.<span class="nx">red <span class="o">+ <span class="nx">Color<span class="p">.<span class="nx">green <span class="o">+ <span class="nx">Color<span class="p">.<span class="nx">blue<span class="p">;
<span class="p">}
<span class="k">else <span class="k">if <span class="p">(<span class="nx">colorName <span class="o">== <span class="s2">"magenta"<span class="p">) <span class="p">{
<span class="k">return <span class="nx">Color<span class="p">.<span class="nx">red <span class="o">+ <span class="nx">Color<span class="p">.<span class="nx">blue<span class="p">;
<span class="p">}
<span class="k">else <span class="k">if <span class="p">(<span class="nx">colorName <span class="o">== <span class="s2">"cyan"<span class="p">) <span class="p">{
<span class="k">return <span class="nx">Color<span class="p">.<span class="nx">green <span class="o">+ <span class="nx">Color<span class="p">.<span class="nx">blue<span class="p">;
<span class="p">}
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h1 id="非法的合并">非法的合并</h1>
<p>TypeScript并非允许所有的合并。 目前,类不能与其它类或变量合并。 想要了解如何模仿类的合并,请参考TypeScript的混入。</p>
<h1 id="模块扩展">模块扩展</h1>
<p>虽然JavaScript不支持合并,但你可以为导入的对象打补丁以更新它们。让我们考察一下这个玩具性的示例:</p>
<div class="language-js highlighter-rouge">
<pre class="highlight"><code><span class="c1">// observable.js
<span class="kr">export <span class="kr">class <span class="nx">Observable<span class="o"><<span class="nx">T<span class="o">> <span class="p">{
<span class="c1">// ... implementation left as an exercise for the reader ...
<span class="p">}
<span class="c1">// map.js
<span class="kr">import <span class="p">{ <span class="nx">Observable <span class="p">} <span class="nx">from <span class="s2">"./observable"<span class="p">;
<span class="nx">Observable<span class="p">.<span class="nx">prototype<span class="p">.<span class="nx">map <span class="o">= <span class="kd">function <span class="p">(<span class="nx">f<span class="p">) <span class="p">{
<span class="c1">// ... another exercise for the reader
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>它也可以很好地工作在TypeScript中, 但编译器对 <code class="highlighter-rouge">Observable.prototype.map</code>一无所知。 你可以使用扩展模块来将它告诉编译器:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="c1">// observable.ts stays the same
<span class="c1">// map.ts
<span class="k">import <span class="p">{ <span class="nx">Observable <span class="p">} <span class="k">from <span class="s2">"./observable"<span class="p">;
<span class="kr">declare <span class="kr">module <span class="s2">"./observable" <span class="p">{
<span class="kr">interface <span class="nx">Observable<span class="o"><<span class="nx">T<span class="o">> <span class="p">{
<span class="nx">map<span class="o"><<span class="nx">U<span class="o">><span class="p">(<span class="na">f<span class="p">: <span class="p">(<span class="na">x<span class="p">: <span class="nx">T<span class="p">) <span class="o">=> <span class="nx">U<span class="p">): <span class="nx">Observable<span class="o"><<span class="nx">U<span class="o">><span class="p">;
<span class="p">}
<span class="p">}
<span class="nx">Observable<span class="p">.<span class="nx">prototype<span class="p">.<span class="nx">map <span class="o">= <span class="kd">function <span class="p">(<span class="nx">f<span class="p">) <span class="p">{
<span class="c1">// ... another exercise for the reader
<span class="p">}
<span class="c1">// consumer.ts
<span class="k">import <span class="p">{ <span class="nx">Observable <span class="p">} <span class="k">from <span class="s2">"./observable"<span class="p">;
<span class="k">import <span class="s2">"./map"<span class="p">;
<span class="kd">let <span class="na">o<span class="p">: <span class="nx">Observable<span class="o"><<span class="kr">number<span class="o">><span class="p">;
<span class="nx">o<span class="p">.<span class="nx">map<span class="p">(<span class="nx">x <span class="o">=> <span class="nx">x<span class="p">.<span class="nx">toFixed<span class="p">());
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>模块名的解析和用<code class="highlighter-rouge">import</code>/<code class="highlighter-rouge">export</code>解析模块标识符的方式是一致的。 更多信息请参考 Modules。 当这些声明在扩展中合并时,就好像在原始位置被声明了一样。 但是,你不能在扩展中声明新的顶级声明-仅可以扩展模块中已经存在的声明。</p>
<h2 id="全局扩展">全局扩展</h2>
<p>你也以在模块内部添加声明到全局作用域中。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="c1">// observable.ts
<span class="k">export <span class="kr">class <span class="nx">Observable<span class="o"><<span class="nx">T<span class="o">> <span class="p">{
<span class="c1">// ... still no implementation ...
<span class="p">}
<span class="kr">declare <span class="nx">global <span class="p">{
<span class="kr">interface <span class="nb">Array<span class="o"><<span class="nx">T<span class="o">> <span class="p">{
<span class="nx">toObservable<span class="p">(): <span class="nx">Observable<span class="o"><<span class="nx">T<span class="o">><span class="p">;
<span class="p">}
<span class="p">}
<span class="nb">Array<span class="p">.<span class="nx">prototype<span class="p">.<span class="nx">toObservable <span class="o">= <span class="kd">function <span class="p">() <span class="p">{
<span class="c1">// ...
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>全局扩展与模块扩展的行为和限制是相同的。</p><br><br>
来源:https://www.cnblogs.com/xiewangfei123/p/12233786.html
頁:
[1]