aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/web/javascript/data_structures/index.html
blob: 28273e864f747c8f323a90e67b17cf75e0fdf39b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
---
title: JavaScript 数据类型和数据结构
slug: Web/JavaScript/Data_structures
tags:
  - JavaScript
  - 初学者
  - 数据结构
  - 类型
translation_of: Web/JavaScript/Data_structures
---
<div>{{jsSidebar("More")}}</div>

<p>编程语言都具有内建的数据结构,但各种编程语言的数据结构常有不同之处。本文试图列出 JavaScript 语言中内建的数据结构及其属性,它们可以用来构建其他的数据结构;同时尽可能地描述与其他语言的不同之处。</p>

<h2 id="动态类型">动态类型</h2>

<p>JavaScript 是一种<strong>弱类型</strong>或者说<strong>动态</strong>语言。这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据:</p>

<pre class="brush: js notranslate">var foo = 42;    // foo is a Number now
foo = "bar"; // foo is a String now
foo = true;  // foo is a Boolean now
</pre>

<h2 id="数据类型">数据类型</h2>

<p>最新的 ECMAScript 标准定义了 8 种数据类型:</p>

<ul>
 <li>7 种{{Glossary("Primitive", "原始类型")}},使用 {{Glossary("typeof")}} 运算符检查:
  <ul>
   <li>{{Glossary("undefined")}}<code>typeof instance === "undefined"</code></li>
   <li>{{Glossary("Boolean")}}<code>typeof instance === "boolean"</code></li>
   <li>{{Glossary("Number")}}<code>typeof instance === "number"</code></li>
   <li>{{Glossary("String")}}<code>typeof instance === "string</code></li>
   <li>{{Glossary("BigInt")}}<code>typeof instance === "bigint"</code></li>
   <li>{{Glossary("Symbol")}}<code>typeof instance === "symbol"</code></li>
   <li>{{Glossary("null")}}<code>typeof instance === "object"</code></li>
  </ul>
 </li>
 <li>{{Glossary("Object")}}<code>typeof instance === "object"</code>。任何 {{Glossary("constructed")}} 对象实例的特殊非数据结构类型,也用做数据结构:new {{Glossary("Object")}},new {{Glossary("Array")}},new {{Glossary("Map")}},new {{Glossary("Set")}},new {{Glossary("WeakMap")}},new {{Glossary("WeakSet")}},new {{Glossary("Date")}},和几乎所有通过 {{Glossary("new keyword")}} 创建的东西。</li>
</ul>

<p>记住 <code>typeof</code> 操作符的唯一目的就是检查数据类型,如果我们希望检查任何从 Object 派生出来的结构类型,使用 <code>typeof</code> 是不起作用的,因为总是会得到 <code>"object"</code>。检查 Object 种类的合适方式是使用 {{Glossary("instanceof")}} 关键字。但即使这样也存在误差。</p>

<h2 id="原始值_primitive_values">原始值( primitive values )</h2>

<p>除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。</p>

<h3 id="布尔类型">布尔类型</h3>

<p>布尔表示一个逻辑实体,可以有两个值:<code>true</code><code>false</code></p>

<h3 id="Null_类型">Null 类型</h3>

<p>Null 类型只有一个值: <code>null</code>,更多详情可查看 {{jsxref("null")}}{{Glossary("Null")}}</p>

<h3 id="Undefined_类型">Undefined 类型</h3>

<p>一个没有被赋值的变量会有个默认值 <code>undefined</code>,更多详情可查看 {{jsxref("undefined")}}{{Glossary("Undefined")}}</p>

<h3 id="数字类型">数字类型</h3>

<p>根据 ECMAScript 标准,JavaScript 中只有一种数字类型:基于 IEEE 754 标准的双精度 64 位二进制格式的值(-(2<sup>53</sup> -1) 到 2<sup>53</sup> -1)。<strong>它并没有为整数给出一种特定的类型</strong>。除了能够表示浮点数外,还有一些带符号的值:<code>+Infinity</code><code>-Infinity</code><code>NaN</code> (非数值,Not-a-Number)。</p>

