2019-04-21 11:50:48 +08:00

286 lines
24 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<article id="wikiArticle">
<div></div>
<p><code><strong><span style="font-family: courier new,andale mono,monospace; line-height: 1.5;">bind()</span></strong></code>方法创建一个新的函数,在调用时设置<code>this</code>关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。</p>
<div><iframe class="interactive interactive-js taller" frameborder="0" height="250" src="https://interactive-examples.mdn.mozilla.net/pages/js/function-bind.html" width="100%"></iframe></div>
<p class="hidden">The source for this interactive example is stored in a GitHub repository. If you'd like to contribute to the interactive examples project, please clone <a class="external" href="https://github.com/mdn/interactive-examples" rel="noopener">https://github.com/mdn/interactive-examples</a> and send us a pull request.</p>
<h2 id="语法">语法</h2>
<pre><code class="language-javascript"><code><var>function</var>.bind(<var>thisArg</var>[, <var>arg1</var>[, <var>arg2</var>[, ...]]])</code></code></pre>
<h3 id="参数">参数</h3>
<dl>
<dt><code>thisArg</code></dt>
<dd>调用绑定函数时作为<code>this</code>参数传递给目标函数的值。 如果使用<a href="Reference/Operators/new" title="new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。"><code>new</code></a>运算符构造绑定函数,则忽略该值。当使用<code>bind</code><code>setTimeout</code>中创建一个函数(作为回调提供)时,作为<code>thisArg</code>传递的任何原始值都将转换为<code>object</code>。如果<code>bind</code>函数的参数列表为空,执行作用域的<code>this</code>将被视为新函数的<code>thisArg</code></dd>
<dt><code>arg1, arg2, ...</code></dt>
<dd>当目标函数被调用时,预先添加到绑定函数的参数列表中的参数。</dd>
</dl>
<h3 id="返回值">返回值</h3>
<p>      返回一个原函数的拷贝,并拥有指定的<strong><code>this</code></strong>值和初始参数。</p>
<h2 id="Description" name="Description">描述</h2>
<div><strong><span style="font-family: courier new,andale mono,monospace; line-height: 1.5;">bind()</span></strong> 函数会创建一个新<strong>绑定函数bound functionBF</strong>。绑定函数是一个<strong>exotic function object</strong>(怪异函数对象,<strong>ECMAScript 2015</strong>中的术语),它包装了原函数对象。调用<strong>绑定函数</strong>通常会导致执行<strong>包装函数</strong><br/>
<strong>绑定函数</strong>具有以下内部属性:</div>
<div> </div>
<div>
<ul>
<li><strong>[[BoundTargetFunction]] </strong>- 包装的函数对象</li>
<li><strong>[[BoundThis]]</strong> - 在调用包装函数时始终作为<strong>this</strong>值传递的值。</li>
<li><strong>[[BoundArguments]]</strong> - 列表,在对包装函数做任何调用都会优先用列表元素填充参数列表。</li>
<li><strong>[[Call]]</strong> - 执行与此对象关联的代码。通过函数调用表达式调用。内部方法的参数是一个<strong>this</strong>值和一个包含通过调用表达式传递给函数的参数的列表。</li>
</ul>
<p>当调用绑定函数时,它调用<strong>[[BoundTargetFunction]]</strong>上的内部方法<strong>[[Call]]</strong>,就像这样<strong>Call(<em>boundThis</em>, <em>args</em>)</strong>。其中,<strong>boundThis</strong><strong>[[BoundThis]]</strong><strong>args</strong><strong>[[BoundArguments]]</strong>加上通过函数调用传入的参数列表。</p>
</div>
<div>
<p>绑定函数也可以使用<a href="/en-US/docs/Web/JavaScript/Reference/Operators/new" title="The new operator creates an instance of a user-defined object type or of one of the built-in object types that has a constructor function."><code>new</code></a>运算符构造,它会表现为目标函数已经被构建完毕了似的。提供的<strong>this</strong>值会被忽略,但前置参数仍会提供给模拟函数。</p>
</div>
<h2 id="示例">示例</h2>
<h3 id="创建绑定函数">创建绑定函数</h3>
<p><code>bind()</code> 最简单的用法是创建一个函数,不论怎么调用,这个函数都有同样的 <strong><code>this</code></strong> 值。JavaScript新手经常犯的一个错误是将一个方法从对象中拿出来然后再调用期望方法中的 <code>this</code> 是原来的对象(比如在回调中传入这个方法)。如果不做特殊处理的话,一般会丢失原来的对象。基于这个函数,用原始的对象创建一个绑定函数,巧妙地解决了这个问题:</p>
<pre><code class="language-javascript">this.x = 9; // 在浏览器中this指向全局的 "window" 对象
var module = {
x: 81,
getX: function() { return this.x; }
};
module.getX(); // 81
var retrieveX = module.getX;
retrieveX();
// 返回9 - 因为函数是在全局作用域中调用的
// 创建一个新函数,把 'this' 绑定到 module 对象
// 新手可能会将全局变量 x 与 module 的属性 x 混淆
var boundGetX = retrieveX.bind(module);
boundGetX(); // 81
</code></pre>
<h3 id="偏函数">偏函数</h3>
<p><code>bind()</code>的另一个最简单的用法是使一个函数拥有预设的初始参数。只要将这些参数(如果有的话)作为<code>bind()</code>的参数写在<code>this</code>后面。当绑定函数被调用时,这些参数会被插入到目标函数的参数列表的开始位置,传递给绑定函数的参数会跟在它们后面。</p>
<pre><code class="language-javascript">function list() {
return Array.prototype.slice.call(arguments);
}
function addArguments(arg1, arg2) {
    return arg1 + arg2
}
var list1 = list(1, 2, 3); // [1, 2, 3]
var result1 = addArguments(1, 2); // 3
// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);
// 创建一个函数,它拥有预设的第一个参数
var addThirtySeven = addArguments.bind(null, 37);
var list2 = leadingThirtysevenList();
// [37]
var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
var result2 = addThirtySeven(5);
// 37 + 5 = 42
var result3 = addThirtySeven(5, 10);
// 37 + 5 = 42 ,第二个参数被忽略
</code></pre>
<h3 id="配合_setTimeout">配合 <code>setTimeout</code></h3>
<p>在默认情况下,使用 <a href="/zh-CN/docs/Web/API/Window/setTimeout" title="WindowOrWorkerGlobalScope 混合的 setTimeout()方法设置一个定时器,该定时器在定时器到期后执行一个函数或指定的一段代码。"><code>window.setTimeout()</code></a> 时,<code>this</code> 关键字会指向 <a href="/zh-CN/docs/Web/API/Window" title="The window object represents a window containing a DOM document; the document property points to the DOM document loaded in that window."><code>window</code></a> (或<code>global</code>)对象。当类的方法中需要 <code>this</code> 指向类的实例时,你可能需要显式地把 <code>this</code> 绑定到回调函数,就不会丢失该实例的引用。</p>
<pre><code class="language-javascript"><code>function LateBloomer() {
this.petalCount = Math.ceil(Math.random() * 12) + 1;
}
// 在 1 秒钟后声明 bloom
LateBloomer.prototype.bloom = function() {
window.setTimeout(this.declare.bind(this), 1000);
};
LateBloomer.prototype.declare = function() {
console.log('I am a beautiful flower with ' +
this.petalCount + ' petals!');
};
var flower = new LateBloomer();
flower.bloom(); // 一秒钟后, 调用'declare'方法</code></code></pre>
<h3 id="作为构造函数使用的绑定函数">作为构造函数使用的绑定函数</h3>
<div class="warning">
<p><strong>警告</strong> :这部分演示了 JavaScript 的能力并且记录了 <code>bind()</code> 的超前用法。以下展示的方法并不是最佳的解决方案,且可能不应该用在任何生产环境中。</p>
</div>
<p>绑定函数自动适应于使用 <a href="Reference/Operators/new" title="new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。"><code>new</code></a> 操作符去构造一个由目标函数创建的新实例。当一个绑定函数是用来构建一个值的,原来提供的 <code>this</code> 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。</p>
<pre><code class="language-javascript">function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return this.x + ',' + this.y;
};
var p = new Point(1, 2);
p.toString(); // '1,2'
var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 以下这行代码在 polyfill 不支持,
// 在原生的bind方法运行没问题:
//(译注polyfill的bind方法如果加上把bind的第一个参数即新绑定的this执行Object()来包装为对象Object(null)则是{},那么也可以支持)
var YAxisPoint = Point.bind(null, 0/*x*/);
var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5'
axisPoint instanceof Point; // true
axisPoint instanceof YAxisPoint; // true
new Point(17, 42) instanceof YAxisPoint; // true</code></pre>
<p>请注意,你不需要做特别的处理就可以用 <a href="Reference/Operators/new" title="new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。"><code>new</code></a> 操作符创建一个绑定函数。也就是说,你不需要做特别处理就可以创建一个可以被直接调用的绑定函数,即使你更希望绑定函数是用 <a href="Reference/Operators/new" title="new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。"><code>new</code></a> 操作符来调用。</p>
<pre><code class="language-javascript">// 这个例子可以直接在你的 javascript 控制台运行
// ...接着上面的代码继续
// 仍然能作为一个普通函数来调用
// (即使通常来说这个不是被期望发生的)
YAxisPoint(13);
emptyObj.x + ',' + emptyObj.y; // '0,13'
</code></pre>
<p>如果你希望一个绑定函数要么只能用 <a href="Reference/Operators/new" title="new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。"><code>new</code></a> 操作符,要么只能直接调用,那你必须在目标函数上显式规定这个限制。</p>
<h3 id="Example:_Creating_shortcuts" name="Example:_Creating_shortcuts">快捷调用</h3>
<p>在你想要为一个需要特定的 <strong><code>this</code></strong> 值的函数创建一个捷径shortcut的时候<code>bind()</code> 也很好用。</p>
<p>你可以用 <a href="Reference/Global_Objects/Array/slice" title="The source for this interactive demo is stored in a GitHub repository. If you'd like to contribute to the interactive demo project, please clone https://github.com/mdn/interactive-examples and send us a pull request."><code>Array.prototype.slice</code></a> 来将一个类似于数组的对象array-like object转换成一个真正的数组就拿它来举例子吧。你可以简单地这样写</p>
<pre><code class="language-javascript">var slice = Array.prototype.slice;
// ...
slice.apply(arguments);</code></pre>
<p><code>bind()</code>可以使这个过程变得简单。在下面这段代码里面,<code>slice</code> 是 <a href="Reference/Global_Objects/Function/prototype" title="Function.prototype 属性存储了 Function 的原型对象。"><code>Function.prototype</code></a> 的 <a href="Reference/Global_Objects/Function/apply" title="apply() 方法调用一个具有给定this值的函数以及作为一个数组或类似数组对象提供的参数。"><code>apply()</code></a> 方法的绑定函数,并且将 <a href="Reference/Global_Objects/Array/prototype" title="Array.prototype  属性表示 Array 构造函数的原型并允许您向所有Array对象添加新的属性和方法。"><code>Array.prototype</code></a> 的 <a href="Reference/Global_Objects/Array/slice" title="The source for this interactive demo is stored in a GitHub repository. If you'd like to contribute to the interactive demo project, please clone https://github.com/mdn/interactive-examples and send us a pull request."><code>slice()</code></a> 方法作为 <strong><code>this</code></strong> 的值。这意味着我们压根儿用不着上面那个 <code>apply()</code>调用了。</p>
<pre><code class="language-javascript">// 与前一段代码的 "slice" 效果相同
var unboundSlice = Array.prototype.slice;
var slice = Function.prototype.apply.bind(unboundSlice);
// ...
slice(arguments);</code></pre>
<h2 id="Compatibility" name="Compatibility">Polyfill</h2>
<p>你可以将这段代码插入到你的脚本开头,从而使你的 <code>bind()</code> 在没有内置实现支持的环境中也可以部分地使用<code>bind</code></p>
<pre><code class="language-javascript">if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
// this instanceof fBound === true时,说明返回的fBound被当做new的构造函数调用
return fToBind.apply(this instanceof fBound
? this
: oThis,
  // 获取调用时(fBound)的传参.bind 返回的函数入参往往是这么传递的
aArgs.concat(Array.prototype.slice.call(arguments)));
};
  // 维护原型关系
