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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
|
---
title: Classes
slug: Web/JavaScript/Reference/Classes
tags:
- Classes
- Constructors
translation_of: Web/JavaScript/Reference/Classes
---
<div>{{JsSidebar("Classes")}}</div>
<p>ECMAScript 6 中引入了類別 (class) 作為 JavaScript 現有原型程式(prototype-based)繼承的語法糖。類別語法並<strong>不是</strong>要引入新的物件導向繼承模型到 JavaScript 中,而是提供一個更簡潔的語法來建立物件和處理繼承。</p>
<h2 id="定義類別">定義類別</h2>
<p>類別實際上是一種特別的函數(<a href="/en-US/docs/Web/JavaScript/Reference/Functions">functions</a>),就跟你可以定義函數敘述和函數宣告一樣,類別的語法有兩個元件:類別敘述(<a href="/en-US/docs/Web/JavaScript/Reference/Operators/class">class expressions</a>)和類別宣告(<a href="/en-US/docs/Web/JavaScript/Reference/Statements/class">class declarations</a>)。</p>
<h3 id="類別宣告">類別宣告</h3>
<p>一個定義類別的方法是使用類別宣告(<strong>class declaration</strong>),要宣告一個類別,你要使用關鍵字 <code>class</code> 搭配類別名稱(此例為 "Polygon")。</p>
<pre class="brush: js">class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
}</pre>
<h4 id="Hoisting">Hoisting</h4>
<p><strong>函數宣告</strong>和<strong>類別宣告</strong>的一個重要差別在於函數宣告是 {{Glossary("Hoisting", "hoisted")}} ,類別宣告則不是。 你需要先宣告你的類別,然後存取它,否則像是下面的程式碼就會丟出一個 {{jsxref("ReferenceError")}}:</p>
<pre class="brush: js example-bad">var p = new Polygon(); // ReferenceError
class Polygon {}
</pre>
<h3 id="類別敘述">類別敘述</h3>
<p><strong>類別敘述</strong>是定義類別的另一種方法。類別敘述可以有名稱或是無名稱。賦予一個有名稱類別敘述的名稱只在類別主體(class's body)中有作用。(但是類別敘述的名稱可以透過該類別(不是實例)的 <a href="/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name">.name</a> 屬性存取。)</p>
<pre class="brush: js">// unnamed
var Polygon = class {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
// named
var Polygon = class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
};
</pre>
<p>注意:類別<strong>敘述</strong>跟上述提到的類別<strong>宣告</strong>一樣,都會受到hoisting的影響。</p>
<h2 id="類別主體與方法定義">類別主體與方法定義</h2>
<p>類別的主體指的是被大括號(<code>{}</code>)包含的部分,你可以在這裡面定義類別成員(members),例如方法(methods)或建構子(constructors)。</p>
<h3 id="Strict_mode">Strict mode</h3>
<p><em>類別宣告</em>與<em>類別敘述</em>的主體都會以<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode">嚴格模式(strict mode</a>)執行,也就是說,建構子、靜態方法和原型方法、getter及setter函數等都會以嚴格模式執行。</p>
<h3 id="建構子">建構子</h3>
<p>建構子(<code><a href="/en-US/docs/Web/JavaScript/Reference/Classes/constructor">constructor</a>)</code>方法是一個特別的方法,用來建立和初始化一個<code>類別</code>的物件。一個類別只能有一個名為建構子(constructor)的特別方法。當類別中含有一個以上的<code>建構子</code>方法時,{{jsxref("SyntaxError")}} 將會被拋出。</p>
<p>一個建構子可以用關鍵字 <code>super</code> 來呼叫父類別的建構子。</p>
<h3 id="原型方法">原型方法</h3>
<p>參見 <a href="/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions">method definitions</a>。</p>
<pre class="brush: js">class Polygon {
constructor(height, width) {
this.height = height;
this.width = width;
}
// Getter
get area() {
return this.calcArea();
}
// Method
calcArea() {
return this.height * this.width;
}
}
const square = new Polygon(10, 10);
console.log(square.area); //100</pre>
<h3 id="靜態方法Static_methods">靜態方法(Static methods)</h3>
<p>關鍵字 <code><a href="/en-US/docs/Web/JavaScript/Reference/Classes/static">static</a></code> 定義了一個類別的靜態方法,靜態方法不需要<a href="/zh-TW/docs/Learn/JavaScript/Objects">實體化</a>它所屬類別的實例就可以被呼叫,它也<strong>無法</strong>被已實體化的類別物件呼叫。靜態方法經常被用來建立給應用程式使用的工具函數。</p>
<pre class="brush: js">class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distance(a, b) {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx*dx + dy*dy);
}
}
const p1 = new Point(5, 5);
const p2 = new Point(10, 10);
console.log(Point.distance(p1, p2)); // 7.0710678118654755
</pre>
<h3 id="裝箱、原型方法及靜態方法">裝箱、原型方法及靜態方法</h3>
<p>當一個靜態方法或原形方法被呼叫,但沒有一個物件的值與this綁定時,被呼叫的函數中this關鍵字會是<strong><code>undefined</code>。</strong>在此情況下,自動裝箱(autoboxing)不會發生<strong>。?</strong>即使我們在非嚴格模式中寫程式,此行為仍然會存在,這是因為所有的函式、定義方法、建構子、getters和setters都是在嚴格模式中執行。因此,若我們沒有定義this的值,this會是<strong><code>undefined</code></strong><code>。</code></p>
<pre class="brush: js">class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined
Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined</pre>
<p>若我們將上述程式用傳統的函式基礎類別(function based classes)表達,自動裝箱則會依據this在其被呼叫的函式中所綁定的值發生。</p>
<pre class="brush: js">function Animal() { }
Animal.prototype.speak = function() {
return this;
}
Animal.eat = function() {
return this;
}
let obj = new Animal();
let speak = obj.speak;
speak(); // 全域物件
let eat = Animal.eat;
eat(); // 全域物件</pre>
<h2 id="用_extends_建立子類別">用 <code>extends</code> 建立子類別</h2>
<p>關鍵字 <code><a href="/en-US/docs/Web/JavaScript/Reference/Classes/extends">extends</a></code> 是在類別宣告或是類別敘述中建立子類別的方法。</p>
<pre class="brush: js">class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Dog extends Animal {
speak() {
console.log(this.name + ' barks.');
}
}
var d = new Dog('Mitzie');
d.speak(); // Mitzie barks.
</pre>
<p>若在子類別中有建構子(constructor),要使用this前則必須先呼叫super()函式。</p>
<p>你也可以擴充(extends)傳統的函式基礎"類別"。</p>
<pre class="brush: js">function Animal (name) {
this.name = name;
}
Animal.prototype.speak = function () {
console.log(this.name + ' makes a noise.');
}
class Dog extends Animal {
speak() {
console.log(this.name + ' barks.');
}
}
var d = new Dog('Mitzie');
d.speak(); // Mitzie barks.</pre>
<p>注意類別並無法擴充一般(non-constructible不可建構的)物件。如果您想要繼承自一般的物件,可以使用{{jsxref("Object.setPrototypeOf()")}}來達成。</p>
<pre class="brush: js">var Animal = {
speak() {
console.log(this.name + ' makes a noise.');
}
};
class Dog {
constructor(name) {
this.name = name;
}
}
// 如果你沒有用以下的方法,當你呼叫speak時會出現TypeError
Object.setPrototypeOf(Dog.prototype, Animal);
var d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.</pre>
<h2 id="Species">Species</h2>
<p>你可能會希望在陣列的衍生類別 <code>MyArray</code> 中回傳陣列 ({{jsxref("Array")}}) ,Species 這個模式讓你能覆寫默認的建構子 (contructor)。</p>
<p>舉例來說,當你使用像 {{jsxref("Array.map", "map()")}} 這類會回傳默認建構子的方法時,你希望能回傳父物件 <code>Array</code> ,而不是 <code>MyArray</code> 物件。 {{jsxref("Symbol.species")}} 符號讓你做到這件事:</p>
<pre class="brush: js">class MyArray extends Array {
// Overwrite species to the parent Array constructor
static get [Symbol.species]() { return Array; }
}
var a = new MyArray(1,2,3);
var mapped = a.map(x => x * x);
console.log(mapped instanceof MyArray); // false
console.log(mapped instanceof Array); // true</pre>
<h2 id="用_super_呼叫父類別">用 <code>super</code> 呼叫父類別</h2>
<p>關鍵字 <code><a href="/en-US/docs/Web/JavaScript/Reference/Operators/super">super</a></code> 是用來提供一個類別呼叫其父類別的函數。</p>
<pre class="brush: js">class Cat {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name + ' makes a noise.');
}
}
class Lion extends Cat {
speak() {
super.speak();
console.log(this.name + ' roars.');
}
}
var l = new Lion('Fuzzy');
l.speak();
// Fuzzy makes a noise.
// Fuzzy roars.
</pre>
<h2 id="ES5_繼承語法與_ES6_類別語法的比較">ES5 繼承語法與 ES6 類別語法的比較</h2>
<p>TBD</p>
<h2 id="範例">範例</h2>
<p>TBD</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('ES6', '#sec-class-definitions', 'Class definitions')}}</td>
<td>{{Spec2('ES6')}}</td>
<td>Initial definition.</td>
</tr>
</tbody>
</table>
<h2 id="瀏覽器相容性">瀏覽器相容性</h2>
<p>{{CompatibilityTable}}</p>
<div>
<table class="compat-table">
<tbody>
<tr>
<th>Feature</th>
<th>Chrome</th>
<th>Firefox (Gecko)</th>
<th>Edge</th>
<th>Internet Explorer</th>
<th>Opera</th>
<th>Safari</th>
</tr>
<tr>
<td>Basic support</td>
<td>{{CompatChrome(42.0)}}[1]<br>
{{CompatChrome(49.0)}}</td>
<td>{{CompatGeckoDesktop(45)}}</td>
<td>13</td>
<td>{{CompatNo}}</td>
<td>{{CompatNo}}</td>
<td>{{CompatSafari(9.0)}}</td>
</tr>
</tbody>
</table>
</div>
<div>
<table class="compat-table">
<tbody>
<tr>
<th>Feature</th>
<th>Android</th>
<th>Firefox Mobile (Gecko)</th>
<th>IE Mobile</th>
<th>Opera Mobile</th>
<th>Safari Mobile</th>
<th>Chrome for Android</th>
</tr>
<tr>
<td>Basic support</td>
<td>{{CompatNo}}</td>
<td>{{CompatGeckoMobile(45)}}</td>
<td>{{CompatUnknown}}</td>
<td>{{CompatUnknown}}</td>
<td>9</td>
<td>{{CompatChrome(42.0)}}[1]<br>
{{CompatChrome(49.0)}}</td>
</tr>
</tbody>
</table>
</div>
<h2 id="參見">參見</h2>
<ul>
<li><a href="/en-US/docs/Web/JavaScript/Reference/Functions">Functions</a></li>
<li><a href="/en-US/docs/Web/JavaScript/Reference/Statements/class"><code>class</code> declaration</a></li>
<li><a href="/en-US/docs/Web/JavaScript/Reference/Operators/class"><code>class</code> expression</a></li>
<li>{{jsxref("Operators/super", "super")}}</li>
<li><a href="https://hacks.mozilla.org/2015/07/es6-in-depth-classes/">Blog post: "ES6 In Depth: Classes"</a></li>
<li><a href="/zh-TW/docs/Web/JavaScript/Reference/Classes/extends">extends</a></li>
</ul>
|