<p>要检查值是否大于或小于 <code>+/-Infinity</code>,你可以使用常量 {{jsxref("Number.MAX_VALUE")}}{{jsxref("Number.MIN_VALUE")}}。另外在 ECMAScript 6 中,你也可以通过 {{jsxref("Number.isSafeInteger()")}} 方法还有 {{jsxref("Number.MAX_SAFE_INTEGER")}}{{jsxref("Number.MIN_SAFE_INTEGER")}} 来检查值是否在双精度浮点数的取值范围内。 超出这个范围,JavaScript 中的数字不再安全了,也就是只有 second mathematical interger 可以在 JavaScript 数字类型中正确表现。</p>

<p>数字类型中只有一个整数有两种表示方法: 0 可表示为 -0 和 +0("0" 是 +0 的简写)。 在实践中,这也几乎没有影响。 例如 <code>+0 === -0</code> 为真。 但是,你可能要注意除以0的时候:</p>

<pre class="brush: js notranslate">42 / +0; // Infinity
42 / -0; // -Infinity
</pre>

<p>尽管一个数字常常仅代表它本身的值,但 JavaScript 提供了一些 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators" title="en/JavaScript/Reference/Operators/Bitwise_Operators"> 位运算符</a>。 这些位运算符和一个单一数字通过位操作可以用来表现一些布尔值。然而自从 JavaScript 提供其他的方式来表示一组布尔值(如一个布尔值数组或一个布尔值分配给命名属性的对象)后,这种方式通常被认为是不好的。位操作也容易使代码难以阅读,理解和维护, 在一些非常受限的情况下,可能需要用到这些技术,比如试图应付本地存储的存储限制。 位操作只应该是用来优化尺寸的最后选择。</p>

<h3 id="BigInt_类型">BigInt 类型</h3>

<p>{{jsxref("BigInt")}}类型是 JavaScript 中的一个基础的数值类型,可以用任意精度表示整数。使用 BigInt,您可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。BigInt 是通过在整数末尾附加 <code>n </code>或调用构造函数来创建的。</p>

<p>通过使用常量{{jsxref("Number.MAX_SAFE_INTEGER")}},您可以获得可以用数字递增的最安全的值。通过引入 BigInt,您可以操作超过{{jsxref("Number.MAX_SAFE_INTEGER")}}的数字。您可以在下面的示例中观察到这一点,其中递增{{jsxref("Number.MAX_SAFE_INTEGER")}}会返回预期的结果:</p>

<pre class="notranslate"><code>&gt; const x = 2n ** 53n;
9007199254740992n
&gt; const y = x + 1n;
9007199254740993n</code>
</pre>

<p>可以对<code>BigInt</code>使用运算符<code>+<font face="Arial, x-locale-body, sans-serif"></font></code><code>*<font face="Arial, x-locale-body, sans-serif"></font></code><code>-<font face="Arial, x-locale-body, sans-serif"></font></code><code>**</code><code>%</code>,就像对数字一样。BigInt 严格来说并不等于一个数字,但它是松散的。</p>

<p>在将<code>BigInt</code>转换为<code>Boolean</code>时,它的行为类似于一个数字:<code>if<font face="Arial, x-locale-body, sans-serif"></font></code><code>||<font face="Arial, x-locale-body, sans-serif"></font></code><code>&amp;&amp;<font face="Arial, x-locale-body, sans-serif"></font></code><code>Boolean<font face="Arial, x-locale-body, sans-serif"> 和</font></code><code>!。</code></p>

<p><code>BigInt</code>不能与数字互换操作。否则,将抛出{{jsxref("TypeError")}}</p>

<h3 id="字符串类型">字符串类型</h3>

<p>JavaScript 的字符串类型用于表示文本数据。它是一组16位的无符号整数值的“元素”。在字符串中的每个元素占据了字符串的位置。第一个元素的索引为 0,下一个是索引 1,依此类推。字符串的长度是它的元素的数量。</p>

