-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path16-6.html
500 lines (460 loc) · 16.3 KB
/
16-6.html
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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="./public/favicon.ico" />
<meta http-equiv="cache-control" content="no-cache" />
<title></title>
<link rel="stylesheet" href=" https://necolas.github.io/normalize.css/8.0.1/normalize.css" />
<link rel="stylesheet" href="./hightlight/default.min.css" />
<link rel="stylesheet" href="./css/main.css" />
<link rel="stylesheet" href="./css/copybutton.css" />
<link rel="stylesheet" href="./css/hightlight.css" />
<script src="./hightlight/hightlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.11/clipboard.min.js"></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-BEVZJDBC7Z"></script>
<script src="./js/gtag.js"></script>
</head>
<body>
<header>
<nav>
<h1>
<span id="toggle-menu"></span>
<a href="index.html"></a>
</h1>
</nav>
</header>
<main>
<aside>
<nav></nav>
</aside>
<article>
<h2 id="16-6">16-6 Private class features</h2>
<p>
Class 欄位預設是公有的。我們可以另外透過前綴
<code>#</code>
讓 Class 成員轉換為私有。 私有屬性 Class 的封裝會被 Javascript 自動強制執行。
</p>
<h3>語法</h3>
<pre><code class="language-js">
class ClassWithPrivate {
#privateField;
#privateFieldWithInitializer = 42;
#privateMethod() {
// …
}
static #privateStaticField;
static #privateStaticFieldWithInitializer = 42;
static #privateStaticMethod() {
// …
}
}
</code></pre>
<p>以下列舉額外的語法限制</p>
<ul>
<li>
所有私有類別中的識別符號都必須是唯一的。靜態屬性和實例屬性共用命名欄位,唯一的例外是定義一組
getter/setter。
</li>
<li>
私有的識別符號不能為
<code>#constructor</code>
</li>
</ul>
<p>大部分 Class 特性都有專屬的私有型態:</p>
<ul>
<li>Private fields</li>
<li>Private methods</li>
<li>Private static fields</li>
<li>Private static methods</li>
<li>Private getters</li>
<li>Private setters</li>
<li>Private static getters</li>
<li>Private static setters</li>
</ul>
<p>然而在 Javascript 裡 ,建構子是無法私有化的。</p>
<p>
以下有一些錯誤示範: 一個是使用
<code>delete</code>
,另個一個則是在 Class 私有欄位外去改變私有特性。
</p>
<pre><code class="language-js error-code">
class ClassWithPrivateField {
#privateField;
constructor() {;
delete this.#privateField; // Syntax error
this.#undeclaredField = 42; // Syntax error
}
}
const instance = new ClassWithPrivateField();
instance.#privateField; // Syntax error
</code></pre>
<p>
JavaScript 作為一種動態語言,由於特殊的 Hash
標識符語法而能夠執行這種編譯時檢查,使其在語法級別上不同於普通屬性。
</p>
<div class="notecard">
<p>
<strong>注意:</strong>
在 Chrome 控制台中運行的代碼可以訪問類外部的私有屬性。這是對 JavaScript 語法限制的僅限
DevTools 的放寬。
</p>
</div>
<p>
如果從不具有該屬性的物件訪問私有屬性,則會拋出 TypeError 而不是 undefined
像普通屬性那樣回傳。
</p>
<pre><code class="language-js error-code">
class C {
#x;
static getX(obj) {
return obj.#x;
}
}
console.log(C.getX(new C())); // undefined
console.log(C.getX({})); // TypeError: Cannot read private member #x from an object whose class did not declare it
</code></pre>
<p>
上面案例也演示了,在生成實體以後我們仍然可以透過靜態方法訪問私有屬性。若直接操作 class
則無法訪問。
</p>
<p>
我們也可以透過
<code>in</code>
運算子去檢查是否有私有屬性。 有的話回傳
<code>true</code>
,否則
<code>false</code>
。
</p>
<pre><code class="language-js correct-code">
class C {
#x;
constructor(x) {
this.#x = x;
}
static getX(obj) {
if (#x in obj) return obj.#x;
return "obj must be an instance of C";
}
}
console.log(C.getX(new C("foo"))); // "foo"
console.log(C.getX(new C(0.196))); // 0.196
console.log(C.getX(new C(new Date()))); // the current date and time
console.log(C.getX({})); // "obj must be an instance of C"
</code></pre>
<p>
請注意,私有名稱按推論始終是預先聲明且不可刪除的:如果您發現一個物件擁有當前類的一個私有屬性(無論是來自
<code>try...catch</code>
還是
<code>in</code>
check),它必須擁有所有其他私人屬性。擁有類的私有屬性的物件通常意味著它是由該類生成的(儘管並非總是如此)。
</p>
<p>
私有屬性不是原型繼承模型的一部分,因為它們只能在當前類的主體內訪問,並且不能由子類繼承。不同類中具有相同名稱的私有屬性完全不同,並且不能互操作。將它們視為附加到每個實例的外部元數據,由類管理。因此,
<code>Object.freeze()</code>
和
<code>Object.seal()</code>
對私有屬性沒有影響。
</p>
<h3>範例</h3>
<h4>Private fields 私有欄位</h4>
<p>
私有欄位包含 私有實例欄位(private instance fields) 以及 私有靜態欄位(private static
fields)。 Class 內部宣告私有欄位的才能被訪問。
</p>
<h4>Private instance fields 私有實例欄位</h4>
<p>就像他們的相對應的公有欄位私有靜態欄位:</p>
<ul>
<li>
在建構子執行前就被加入或者在子類別中的
<code>super</code>
被觸發後立即執行。
</li>
<li>只能在 class 實例裡面被訪問</li>
</ul>
<pre><code class="language-js">
class ClassWithPrivateField {
#privateField;
constructor() {
this.#privateField = 42;
}
}
class Subclass extends ClassWithPrivateField {
#subPrivateField;
constructor() {
super();
this.#subPrivateField = 23;
}
}
new Subclass(); // In some dev tools, it shows Subclass {#privateField: 42, #subPrivateField: 23}
</code></pre>
<div class="notecard">
<p>
<strong>注意:</strong>
來自
<code>#ClassWithPrivateField</code>
的
<code>#privateField</code>
基本 class 對
<code>#ClassWithPrivateField</code>
來說是私有的, 不能從衍生的子類訪問。
</p>
</div>
<h3>Returning overriding object 回傳覆寫物件</h3>
<p>
一個類別的建構子可以回傳不同的物件,這個物件會被衍生的 class 建構子當作新的
<code>this</code>
。這個衍生的 class
可能會定義私有的欄位在回傳的物件上。這意味著衍生物件可能標記私有欄位給不相關的物件上。
</p>
<pre><code class="language-js">
class Stamper extends class {
// A base class whose constructor returns the object it's given
constructor(obj) {
return obj;
}
} {
// This declaration will "stamp" the private field onto the object
// returned by the base class constructor
#stamp = 42;
static getStamp(obj) {
return obj.#stamp;
}
}
const obj = {};
new Stamper(obj);
// `Stamper` calls `Base`, which returns `obj`, so `obj` is
// now the `this` value. `Stamper` then defines `#stamp` on `obj`
console.log(obj); // In some dev tools, it shows {#stamp: 42}
console.log(Stamper.getStamp(obj)); // 42
console.log(obj instanceof Stamper); // false
// You cannot stamp private properties twice
new Stamper(obj); // Error: Initializing an object twice is an error with private fields
</code></pre>
<div class="notecard warning">
<p>
警告:在操作上可能會有一些讓人困惑。一般來說你會建議避免從建構子回傳任何東西,尤其是跟
<code>this</code>
無關的東西。
</p>
</div>
<h3>Private static fields 私有靜態欄位</h3>
<p>類似相對應的公有欄位,私有靜態欄位有以下特性:</p>
<ul>
<li>編譯階段被加入至構造函式中</li>
<li>只能由本身的 class 訪問</li>
</ul>
<pre><code class="language-js">
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
return ClassWithPrivateStaticField.#privateStaticField;
}
}
console.log(ClassWithPrivateStaticField.publicStaticMethod()); // 42
</code></pre>
<p>
class 靜態私有欄位的限制:只有定義了靜態私有欄位的 class 能訪問到。使用
<code>this</code>
很有可能導致非預期錯誤。舉個例子:
<code>this</code>
指的是
<code>Subclass</code>
,(不是
<code>ClassWithPrivateStaticField</code>
) 當我們呼叫
<code>Subclass.publicStaticMethod()</code>
的時候會造成
<code>TypeError</code>
。
</p>
<pre><code class="language-js">
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
return this.#privateStaticField;
}
}
class Subclass extends ClassWithPrivateStaticField {}
Subclass.publicStaticMethod(); // TypeError: Cannot read private member #privateStaticField from an object whose class did not declare it
</code></pre>
<p>
呼叫
<code>super</code>
也會有相同的問題。因為
<code>super</code>
方法無法以
<code>super</code>
類別當作
<code>this</code>
的情況被呼叫。
</p>
<pre><code class="language-js">
class ClassWithPrivateStaticField {
static #privateStaticField = 42;
static publicStaticMethod() {
// When invoked through super, `this` still refers to Subclass
return this.#privateStaticField;
}
}
class Subclass extends ClassWithPrivateStaticField {
static callSuperMethod() {
return super.publicStaticMethod();
}
}
Subclass.callSuperMethod(); // TypeError: Cannot read private member #privateStaticField from an object whose class did not declare it
</code></pre>
<p>
因此你會建議盡量透過 class 名稱去訪問靜態私有變數而不是透過
<code>this</code>
。所以繼承不會打破這個方法。
</p>
<h3>Private methods 私有方法</h3>
<p>私有方法包括私有私有實例方法以及私有靜態方法。私有方法只能在類別宣告中被訪問</p>
<h3>Private instance methods 私有實例方法</h3>
<p>與相對應的公有方法不同,私有實例方法:</p>
<ul>
<li>在實例欄位被掛載之前會立即掛載</li>
<li>
僅適用於該類的實例,不適用於其
<code>.prototype</code>
屬性。
</li>
</ul>
<pre><code class="language-js">
class ClassWithPrivateMethod {
#privateMethod() {
return 42;
}
publicMethod() {
return this.#privateMethod();
}
}
const instance = new ClassWithPrivateMethod();
console.log(instance.publicMethod()); // 42
</code></pre>
<p>
私有實例方法可以是生成器、異步或異步生成器函數。私有
<code>getter</code>
和
<code>setter</code>
也是可能的,並且遵循與其對應的公共
<code>getter</code>
和
<code>setter</code>
相同的語法要求。
</p>
<pre><code class="language-js">
class ClassWithPrivateAccessor {
#message;
get #decoratedMessage() {
return `🎬${this.#message}🛑`;
}
set #decoratedMessage(msg) {
this.#message = msg;
}
constructor() {
this.#decoratedMessage = "hello world";
console.log(this.#decoratedMessage);
}
}
new ClassWithPrivateAccessor(); // 🎬hello world🛑
</code></pre>
<p>
與公有方法不同,私有方法無法通過其類的
<code>.prototype</code>
屬性訪問。
</p>
<pre><code class="language-js">
class C {
#method() {}
static getMethod(x) {
return x.#method;
}
}
console.log(C.getMethod(new C())); // [Function: #method]
console.log(C.getMethod(C.prototype)); // TypeError: Receiver must be an instance of class C
</code></pre>
<h3>Private static methods 私有靜態方法</h3>
<p>與公共方法一樣,私有靜態方法:</p>
<ul>
<li>在類別編譯時被添加到類別的構造函數中</li>
<li>僅適用於類別本身。</li>
</ul>
<pre><code class="language-js">
class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 42;
}
static publicStaticMethod() {
return ClassWithPrivateStaticMethod.#privateStaticMethod();
}
}
console.log(ClassWithPrivateStaticMethod.publicStaticMethod()); // 42
</code></pre>
<p>私有靜態方法可以是生成器、異步和異步生成器函數。</p>
<p>
前面提到的私有靜態欄位的相同限制也適用於私有靜態方法,並且類似地在使用它時可能會導致意外的行為。在下面的範例中,當我們嘗試調用
<code>Subclass.publicStaticMethod()</code>
時,它引用的是
<code>Subclass</code>
類(而不是
<code>ClassWithPrivateStaticMethod</code>
類),因此會導致
<code>TypeError</code>
。
</p>
<pre><code class="language-js">
class ClassWithPrivateStaticMethod {
static #privateStaticMethod() {
return 42;
}
static publicStaticMethod() {
return this.#privateStaticMethod();
}
}
class Subclass extends ClassWithPrivateStaticMethod {}
console.log(Subclass.publicStaticMethod()); // TypeError: Cannot read private member #privateStaticMethod from an object whose class did not declare it
</code></pre>
<h3>Simulating private constructors 模擬私有建構子</h3>
<p>
許多其他語言都包含將構造函數標記為私有的功能,這可以防止類在類本身之外實例化 -
您只能使用創建實例的靜態工廠方法,或者根本無法創建實例。 JavaScript
沒有本地方法來執行此操作,但可以通過使用私有靜態標誌來完成。
</p>
<pre><code class="language-js">
class PrivateConstructor {
static #isInternalConstructing = false;
constructor() {
if (!PrivateConstructor.#isInternalConstructing) {
throw new TypeError("PrivateConstructor is not constructable");
}
PrivateConstructor.#isInternalConstructing = false;
// More initialization logic
}
static create() {
PrivateConstructor.#isInternalConstructing = true;
const instance = new PrivateConstructor();
return instance;
}
}
new PrivateConstructor(); // TypeError: PrivateConstructor is not constructable
PrivateConstructor.create(); // PrivateConstructor {}
</code></pre>
<p>資料來源:</p>
<p>
<a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields"
>
https://developer.mozilla.org/.../Private_class_fields
</a>
</p>
</article>
</main>
</body>
<script type="module" src="./js/main.js"></script>
</html>