if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype;
}
  // 下行的代码使fBound.prototype是fNOP的实例,因此
  // 返回的fBound若作为new的构造函数,new生成的新对象作为this传入fBound,新对象的__proto__就是fNOP的实例
fBound.prototype = new fNOP();
return fBound;
};
}</code></pre>
<p>上述算法和实际的实现算法还有许多其他的不同 (尽管可能还有其他不同之处,却没有那个必要去穷尽):</p>
<ul>
<li>这部分实现依赖于<a href="Reference/Global_Objects/Array/slice" title="The source for this interactive demo is stored in a GitHub repository. If you'd like to contribute to the interactive demo project, please clone https://github.com/mdn/interactive-examples and send us a pull request."><code>Array.prototype.slice()</code></a> <a href="Reference/Global_Objects/Array/concat" title="concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。"><code>Array.prototype.concat()</code></a> <a href="Reference/Global_Objects/Function/call" title="call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。"><code>Function.prototype.call()</code></a>这些原生方法。</li>
<li>这部分实现创建的函数并没有<a href="Reference/Global_Objects/Function/caller" title="返回调用指定函数的函数."><code>caller</code></a> 以及会在 getset或者deletion上抛出<a href="Reference/Global_Objects/TypeError" title="TypeError类型错误 对象用来表示值的类型非预期类型时发生的错误。"><code>TypeError</code></a>错误的 arguments 属性这两个不可改变的“毒药” 。(假如环境支持{jsxref("Object.defineProperty")}}, 或者实现支持<a class="new" href="Reference/Global_Objects/Object/defineGetter" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>__defineGetter__</code></a> and <a class="new" href="Reference/Global_Objects/Object/defineSetter" rel="nofollow" title="此页面仍未被本地化, 期待您的翻译!"><code>__defineSetter__</code></a> 扩展)</li>
<li>这部分实现创建的函数有 <code>prototype</code> 属性。(正确的绑定函数没有)</li>
<li>这部分实现创建的绑定函数所有的 length 属性并不是同ECMA-262标准一致的它创建的函数的 length 是0而在实际的实现中根据目标函数的 length 和预先指定的参数个数可能会返回非零的 length。</li>
</ul>
<p>如果你选择使用这部分实现,<strong>你不能依赖于那些与 ECMA-262, 5th edition 规定的行为偏离的例子。</strong><code>bind()</code> 函数被广泛支持之前,某些情况下(或者为了兼容某些特定需求对其做一些特定修改后)可以选择用这个实现作为过渡。</p>
<p>请访问 <a class="external" href="https://github.com/Raynos/function-bind" rel="noopener">https://github.com/Raynos/function-bind</a> 以查找更完整的解决方案!</p>
<h2 id="规范" style="margin-bottom: 20px; line-height: 30px; font-size: 2.14285714285714rem;">规范</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">Specification</th>
<th scope="col">Status</th>
<th scope="col">Comment</th>
</tr>
<tr>
<td><a class="external" href="https://www.ecma-international.org/ecma-262/5.1/#sec-15.3.4.5" hreflang="en" lang="en" rel="noopener">ECMAScript 5.1 (ECMA-262)<br/><small lang="zh-CN">Function.prototype.bind</small></a></td>
<td><span class="spec-Standard">Standard</span></td>
<td>初始定义。在 JavaScript 1.8.5 中实现</td>
</tr>
<tr>
<td><a class="external" href="https://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.bind" hreflang="en" lang="en" rel="noopener">ECMAScript 2015 (6th Edition, ECMA-262)<br/><small lang="zh-CN">Function.prototype.bind</small></a></td>
<td><span class="spec-Standard">Standard</span></td>
<td> </td>
</tr>
<tr>
<td><a class="external" href="https://tc39.github.io/ecma262/#sec-function.prototype.bind" hreflang="en" lang="en" rel="noopener">ECMAScript Latest Draft (ECMA-262)<br/><small lang="zh-CN">Function.prototype.bind</small></a></td>
<td><span class="spec-Draft">Draft</span></td>
<td> </td>
</tr>
</tbody>
</table>
<h2 id="浏览器兼容">浏览器兼容</h2>
<div>
<div class="hidden">The compatibility table on this page is generated from structured data. If you'd like to contribute to the data, please check out <a class="external" href="https://github.com/mdn/browser-compat-data" rel="noopener">https://github.com/mdn/browser-compat-data</a> and send us a pull request.</div>
<p></p><div class="bc-data"><a class="bc-github-link external" href="https://github.com/mdn/browser-compat-data" rel="noopener">Update compatibility data on GitHub</a><table class="bc-table bc-table-js"><thead><tr class="bc-platforms"><td></td><th class="bc-platform-desktop" colspan="6"><span>Desktop</span></th><th class="bc-platform-mobile" colspan="7"><span>Mobile</span></th><th class="bc-platform-server" colspan="1"><span>Server</span></th></tr><tr class="bc-browsers"><td></td><th class="bc-browser-chrome"><span class="bc-head-txt-label bc-head-icon-chrome">Chrome</span></th><th class="bc-browser-edge"><span class="bc-head-txt-label bc-head-icon-edge">Edge</span></th><th class="bc-browser-firefox"><span class="bc-head-txt-label bc-head-icon-firefox">Firefox</span></th><th class="bc-browser-ie"><span class="bc-head-txt-label bc-head-icon-ie">Internet Explorer</span></th><th class="bc-browser-opera"><span class="bc-head-txt-label bc-head-icon-opera">Opera</span></th><th class="bc-browser-safari"><span class="bc-head-txt-label bc-head-icon-safari">Safari</span></th><th class="bc-browser-webview_android"><span class="bc-head-txt-label bc-head-icon-webview_android">Android webview</span></th><th class="bc-browser-chrome_android"><span class="bc-head-txt-label bc-head-icon-chrome_android">Chrome for Android</span></th><th class="bc-browser-edge_mobile"><span class="bc-head-txt-label bc-head-icon-edge_mobile">Edge Mobile</span></th><th class="bc-browser-firefox_android"><span class="bc-head-txt-label bc-head-icon-firefox_android">Firefox for Android</span></th><th class="bc-browser-opera_android"><span class="bc-head-txt-label bc-head-icon-opera_android">Opera for Android</span></th><th class="bc-browser-safari_ios"><span class="bc-head-txt-label bc-head-icon-safari_ios">Safari on iOS</span></th><th class="bc-browser-samsunginternet_android"><span class="bc-head-txt-label bc-head-icon-samsunginternet_android">Samsung Internet</span></th><th class="bc-browser-nodejs"><span class="bc-head-txt-label bc-head-icon-nodejs">Node.js</span></th></tr></thead><tbody><tr><th scope="row"><code>bind</code></th><td class="bc-supports-yes bc-browser-chrome"><span class="bc-browser-name">Chrome</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
7</td><td class="bc-supports-yes bc-browser-edge"><span class="bc-browser-name">Edge</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
12</td><td class="bc-supports-yes bc-browser-firefox"><span class="bc-browser-name">Firefox</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
4</td><td class="bc-supports-yes bc-browser-ie"><span class="bc-browser-name">IE</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
9</td><td class="bc-supports-yes bc-browser-opera"><span class="bc-browser-name">Opera</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
11.6</td><td class="bc-supports-yes bc-browser-safari"><span class="bc-browser-name">Safari</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
5.1</td><td class="bc-supports-yes bc-browser-webview_android"><span class="bc-browser-name">WebView Android</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
4</td><td class="bc-supports-yes bc-browser-chrome_android"><span class="bc-browser-name">Chrome Android</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
18</td><td class="bc-supports-yes bc-browser-edge_mobile"><span class="bc-browser-name">Edge Mobile</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
Yes</td><td class="bc-supports-yes bc-browser-firefox_android"><span class="bc-browser-name">Firefox Android</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
4</td><td class="bc-supports-yes bc-browser-opera_android"><span class="bc-browser-name">Opera Android</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
12</td><td class="bc-supports-yes bc-browser-safari_ios"><span class="bc-browser-name">Safari iOS</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
6</td><td class="bc-supports-yes bc-browser-samsunginternet_android"><span class="bc-browser-name">Samsung Internet Android</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
Yes</td><td class="bc-supports-yes bc-browser-nodejs"><span class="bc-browser-name">nodejs</span><abbr class="bc-level-yes only-icon" title="Full support">
<span>Full support</span>
</abbr>
Yes</td></tr></tbody></table><section class="bc-legend" id="sect1"><h3 class="offscreen" id="Legend">Legend</h3><dl><dt><span class="bc-supports-yes bc-supports">
<abbr class="bc-level bc-level-yes only-icon" title="Full support">
<span>Full support</span>
 
</abbr></span></dt><dd>Full support</dd></dl></section></div><p></p>
</div>
<h2 id="相关链接">相关链接</h2>
<ul>
<li><a href="Reference/Global_Objects/Function/apply" title="apply() 方法调用一个具有给定this值的函数以及作为一个数组或类似数组对象提供的参数。"><code>Function.prototype.apply()</code></a></li>
<li><a href="Reference/Global_Objects/Function/call" title="call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数(参数的列表)。"><code>Function.prototype.call()</code></a></li>
<li><a href="Reference/Functions" title="有关更多示例和说明请参阅有关函数的JavaScript指南。">Functions</a></li>
</ul>
</article>