<p>不同于类 C 语言,JavaScript 字符串是不可更改的。这意味着字符串一旦被创建,就不能被修改。但是,可以基于对原始字符串的操作来创建新的字符串。例如:</p>

<ul>
 <li>获取一个字符串的子串可通过选择个别字母或者使用 {{jsxref("String.substr()")}}.</li>
 <li>两个字符串的连接使用连接操作符 (<code>+</code>) 或者 {{jsxref("String.concat()")}}.</li>
</ul>

<h4 id="注意代码中的“字符串类型”!">注意代码中的“字符串类型”!</h4>

<p>可以使用字符串来表达复杂的数据。以下是一些很好的性质:</p>

<ul>
 <li>容易连接构造复杂的字串符</li>
 <li>字符串容易被调试(你看到的往往在字符串里)</li>
 <li>字符串通常是许多 APIs 的常见标准 (<a href="/en/DOM/HTMLInputElement" title="HTMLInputElement">input fields</a>, <a href="/en/Storage" title="Storage">local storage</a> values, {{ domxref("XMLHttpRequest") }}当使用<code>responseText等</code>的时候回应) 而且他只能与字符串一同使用。</li>
</ul>

<p>使用约定,字符串一般可以用来表达任何数据结构。这不是一个好主意。例如,使用一个分隔符,可以模拟一个列表(而 JavaScript 数组可能更适合)。不幸的是,当分隔符用于列表中的元素时,列表就会被破坏。 可以选择转义字符,等等。所有这些都需要约定,并造成不必要的维护负担。</p>

<p>表达文本数据和符号数据时候推荐使用字符串。当表达复杂的数据时,使用字符串解析和适当的缩写。</p>

<h3 id="符号类型">符号类型</h3>

<p>符号(Symbols)是 ECMAScript 第6版新定义的。符号类型是唯一的并且是不可修改的, 并且也可以用来作为 Object 的 key 的值(如下). 在某些语言当中也有类似的原子类型(Atoms). 你也可以认为为它们是 C 里面的枚举类型. 更多细节请看 {{Glossary("Symbol")}}{{jsxref("Symbol")}}</p>

<h2 id="对象">对象</h2>

<p>在计算机科学中, 对象是指内存中的可以被 {{Glossary("Identifier", "标识符")}}引用的一块区域.</p>

<h3 id="属性">属性</h3>

<p>在 JavaScript 里,对象可以被看作是一组属性的集合。用 <a href="/en/JavaScript/Guide/Values,_variables,_and_literals#Object_literals">对象字面量语法</a> 来定义一个对象时,会自动初始化一组属性。(也就是说,你定义一个var a = "Hello",那么a本身就会有a.substring这个方法,以及a.length这个属性,以及其它;如果你定义了一个对象,var a = {},那么 a 就会自动有 a.hasOwnProperty 及 a.constructor 等属性和方法。)而后,这些属性还可以被增减。属性的值可以是任意类型,包括具有复杂数据结构的对象。属性使用键来标识,它的键值可以是一个字符串或者符号值(Symbol)。</p>

<p>ECMAScript 定义的对象中有两种属性:数据属性和访问器属性。</p>

<h4 id="数据属性">数据属性</h4>

<p>数据属性是键值对,并且每个数据属性拥有下列特性:</p>

<p><strong>数据属性的特性(Attributes of a data property)</strong></p>

