diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/zh-cn/web/javascript/reference/global_objects/object/defineproperty | |
parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip |
initial commit
Diffstat (limited to 'files/zh-cn/web/javascript/reference/global_objects/object/defineproperty')
-rw-r--r-- | files/zh-cn/web/javascript/reference/global_objects/object/defineproperty/index.html | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/files/zh-cn/web/javascript/reference/global_objects/object/defineproperty/index.html b/files/zh-cn/web/javascript/reference/global_objects/object/defineproperty/index.html new file mode 100644 index 0000000000..beaca31a6a --- /dev/null +++ b/files/zh-cn/web/javascript/reference/global_objects/object/defineproperty/index.html @@ -0,0 +1,545 @@ +--- +title: Object.defineProperty() +slug: Web/JavaScript/Reference/Global_Objects/Object/defineProperty +tags: + - ECMAScript 5 + - JavaScript + - JavaScript 1.8.5 + - Method + - Object + - 对象 + - 方法 +translation_of: Web/JavaScript/Reference/Global_Objects/Object/defineProperty +--- +<div>{{JSRef}}</div> + +<p><code><strong>Object.defineProperty()</strong></code> 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。</p> + +<div class="note"> +<p><strong>备注:</strong>应当直接在 {{jsxref("Object")}} 构造器对象上调用此方法,而不是在任意一个 <code>Object</code> 类型的实例上调用。</p> +</div> + +<div>{{EmbedInteractiveExample("pages/js/object-defineproperty.html")}}</div> + + + +<h2 id="语法">语法</h2> + +<pre class="syntaxbox notranslate">Object.defineProperty(<var>obj</var>, <var>prop</var>, <var>descriptor</var>)</pre> + +<h3 id="参数">参数</h3> + +<dl> + <dt><code><var>obj</var></code></dt> + <dd>要定义属性的对象。</dd> + <dt><code><var>prop</var></code></dt> + <dd>要定义或修改的属性的名称或 {{jsxref("Symbol")}} 。</dd> + <dt><code><var>descriptor</var></code></dt> + <dd>要定义或修改的属性描述符。</dd> +</dl> + +<h3 id="返回值">返回值</h3> + +<p>被传递给函数的对象。</p> + +<div class="note"> +<p>在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而<code>Object.defineProperty</code> 是定义key为Symbol的属性的方法之一。</p> +</div> + +<h2 id="描述">描述</h2> + +<p>该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到({{jsxref("Statements/for...in", "for...in")}} 或 {{jsxref("Object.keys")}}<a href="/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys"> </a>方法),可以改变这些属性的值,也可以{{jsxref("Operators/delete", "删除")}}这些属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 <code>Object.defineProperty()</code> 添加的属性值是不可修改(immutable)的。</p> + +<p>对象里目前存在的属性描述符有两种主要形式:<em>数据描述符</em>和<em>存取描述符</em>。<em>数据描述符</em>是一个具有值的属性,该值可以是可写的,也可以是不可写的。<em>存取描述符</em>是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。</p> + +<p>这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 <code>Object.defineProperty()</code> 定义属性时的默认值):</p> + +<dl> + <dt><code>configurable</code></dt> + <dd>当且仅当该属性的 <code>configurable</code> 键值为 <code>true</code> 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。<br> + <strong>默认为 </strong> <strong><code>false</code></strong>。</dd> + <dt><code>enumerable</code></dt> + <dd>当且仅当该属性的 <code>enumerable</code> 键值为 <code>true</code> 时,该属性才会出现在对象的枚举属性中。<br> + <strong>默认为 <code>false</code></strong>。</dd> +</dl> + +<p>数据描述符还具有以下可选键值:</p> + +<dl> + <dt><code>value</code></dt> + <dd>该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。<br> + <strong>默认为 {{jsxref("undefined")}}</strong>。</dd> + <dt><code>writable</code></dt> + <dd>当且仅当该属性的 <code>writable</code> 键值为 <code>true</code> 时,属性的值,也就是上面的 <code>value</code>,才能被{{jsxref("Operators/Assignment_Operators", "赋值运算符")}}改变。<br> + <strong>默认为 <code>false</code>。</strong></dd> +</dl> + +<p>存取描述符还具有以下可选键值:</p> + +<dl> + <dt><code>get</code></dt> + <dd>属性的 getter 函数,如果没有 getter,则为 <code>undefined</code>。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 <code>this</code> 对象(由于继承关系,这里的<code>this</code>并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。<br> + <strong>默认为 {{jsxref("undefined")}}</strong>。</dd> + <dt><code>set</code></dt> + <dd>属性的 setter 函数,如果没有 setter,则为 <code>undefined</code>。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 <code>this</code> 对象。<br> + <strong>默认为 {{jsxref("undefined")}}</strong>。</dd> +</dl> + +<h4 id="描述符默认值汇总">描述符默认值汇总</h4> + +<ul> + <li>拥有布尔值的键 <code>configurable</code>、<code>enumerable</code> 和 <code>writable</code> 的默认值都是 <span style="font-family: courier new,andale mono,monospace; line-height: 1.5;"><code>false</code></span>。</li> + <li>属性值和函数的键 <code>value</code>、<code>get</code> 和 <code>set</code> 字段的默认值为 <code>undefined</code>。</li> +</ul> + +<h4 id="描述符可拥有的键值">描述符可拥有的键值</h4> + +<dl> + <dt> + <table class="standard-table"> + <caption></caption> + <tbody> + <tr> + <td></td> + <td><code>configurable</code></td> + <td><code>enumerable</code></td> + <td><code>value</code></td> + <td><code>writable</code></td> + <td><code>get</code></td> + <td><code>set</code></td> + </tr> + <tr> + <td>数据描述符</td> + <td>可以</td> + <td>可以</td> + <td>可以</td> + <td>可以</td> + <td>不可以</td> + <td>不可以</td> + </tr> + <tr> + <td>存取描述符</td> + <td>可以</td> + <td>可以</td> + <td>不可以</td> + <td>不可以</td> + <td>可以</td> + <td>可以</td> + </tr> + </tbody> + </table> + </dt> +</dl> + +<p>如果一个描述符不具有 <code>value</code>、<code>writable</code>、<code>get</code> 和 <code>set</code> 中的任意一个键,那么它将被认为是一个数据描述符。如果一个描述符同时拥有 <code>value</code> 或 <code>writable</code> 和 <code>get</code> 或 <code>set</code> 键,则会产生一个异常。</p> + +<p>记住,这些选项不一定是自身属性,也要考虑继承来的属性。为了确认保留这些默认值,在设置之前,可能要冻结 {{jsxref("Object.prototype")}},明确指定所有的选项,或者通过 {{jsxref("Object.create", "Object.create(null)")}} 将 {{jsxref("Object.prototype.__proto__", "__proto__")}} 属性指向 {{jsxref("null")}}。</p> + +<pre class="brush: js notranslate">// 使用 __proto__ +var obj = {}; +var descriptor = Object.create(null); // 没有继承的属性 +// 默认没有 enumerable,没有 configurable,没有 writable +descriptor.value = 'static'; +Object.defineProperty(obj, 'key', descriptor); + +// 显式 +Object.defineProperty(obj, "key", { + enumerable: false, + configurable: false, + writable: false, + value: "static" +}); + +// 循环使用同一对象 +function withValue(value) { + var d = withValue.d || ( + withValue.d = { + enumerable: false, + writable: false, + configurable: false, + value: null + } + ); + d.value = value; + return d; +} +// ... 并且 ... +Object.defineProperty(obj, "key", withValue("static")); + +// 如果 freeze 可用, 防止后续代码添加或删除对象原型的属性 +// (value, get, set, enumerable, writable, configurable) +(Object.freeze||Object)(Object.prototype);</pre> + +<h2 id="示例">示例</h2> + +<p>如果你想了解如何使用 <code>Object.defineProperty</code> 方法和<em>类二进制标记</em>语法<span style="line-height: 1.5;">,可以看看</span>这些<a href="/en-US/docs/JavaScript/Reference/Global_Objects/Object/defineProperty/Additional_examples" style="line-height: 1.5;">额外示例</a><span style="line-height: 1.5;">。</span></p> + +<h3 id="创建属性">创建属性</h3> + +<p>如果对象中不存在指定的属性,<span style="font-family: courier new,andale mono,monospace; line-height: 1.5;"><code>Object.defineProperty()</code></span> 会创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。</p> + +<pre class="brush: js notranslate">var o = {}; // 创建一个新对象 + +// 在对象中添加一个属性与数据描述符的示例 +Object.defineProperty(o, "a", { + value : 37, + writable : true, + enumerable : true, + configurable : true +}); + +// 对象 o 拥有了属性 a,值为 37 + +// 在对象中添加一个设置了存取描述符属性的示例 +var bValue = 38; +Object.defineProperty(o, "b", { + // 使用了方法名称缩写(ES2015 特性) + // 下面两个缩写等价于: + // get : function() { return bValue; }, + // set : function(newValue) { bValue = newValue; }, + get() { return bValue; }, + set(newValue) { bValue = newValue; }, + enumerable : true, + configurable : true +}); + +o.b; // 38 +// 对象 o 拥有了属性 b,值为 38 +// 现在,除非重新定义 o.b,o.b 的值总是与 bValue 相同 + +// 数据描述符和存取描述符不能混合使用 +Object.defineProperty(o, "conflict", { + value: 0x9f91102, + get() { return 0xdeadbeef; } +}); +// 抛出错误 TypeError: value appears only in data descriptors, get appears only in accessor descriptors +</pre> + +<h3 id="修改属性">修改属性</h3> + +<p>如果属性已经存在,<code>Object.defineProperty()</code>将尝试根据描述符中的值以及对象当前的配置来修改这个属性。如果旧描述符将其<code>configurable</code> 属性设置为<code>false</code>,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换。</p> + +<p>当试图改变不可配置属性(除了 <code>value</code> 和 <code>writable</code> 属性之外)的值时,会抛出{{jsxref("TypeError")}},除非当前值和新值相同。</p> + +<h4 id="Writable_属性">Writable 属性</h4> + +<p>当 <code>writable</code> 属性设置为 <code>false</code> 时,该属性被称为“不可写的”。它不能被重新赋值。</p> + +<pre class="brush: js notranslate">var o = {}; // 创建一个新对象 + +Object.defineProperty(o, 'a', { + value: 37, + writable: false +}); + +console.log(o.a); // logs 37 +o.a = 25; // No error thrown +// (it would throw in strict mode, +// even if the value had been the same) +console.log(o.a); // logs 37. The assignment didn't work. + +// strict mode +(function() { + 'use strict'; + var o = {}; + Object.defineProperty(o, 'b', { + value: 2, + writable: false + }); + o.b = 3; // throws TypeError: "b" is read-only + return o.b; // returns 2 without the line above +}());</pre> + +<p>如示例所示,试图写入非可写属性不会改变它,也不会引发错误。</p> + +<h4 id="Enumerable_属性">Enumerable 属性</h4> + +<p><code>enumerable</code> 定义了对象的属性是否可以在 {{jsxref("Statements/for...in", "for...in")}} 循环和 {{jsxref("Object.keys()")}} 中被枚举。</p> + +<pre class="brush: js notranslate">var o = {}; +Object.defineProperty(o, "a", { value : 1, enumerable: true }); +Object.defineProperty(o, "b", { value : 2, enumerable: false }); +Object.defineProperty(o, "c", { value : 3 }); // enumerable 默认为 false +o.d = 4; // 如果使用直接赋值的方式创建对象的属性,则 enumerable 为 true +Object.defineProperty(o, Symbol.for('e'), { + value: 5, + enumerable: true +}); +Object.defineProperty(o, Symbol.for('f'), { + value: 6, + enumerable: false +}); + +for (var i in o) { + console.log(i); +} +// logs 'a' and 'd' (in undefined order) + +Object.keys(o); // ['a', 'd'] + +o.propertyIsEnumerable('a'); // true +o.propertyIsEnumerable('b'); // false +o.propertyIsEnumerable('c'); // false +o.propertyIsEnumerable('d'); // true +o.propertyIsEnumerable(Symbol.for('e')); // true +o.propertyIsEnumerable(Symbol.for('f')); // false + +var p = { ...o } +p.a // 1 +p.b // undefined +p.c // undefined +p.d // 4 +p[Symbol.for('e')] // 5 +p[Symbol.for('f')] // undefined</pre> + +<h4 id="Configurable_属性">Configurable 属性</h4> + +<p><code>configurable</code> 特性表示对象的属性是否可以被删除,以及除 <code>value</code> 和 <code>writable</code> 特性外的其他特性是否可以被修改。</p> + +<pre class="brush: js notranslate">var o = {}; +Object.defineProperty(o, 'a', { + get() { return 1; }, + configurable: false +}); + +Object.defineProperty(o, 'a', { + configurable: true +}); // throws a TypeError +Object.defineProperty(o, 'a', { + enumerable: true +}); // throws a TypeError +Object.defineProperty(o, 'a', { + set() {} +}); // throws a TypeError (set was undefined previously) +Object.defineProperty(o, 'a', { + get() { return 1; } +}); // throws a TypeError +// (even though the new get does exactly the same thing) +Object.defineProperty(o, 'a', { + value: 12 +}); // throws a TypeError // ('value' can be changed when 'configurable' is false but not in this case due to 'get' accessor) + +console.log(o.a); // logs 1 +delete o.a; // Nothing happens +console.log(o.a); // logs 1</pre> + +<p>如果 <code>o.a</code> 的 <code>configurable</code> 属性为 <code>true</code>,则不会抛出任何错误,并且,最后,该属性会被删除。</p> + +<h3 id="添加多个属性和默认值">添加多个属性和默认值</h3> + +<p>考虑特性被赋予的默认特性值非常重要,通常,使用点运算符和 <code>Object.defineProperty()</code> 为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示。</p> + +<pre class="brush: js notranslate">var o = {}; + +o.a = 1; +// 等同于: +Object.defineProperty(o, "a", { + value: 1, + writable: true, + configurable: true, + enumerable: true +}); + + +// 另一方面, +Object.defineProperty(o, "a", { value : 1 }); +// 等同于: +Object.defineProperty(o, "a", { + value: 1, + writable: false, + configurable: false, + enumerable: false +}); +</pre> + +<h3 id="自定义_Setters_和_Getters">自定义 Setters 和 Getters</h3> + +<p>下面的例子展示了如何实现一个自存档对象。当设置<code>temperature</code> 属性时,<code>archive</code> 数组会收到日志条目。</p> + +<pre class="brush: js notranslate">function Archiver() { + var temperature = null; + var archive = []; + + Object.defineProperty(this, 'temperature', { + get: function() { + console.log('get!'); + return temperature; + }, + set: function(value) { + temperature = value; + archive.push({ val: temperature }); + } + }); + + this.getArchive = function() { return archive; }; +} + +var arc = new Archiver(); +arc.temperature; // 'get!' +arc.temperature = 11; +arc.temperature = 13; +arc.getArchive(); // [{ val: 11 }, { val: 13 }]</pre> + +<p>下面这个例子中,getter 总是会返回一个相同的值。</p> + +<pre class="brush: js notranslate">var pattern = { + get: function () { + return 'I alway return this string,whatever you have assigned'; + }, + set: function () { + this.myname = 'this is my name string'; + } +}; + + +function TestDefineSetAndGet() { + Object.defineProperty(this, 'myproperty', pattern); +} + + +var instance = new TestDefineSetAndGet(); +instance.myproperty = 'test'; + +// 'I alway return this string,whatever you have assigned' +console.log(instance.myproperty); +// 'this is my name string' +console.log(instance.myname);</pre> + +<h3 id="继承属性">继承属性</h3> + +<p>如果访问者的属性是被继承的,它的 <code>get</code> 和 <code>set</code> 方法会在子对象的属性被访问或者修改时被调用。如果这些方法用一个变量存值,该值会被所有对象共享。</p> + +<pre class="brush: js notranslate">function myclass() { +} + +var value; +Object.defineProperty(myclass.prototype, "x", { + get() { + return value; + }, + set(x) { + value = x; + } +}); + +var a = new myclass(); +var b = new myclass(); +a.x = 1; +console.log(b.x); // 1 +</pre> + +<p>这可以通过将值存储在另一个属性中解决。在 <code>get</code> 和 <code>set</code> 方法中,<code>this</code> 指向某个被访问和修改属性的对象。</p> + +<pre class="brush: js notranslate">function myclass() { +} + +Object.defineProperty(myclass.prototype, "x", { + get() { + return this.stored_x; + }, + set(x) { + this.stored_x = x; + } +}); + +var a = new myclass(); +var b = new myclass(); +a.x = 1; +console.log(b.x); // undefined</pre> + +<p>不像访问者属性,值属性始终在对象自身上设置,而不是一个原型。然而,如果一个不可写的属性被继承,它仍然可以防止修改对象的属性。</p> + +<pre class="brush: js notranslate">function myclass() { +} + +myclass.prototype.x = 1; +Object.defineProperty(myclass.prototype, "y", { + writable: false, + value: 1 +}); + +var a = new myclass(); +a.x = 2; +console.log(a.x); // 2 +console.log(myclass.prototype.x); // 1 +a.y = 2; // Ignored, throws in strict mode +console.log(a.y); // 1 +console.log(myclass.prototype.y); // 1 +</pre> + +<h2 id="规范">规范</h2> + +<table class="standard-table"> + <tbody> + <tr> + <th scope="col">规范</th> + <th scope="col">状态</th> + <th scope="col">备注</th> + </tr> + <tr> + <td>{{SpecName('ES5.1', '#sec-15.2.3.6', 'Object.defineProperty')}}</td> + <td>{{Spec2('ES5.1')}}</td> + <td>Initial definition. Implemented in JavaScript 1.8.5.</td> + </tr> + <tr> + <td>{{SpecName('ES6', '#sec-object.defineproperty', 'Object.defineProperty')}}</td> + <td>{{Spec2('ES6')}}</td> + <td></td> + </tr> + <tr> + <td>{{SpecName('ESDraft', '#sec-object.defineproperty', 'Object.defineProperty')}}</td> + <td>{{Spec2('ESDraft')}}</td> + <td></td> + </tr> + </tbody> +</table> + +<h2 id="浏览器兼容性">浏览器兼容性</h2> + + + +<p>{{Compat("javascript.builtins.Object.defineProperty")}}</p> + +<h2 id="兼容性问题">兼容性问题</h2> + +<h3 id="重定义数组_Array_对象的_length_属性">重定义数组 <code>Array</code> 对象的 <code>length</code> 属性</h3> + +<p>重定义数组的 {{jsxref("Array.length", "length")}} 属性是可能的,但是会受到一般的重定义限制。({{jsxref("Array.length", "length")}} 属性初始为 non-configurable,non-enumerable 以及 writable。对于一个内容不变的数组,改变其 {{jsxref("Array.length", "length")}} 属性的值或者使它变为 non-writable 是可能的。但是改变其可枚举性和可配置性或者当它是 non-writable 时尝试改变它的值或是可写性,这两者都是不允许的。)然而,并不是所有的浏览器都允许 <code>Array.length</code> 的重定义。</p> + +<p>在 Firefox 4 至 22 版本中,尝试重定义数组的 length 属性都会抛出 {{jsxref("TypeError")}} 异常。</p> + +<p>一些版本的 Chrome 中,<code>Object.defineProperty()</code> 在某些情况下会忽略不同于数组当前{{jsxref("Array.length", "length")}}属性的length值。有些情况下改变可写性并不起作用(也不抛出异常)。同时,比如{{jsxref("Array.prototype.push")}}的一些数组操作方法也不会考虑不可读的length属性。</p> + +<p>一些版本的 Safari 中,<code>Object.defineProperty()</code> 在某些情况下会忽略不同于数组当前{{jsxref("Array.length", "length")}}属性的length值。尝试改变可写性的操作会正常执行而不抛出错误,但事实上并未改变属性的可写性。</p> + +<p>只在Internet Explorer 9及以后版本和Firefox 23及以后版本中,才完整地正确地支持数组 {{jsxref("Array.length", "length")}} 属性的重新定义。目前不要依赖于重定义数组 {{jsxref("Array.length", "length")}} 属性能够起作用,或在特定情形下起作用。与此同时,即使你能够依赖于它,你也<a href="http://whereswalden.com/2013/08/05/new-in-firefox-23-the-length-property-of-an-array-can-be-made-non-writable-but-you-shouldnt-do-it/">没有合适的理由这样做</a>。</p> + +<h3 id="Internet_Explorer_8_特别备注">Internet Explorer 8 特别备注</h3> + +<p>Internet Explorer 8 实现了 <code>Object.defineProperty()</code> 方法,但<a class="external" href="http://msdn.microsoft.com/en-us/library/dd229916%28VS.85%29.aspx">只能在 DOM 对象上使用</a>。 需要注意的一些事情:</p> + +<ul> + <li>尝试在原生对象上使用 <code>Object.defineProperty()</code> 会报错。</li> + <li>属性特性必须设置一些特定的值。对于数据属性描述符,<code>configurable</code>, <code>enumerable</code> 和 <code>writable</code> 属性必须全部设置为 <code>true</code>;对于访问器属性描述符,<code>configurable</code> 必须设置为 <code>true</code>,<code>enumerable</code> 必须设置为 <code>false</code>。(?) 任何试图提供其他值(?)将导致一个错误抛出。</li> + <li>重新配置一个属性首先需要删除该属性。如果属性没有删除,就如同重新配置前的尝试。</li> +</ul> + +<h3 id="Chrome_37(及以下)特别备注">Chrome 37(及以下)特别备注</h3> + +<p>Chrome 37(及以下)有一个 <a href="https://bugs.chromium.org/p/v8/issues/detail?id=3448">bug</a>,使用 <code>writable: false</code> 定义原型 prototype 属性,或者函数时,不会像预期的那样工作。</p> + +<h2 id="相关链接">相关链接</h2> + +<ul> + <li><a href="/zh-CN/docs/Enumerability_and_ownership_of_properties">属性的可枚举性和所有权</a></li> + <li>{{jsxref("Object.defineProperties()")}}</li> + <li>{{jsxref("Object.propertyIsEnumerable()")}}</li> + <li>{{jsxref("Object.getOwnPropertyDescriptor()")}}</li> + <li>{{jsxref("Object.prototype.watch()")}}</li> + <li>{{jsxref("Object.prototype.unwatch()")}}</li> + <li>{{jsxref("Operators/get", "get")}}</li> + <li>{{jsxref("Operators/set", "set")}}</li> + <li>{{jsxref("Object.create()")}}</li> + <li><a href="/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty/Additional_examples">Additional <code>Object.defineProperty</code> examples</a></li> + <li>{{jsxref("Reflect.defineProperty()")}}</li> +</ul> |