<table class="standard-table">
 <tbody>
  <tr>
   <th>特性</th>
   <th>数据类型</th>
   <th>描述</th>
   <th>默认值</th>
  </tr>
  <tr>
   <td>[[Value]]</td>
   <td>任何Javascript类型</td>
   <td>包含这个属性的数据值。</td>
   <td>undefined</td>
  </tr>
  <tr>
   <td>[[Writable]]</td>
   <td>Boolean</td>
   <td>如果该值为 <code>false,</code>则该属性的 [[Value]] 特性 不能被改变。</td>
   <td>false</td>
  </tr>
  <tr>
   <td>[[Enumerable]]</td>
   <td>Boolean</td>
   <td>如果该值为 <code>true,</code>则该属性可以用 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in">for...in</a> 循环来枚举。</td>
   <td>false</td>
  </tr>
  <tr>
   <td>[[Configurable]]</td>
   <td>Boolean</td>
   <td>如果该值为 <code>false,</code>则该属性不能被删除,并且 除了 [[Value]] 和 [[Writable]] 以外的特性都不能被改变。</td>
   <td>false</td>
  </tr>
 </tbody>
</table>

<table class=" standard-table">
 <caption>过时的属性(在ECMAScript 3定义的, 在ECMAScript 5被重命名)</caption>
 <tbody>
  <tr>
   <th>属性</th>
   <th>类型</th>
   <th>描述</th>
  </tr>
  <tr>
   <td>Read-only</td>
   <td>Boolean</td>
   <td>ES5 [[Writable]] 属性的反状态(Reversed state)</td>
  </tr>
  <tr>
   <td>DontEnum</td>
   <td>Boolean</td>
   <td>ES5 [[Enumerable]]  属性的反状态</td>
  </tr>
  <tr>
   <td>DontDelete</td>
   <td>Boolean</td>
   <td>ES5 [[Configurable]] 属性的反状态</td>
  </tr>
 </tbody>
</table>

<h4 id="访问器属性">访问器属性</h4>

<p>访问器属性有一个或两个访问器函数 (get 和 set) 来存取数值,并且有以下特性:</p>

<table class="standard-table">
 <caption>一个访问器属性的特性</caption>
 <tbody>
  <tr>
   <th>特性</th>
   <th>类型</th>
   <th>描述</th>
   <th>默认值</th>
  </tr>
  <tr>
   <td>[[Get]]</td>
   <td>函数对象或者 undefined</td>
   <td>该函数使用一个空的参数列表,能够在有权访问的情况下读取属性值。另见 <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/get">get</a></code></td>
   <td>undefined</td>
  </tr>
  <tr>
   <td>[[Set]]</td>
   <td>函数对象或者 undefined</td>
   <td>该函数有一个参数,用来写入属性值,另见 <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/set">set</a></code></td>
   <td>undefined</td>
  </tr>
  <tr>
   <td>[[Enumerable]]</td>
   <td>Boolean</td>
   <td>如果该值为 <code>true,则该属性可以用</code> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in">for...in</a> 循环来枚举。</td>
   <td>false</td>
  </tr>
  <tr>
   <td>[[Configurable]]</td>
   <td>Boolean</td>
   <td>如果该值为 <code>false,则该属性不能被删除,并且不能被转变成一个数据属性。</code></td>
   <td>false</td>
  </tr>
 </tbody>
</table>

<div class="note">
<p>注意:这些特性只有 JavaScript 引擎才用到,因此你不能直接访问它们。所以特性被放在两对方括号中,而不是一对。</p>
</div>

<h3 id="标准的_对象_和函数">"标准的" 对象, 和函数</h3>

<p>一个 JavaScript 对象就是键和值之间的映射。键是一个字符串(或者 {{jsxref("Symbol")}}),值可以是任意类型的值。 这使得对象非常符合 <a class="external" href="http://en.wikipedia.org/wiki/Hash_table">哈希表</a></p>

<p>函数是一个附带可被调用功能的常规对象。</p>

<h3 id="日期">日期</h3>

<p>当你想要显示日期时,毋庸置疑,使用内建的 <a href="/en/JavaScript/Reference/Global_Objects/Date">Date</a> 对象。</p>

<h3 id="有序集_数组和类型数组">有序集: 数组和类型数组</h3>

<p><a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array" title="Array">数组</a>是一种使用整数作为键(integer-key-ed)属性和长度(length)属性之间关联的常规对象。此外,数组对象还继承了 Array.prototype 的一些操作数组的便捷方法。例如, <code><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf" title="en/JavaScript/Reference/Global_Objects/Array/indexOf">indexOf</a></code> (搜索数组中的一个值) or <code><a href="https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/push" title="en/JavaScript/Reference/Global_Objects/Array/push">push</a></code> (向数组中添加一个元素),等等。 这使得数组是表示列表或集合的最优选择。</p>

<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays">类型数组(Typed Arrays)</a>是 ECMAScript Edition 6 中新定义的 JavaScript 内建对象,提供了一个基本的二进制数据缓冲区的类数组视图。下面的表格能帮助你找到对等的 C 语言数据类型:</p>

<p>{{page("/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray", "TypedArray_objects", "", 0, 3)}}</p>

<h3 id="键控集_Maps_Sets_WeakMaps_WeakSets">键控集: Maps, Sets, WeakMaps, WeakSets</h3>

<p>这些数据结构把对象的引用当作键,其在 ECMAScript 第6版中有介绍。当 {{jsxref("Map")}}{{jsxref("WeakMap")}} 把一个值和对象关联起来的时候, {{jsxref("Set")}}{{jsxref("WeakSet")}} 表示一组对象。 Map 和 WeakMaps 之间的差别在于,在前者中,对象键是可枚举的。这允许垃圾收集器优化后面的枚举(This allows garbage collection optimizations in the latter case)。</p>

<p>在纯 ECMAScript 5 下可以实现 Maps 和 Sets 。然而,因为对象并不能进行比较(就对象“小于”示例来讲),所以查询必定是线性的。他们本地实现(包括WeakMaps)查询所花费的时间可能是对数增长。</p>

<p>通常,可以通过直接在对象上设置属性或着使用 data-*属性,来绑定数据到 DOM 节点。然而缺陷是在任何的脚本里,数据都运行在同样的上下文中。Maps 和 WeakMaps 方便将数据私密的绑定到一个对象。 </p>

<h3 id="结构化数据_JSON">结构化数据: JSON</h3>

<p>JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,来源于 JavaScript 同时也被多种语言所使用。 JSON 用于构建通用的数据结构。参见 {{Glossary("JSON")}} 以及 {{jsxref("JSON")}} 了解更多。</p>

<h3 id="标准库中更多的对象">标准库中更多的对象</h3>

<p>JavaScript 有一个内置对象的标准库。请查看 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects">参考</a> 来了解更多对象。</p>

<h2 id="使用_typeof_操作符判断对象类型">使用 typeof 操作符判断对象类型</h2>

<p>typeof 运算符可以帮助你查询变量的类型。要了解更多细节和注意事项请阅读<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof">参考页</a></p>

<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('ES1')}}</td>
   <td>{{Spec2('ES1')}}</td>
   <td>初始定义</td>
  </tr>
  <tr>
   <td>{{SpecName('ES5.1', '#sec-8', 'Types')}}</td>
   <td>{{Spec2('ES5.1')}}</td>
   <td></td>
  </tr>
  <tr>
   <td>{{SpecName('ES2015', '#sec-ecmascript-data-types-and-values', 'ECMAScript Data Types and Values')}}</td>
   <td>{{Spec2('ES2015')}}</td>
   <td>
    <p>Added Symbol.</p>
   </td>
  </tr>
  <tr>
   <td>{{SpecName('ESDraft', '#sec-ecmascript-data-types-and-values', 'ECMAScript Data Types and Values')}}</td>
   <td>{{Spec2('ESDraft')}}</td>
   <td></td>
  </tr>
 </tbody>
</table>

<h2 id="参考资料">参考资料</h2>

<ul>
 <li><a class="link-https" href="https://github.com/nzakas/computer-science-in-javascript/">Nicholas Zakas collection of common data structure and common algorithms in JavaScript.</a></li>
 <li><a href="https://github.com/monmohan/DataStructures_In_Javascript" title="https://github.com/monmohan/DataStructures_In_Javascript">Search Tre(i)es implemented in JavaScript</a></li>
</ul>