aboutsummaryrefslogtreecommitdiff
path: root/files/zh-tw/web/javascript/a_re-introduction_to_javascript/index.html
blob: 97ff02731233c3129e8a3d12f46f40ca17b48ecf (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
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
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
---
title: 重新介紹 JavaScript
slug: Web/JavaScript/A_re-introduction_to_JavaScript
tags:
  - JavaScript
  - 待翻譯
  - 所有類別
translation_of: Web/JavaScript/A_re-introduction_to_JavaScript
---
<p>{{jsSidebar}}</p>

<h2 id="介紹">介紹</h2>

<p>為何需要重新介紹?因為 <a href="/zh_tw/JavaScript" title="zh_tw/JavaScript">JavaScript</a> 堪稱是<a class="external" href="http://javascript.crockford.com/javascript.html">全世界最被人誤解的程式語言</a>。儘管 JavaScript 再怎麼的被嘲諷為小兒科,在它誤導人的簡潔下隱藏著強大的語言功能。2005 年是個許多知名 JavaScript 應用程式推出的年度,在在證明:更加瞭解這項科技對任何網頁開發者來說皆是重要的技能。</p>

<p>先從該語言的歷史說起。1995 年,Brendan Eich,一位 Netscape (網景)的工程師,創造了 JavaScript。1996 年初,JavaScript 隨著 Netscape 2 首次推出。它原本要被命名為 LiveScript,結果因為行銷策略為了強調昇陽的 Java 程式語言的普遍性,而不幸的被改名 — 即便兩者之間沒有太大的關係。從此之後,這便成為了混淆的元兇。</p>

<p>Microsoft 在幾個月後隨著 IE 3 推出了跟該語言大致上相容的 JScript。Netscape 在 1997 年將該語言送交 <a class="external" href="http://www.ecma-international.org/">ECMA International</a>,一個歐洲標準化組織,而在 1997 年的時候產生了初版的 <a href="/zh_tw/ECMAScript" title="zh_tw/ECMAScript">ECMAScript</a>。該標準在 1999 年的時候以 <a class="external" href="http://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMAScript 第三版</a>的形式推出了更新,從此之後大致上都相當穩定,不過近期有在研發第四版。</p>

<p>這個穩定性對開發者來說是相當好的事情,因為它讓不少實作 (implementation) 有時間慢慢趕上。我會把重點放在第三版的語法。為了避免混淆,我會繼續使用 JavaScript 這個名稱。</p>

<p>與其他程式語言大大不同的是,JavaScript 沒有任何輸入或輸出的觀念。它是被設計成在一個宿主 (host) 環境下執行的腳本 (script) 語言,所以任何與外界通訊的方式,都是宿主環境的責任。瀏覽器是最常見的宿主環境,不過有些程式也有 JavaScript 解釋器,如 Adobe Acrobat、Photoshop、以及 Yahoo! Widget Engine 等等。</p>

<h3 id=".E6.A6.82.E8.A6.81" name=".E6.A6.82.E8.A6.81">概要</h3>

<p>先從任何語言最基本的方面講起:型態 (type)。JavaScript 程式可以改變「值」(value),而這些值各自有其歸屬的型態。JavaScript 的型態有:</p>

<ul>
 <li><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Number" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Number">Number</a> (數字)</li>
 <li><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/String" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/String">String</a> (字串)</li>
 <li><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Boolean" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Boolean">Boolean</a> (布林值)</li>
 <li><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Function" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Function">Function</a> (函式)</li>
 <li><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Object" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Object">Object</a> (物件)</li>
</ul>

<p>...以及稍微怪一點的 undefined (未定義)和 null (空)。還有,算是一種特殊物件的 <a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Array" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Array">Array</a> (陣列)。還有,額外的特殊物件 <a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Date" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Date">Date</a> (日期)以及 <a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/RegExp" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/RegExp">Regular Expression</a>。另外,如果真的要達到技術上的準確性,連函式也只算是一種特殊的物件。所以型態分佈表看起來應該像這樣:</p>

<ul>
 <li>Number (數字)</li>
 <li>String (字串)</li>
 <li>Boolean (布林)</li>
 <li>Object (物件)
  <ul>
   <li>Function (函式)</li>
   <li>Array (陣列)</li>
   <li>Date (日期)</li>
   <li>RegExp</li>
  </ul>
 </li>
 <li>Null (空)</li>
 <li>Undefined (未定義)</li>
</ul>

<p>其實也有內建的 <a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Error" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Error">Error</a> (錯誤)類型,不過,先把重點放在上面的分佈表比較容易。</p>

<h3 id=".E6.95.B8.E5.AD.97" name=".E6.95.B8.E5.AD.97">數字</h3>

<p>根據規格,JavaScript 數字算是「雙精確度 64 位元格式 IEEE 754 值」("double-precision 64-bit format IEEE 754 values")。這能造成一些有趣的後果。JavaScript 沒有所謂的整數,所以你在做算術的時候得小心一點,尤其是假如你習慣了 C 或 Java 的數學。小心類似下的的事情:</p>

<pre class="brush: js">0.1 + 0.2 = 0.30000000000000004
</pre>

<p>JavaScript 支援標準的<a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/Arithmetic_Operators" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/Arithmetic_Operators">數字運算子</a>,包含加法、減法、取餘數、等算術。另外還有一個之前忘記提的內建物件,<a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Math" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Math">Math</a> (數學),用以處理較為進階的數學函數和常數:</p>

<pre class="brush: js">Math.sin(3.5);
d = Math.PI * r * r;
</pre>

<p>你可以用內建的 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Functions/parseInt" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Functions/parseInt">parseInt()</a></code> 函式將字串轉成整數。這個函式有個可選用的第二個參數(在此建議你一定要指定),用以指定進位數。</p>

<pre class="brush: js">&gt; parseInt("123", 10)
123
&gt; parseInt("010", 10)
10
</pre>

<p>如果你不指定進位數,就有可能得到意想不到的結果:</p>

<pre class="brush: js">&gt; parseInt("010")
8
</pre>

<p>這是因為 <code>parseInt</code> 函數把字串當成八進位的數字,因為開頭有個 0。</p>

<p>如果要把二進位的數字轉成整數,只要把進位數改掉就行了:</p>

<pre class="brush: js">&gt; parseInt("11", 2)
3
</pre>

<p>有個特殊的數字,叫做 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Properties/NaN" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Properties/NaN">NaN</a></code> (「Not a Number」,「非數字」的簡稱),假如遞進去的字串不是數字,則會回傳 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Properties/NaN" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Properties/NaN">NaN</a></code></p>

<pre class="brush: js">&gt; parseInt("hello", 10)
NaN
</pre>

<p><code>NaN</code> 很毒:將其進行任何數學運算,結果仍會是 <code>NaN</code></p>

<pre class="brush: js">&gt; NaN + 5
NaN
</pre>

<p>你可以用內建的 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Functions/isNaN" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Functions/isNaN">isNaN()</a></code> 函式來判斷一個值是否為 <code>NaN</code></p>

<pre class="brush: js">&gt; isNaN(NaN)
true
</pre>

<p>JavaScript 也有特殊的值 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Properties/Infinity" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Properties/Infinity">Infinity</a></code> (無限)以及 <code>-Infinity</code> (負無限):</p>

<pre class="brush: js">&gt; 1 / 0
Infinity
&gt; -1 / 0
-Infinity
</pre>

<h3 id=".E5.AD.97.E4.B8.B2" name=".E5.AD.97.E4.B8.B2">字串</h3>

<p>JavaScript 的字串是一序列的字元。更精確的說,是一序列的 <a href="/zh_tw/Core_JavaScript_1.5_Guide/Unicode" title="zh_tw/Core_JavaScript_1.5_Guide/Unicode">Unicode 字元</a>,每個字元皆以一個 16 位元的數字作為代表。這讓任何人不需要為國際化感到擔心。</p>

<p>如果你要代表一個單一字元,用長度為 1 的字串即可。</p>

<p>要得知一個字串的長度,請存取該字串的 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/String/length" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/String/length">length</a></code>(長度)屬性:</p>

<pre class="brush: js">&gt; "hello".length
5
</pre>

<p>剛剛可是與 JavaScript 物件的第一次接觸呢!字串也是物件喔。字串甚至也有<a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/String#Methods" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/String#Methods">方法 (method)</a></p>

<pre class="brush: js">&gt; "hello".charAt(0) //位置 0 的字元
h
&gt; "hello, world".replace("hello", "goodbye") //把 hello 換成 goodbye
goodbye, world
&gt; "hello".toUpperCase() //轉成大寫
HELLO
</pre>

<h3 id=".E5.85.B6.E4.BB.96.E9.A1.9E.E5.9E.8B" name=".E5.85.B6.E4.BB.96.E9.A1.9E.E5.9E.8B">其他類型</h3>

<p>JavaScript 對下列兩者是有分別的:<code>null</code> (空),屬於「object」型態的一種物件,用以明言表示無數值,以及 <code>undefined</code> (無定義),屬於「undefined」類型的一種物件,用以表示未初始化的值,也就是說,根本還沒指定數值。雖然姑且先不論變數,但在 JavaScript 你可以宣告一個變數但不指定其值。如果你這麼做的話,那該變數的型態便是 <code>undefined</code></p>

<p>JavaScript 有布林 (boolean) 型態,可能的值有 <code>true</code> (真)與 <code>false</code> (假)且兩者皆為關鍵字。根據下列規則,任何值都可以被轉換成布林值:</p>

<ol>
 <li><code>false</code><code>0</code>、空字串 (<code>""</code>)、<code>NaN</code><code>null</code>、以及 <code>undefined</code> 都會成為 <code>false</code></li>
 <li>所有其他的值都會成為 <code>true</code></li>
</ol>

<p>你可以用 <code>Boolean()</code> 函式特別進行轉換:</p>

<pre class="brush: js">&gt; Boolean("")
false
&gt; Boolean(234)
true
</pre>

<p>不過很少需要這樣,因為假如 JavaScript 遇到需要接收布林值的時候,如 <code>if</code> 陳述式(見下),便會無聲無息的進行布林轉換。有鑑於此,常常會有「真值」("true values") 與「假值」("false values") 等說法,意思是指值在布林轉換過程中會被轉成 <code>true</code> 或是 <code>false</code>。這種值也有被稱為「真的」("truthy") 或「假的」("falsy")。</p>

<p>JavaScript 支援布林運算,如 <code>&amp;&amp;</code> (邏輯「<em></em>」,英稱 <em>and</em>)、<code>||</code> (邏輯「<em></em>」,英稱 <em>or</em>)、以及 <code>!</code> (邏輯「<em></em>」,英稱 <em>not</em>),請見下。</p>

<h3 id=".E8.AE.8A.E6.95.B8" name=".E8.AE.8A.E6.95.B8">變數</h3>

<p>在 JavaScript,要宣告新變數,使用的是 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Statements/var" title="zh_tw/Core_JavaScript_1.5_Reference/Statements/var">var</a></code> 關鍵字:</p>

<pre class="brush: js">var a;
var name = "simon";
</pre>

<p>如果你宣告一個變數但不指定任何值,其型態便為 <code>undefined</code> (未定義)。 <span class="comment">應該注意關於 JS 的區塊不會構成新變數作用域</span></p>

<h3 id=".E9.81.8B.E7.AE.97.E5.AD.90" name=".E9.81.8B.E7.AE.97.E5.AD.90">運算子</h3>

<p>JavaScript 的數字運算子有 <code>+</code><code>-</code><code>*</code><code>/</code>、以及 <code>%</code> - 最後一個是取餘數的運算子(英稱 <em>mod</em>)。用來指定值的運算子是 <code>=</code>,另外還有複合指定陳述式,如 <code>+=</code> 以及 <code>-=</code>。這些是用以延伸 <code>x = x <em>運算子</em> y</code></p>

<pre class="brush: js">x += 5
x = x + 5
</pre>

<p>你可以用 <code>++</code><code>--</code> 來分別增加或是減少數值。這些運算子可以放在變數的開頭或結尾。</p>

<p><a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/String_Operators" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/String_Operators"><code>+</code> 運算子</a>也能把字串連接 (concatenate) 起來:</p>

<pre class="brush: js">&gt; "hello" + " world"
hello world
</pre>

<p>如果你把一字串加到一個數字(或其他數值),會先把所有的東西轉成字串。這會讓你意想不到:</p>

<pre class="brush: js">&gt; "3" + 4 + 5
345
&gt; 3 + 4 + "5"
75
</pre>

<p>把一個空字串加到一個東西是個將其轉成字串的好方法之一。</p>

<p>JavaScript 中進行<a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/Comparison_Operators" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/Comparison_Operators">比較</a>可以用 <code>&lt;</code><code>&gt;</code><code>&lt;=</code>、以及 <code>&gt;=</code>。這些對字串和數字都有用。等值比較 (equality) 比較沒那麼直接。雙等號運算子(等於)會進行型態強制轉換,假如比較的資料型態不一樣,有時結果會相當有趣:</p>

<pre class="brush: js">&gt; "dog" == "dog"
true
&gt; 1 == true
true
</pre>

<p>要避免型態強制轉換,要用三等號運算子(絕對等於):</p>

<pre class="brush: js">&gt; 1 === true
false
&gt; true === true
true
</pre>

<p>另外還有 <code>!=</code> (不等於)以及 <code>!==</code> (絕對不等於)運算子。</p>

<p>假如你需要用的話,JavaScript 也有<a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/Bitwise_Operators" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/Bitwise_Operators">逐位元 (bitwise) 運算子</a></p>

<h3 id=".E6.8E.A7.E5.88.B6.E7.B5.90.E6.A7.8B" name=".E6.8E.A7.E5.88.B6.E7.B5.90.E6.A7.8B">控制結構</h3>

<p>JavaScript 跟其他同屬 C 家族的程式語言有類似的控制結構。條件陳述式是靠 <code>if</code> 以及 <code>else</code> 來表示。如果你喜歡還可以串起來:</p>

<pre class="brush: js">var name = "貓咪";
if (name == "狗狗") {
  name += "!";
} else if (name == "貓咪") {
  name += "!!";
} else {
  name = "!" + name;
}
name == "貓咪!!"
</pre>

<p>JavaScript 有 <code>while</code> 迴圈以及 <code>do-while</code> 迴圈。前者適合做基本的迴圈,而後者是當你要迴圈至少執行一次就需要用到:</p>

<pre class="brush: js">while (true) {
  // 無限迴圈!
}

do {
  var input = get_input();
} while (inputIsNotValid(input))
</pre>

<p>JavaScript 的 <code>for</code> 迴圈跟 C 和 Java 的一樣:可以讓你用一行就提供控制的條件與資訊。</p>

<pre class="brush: js">for (var i = 0; i &lt; 5; i++) {
  // 會執行 5 次
}
</pre>

<p><code>&amp;&amp;</code> 以及 <code>||</code> 運算子用的是「短路邏輯」(short-circuit logic),也就是說,第二個運算值是否會被執行靠的是第一個運算值。這用來在存取一個物件的屬性前檢查物件是否為空 (null) 非常有用:</p>

<pre class="brush: js">var name = o &amp;&amp; o.getName();
</pre>

<p>或是用來設預設值:</p>

<pre class="brush: js">var name = otherName || "預設";
</pre>

<p>JavaScript 也有三元運算子 (tertiary operator),可以用來寫單行的條件陳述式:</p>

<pre class="brush: js">var allowed = (age &gt; 18) ? "是" : "否";
</pre>

<p>switch 陳述式可以根據一個數字或字串做不同決定:</p>

<pre class="brush: js">switch(action) {
    case '畫':
        drawit(); //開始畫
        break; //中斷
    case '吃':
        eatit(); //開始吃
        break; //中斷
    default: //預設
        donothing(); //不做任何事
}
</pre>

<p>如果你不加個 <code>break</code> (中斷)陳述式,執行的程式碼會往下「掉」一層。你很少會需要這樣 - 不過假如你真的要這樣,最好用個註解說明一下,這樣才方便除錯:</p>

<pre class="brush: js">switch(a) {
    case 1: //往下「掉」一層
    case 2:
        eatit(); //開始吃
        break; //中斷
    default: //預設
        donothing(); //不做任何事
}
</pre>

<p>default 子句 (clause) 是選擇性的,可有可無。如果你喜歡的話,你可以在 switch 部分或 case 部分放表達式 (expression);兩者之間的比較使用的會是 <code>===</code> 運算子:</p>

<pre class="brush: js">switch(1 + 3){
    case 2 + 2:
        yay(); //耶!
        break; //中斷
    default: //預設
        neverhappens(); //根本不會發生
}
</pre>

<h3 id=".E7.89.A9.E4.BB.B6" name=".E7.89.A9.E4.BB.B6">物件</h3>

<p>JavaScript 物件是一系列的「名稱對數值組合」(name-value pair)。有鑑於此,它們和下列的東西很相近:</p>

<ul>
 <li>Python 的 dictionary (字典)</li>
 <li>Perl 和 Ruby 的 hash (雜湊)</li>
 <li>C 和 C++ 的 hash table (雜湊表)</li>
 <li>Java 的 HashMap (雜湊地圖)</li>
 <li>PHP 的 associative array (聯合陣列)</li>
</ul>

<p>這種資料結構的普遍性證明了其多樣性。由於 JavaScript 的任何東西(核心類型以外)都是物件,任何 JavaScript 程式都自然而然的用到許多雜湊表查詢。還好這些查詢的速度都很快!</p>

<p>「名稱」的部分是個 JavaScript 字串,而「數值」可以是任何 JavaScript 值--包括物件。這可以讓你隨心所欲的建構複雜的資料結構。</p>

<p>建立空物件有種基本方法:</p>

<pre class="brush: js">var obj = new Object();
</pre>

<p>以及:</p>

<pre class="brush: js">var obj = {};
</pre>

<p>這兩者在意義上相等;後者叫做物件實體語法 (object literal),比較方便。早期並沒有物件實體語法,也就是為何許多程式碼用的還是舊的方法。</p>

<p>一旦建立了,一個物件的屬性可以用兩種方法存取:</p>

<pre class="brush: js">obj.name = "小明"
var name = obj.name;
</pre>

<p>還有...</p>

<pre class="brush: js">obj["name"] = "小明";
var name = obj["name"];
</pre>

<p>這兩者也是在意義上相等。第二種方法的優點是屬性的名稱可以在執行的時候以字串提供,也就是說可以動態的變動。這也可以用來取得和設定名稱是保留關鍵字 (reserved keyword) 的屬性:</p>

<pre class="brush: js">obj.for = "Simon"; //語法錯誤
obj["for"] = "Simon"; //沒問題
</pre>

<p>物件實體語法可以一次把物件完全初始化:</p>

<pre class="brush: js">var obj = {
    name: "胡蘿蔔", //名稱
    "for": "小華", //給誰
    details: { //詳細資訊
        color: "橘", //顏色
        size: 12 //大小
    }
}
</pre>

<p>存取屬性也可以連在一起:</p>

<pre class="brush: js">&gt; obj.details.color
橘
&gt; obj["details"]["size"]
12
</pre>

<h3 id=".E9.99.A3.E5.88.97" name=".E9.99.A3.E5.88.97">陣列</h3>

<p>JavaScript 的陣列其實是一種特殊的物件。它們的運作方式跟正常的物件很像(數字性的屬性只能透過 [] 語法進行存取),不過有個神奇的屬性,叫做「length」(長度)。這個屬性一定是陣列最高索引數加一。</p>

<p>建立陣列的舊方法如下:</p>

<pre class="brush: js">&gt; var a = new Array();
&gt; a[0] = "狗";
&gt; a[1] = "貓";
&gt; a[2] = "雞";
&gt; a.length
3
</pre>

<p>比較方便的語法便是使用陣列實體語法:</p>

<pre class="brush: js">&gt; var a = ["狗", "貓", "雞"];
&gt; a.length
3
</pre>

<p>在陣列實體語法結尾留個空逗點在各瀏覽器間結果參差不齊,所以最好不要這樣:</p>

<pre class="brush: js">&gt; var a = ["狗", "貓", "雞", ]; //最好不要這麼做
</pre>

<p>注意--<code>array.length</code> 不一定是陣列的項目數。比方說:</p>

<pre class="brush: js">&gt; var a = ["狗", "貓", "雞"];
&gt; a[100] = "狐";
&gt; a.length
101
</pre>

<p>別忘了--陣列的 length 就是最高索引數加一。</p>

<p>如果你查詢一個不存在的陣列索引,得到的就是 <code>undefined</code></p>

<pre class="brush: js">&gt; typeof(a[90])
undefined
</pre>

<p>利用上述,便可以像下列一樣在陣列上做迴圈:</p>

<pre class="brush: js">for (var i = 0; i &lt; a.length; i++) {
    //處理 a[i]
}
</pre>

<p>這樣不是很有效率,因為每迴圈一次就會查詢一次 length 屬性。比較好的做法是:</p>

<pre class="brush: js">for (var i = 0, len = a.length; i &lt; len; i++) {
    //處理 a[i]
}
</pre>

<p>一個更棒的寫法是:</p>

<pre class="brush: js">for (var i = 0, item; item = a[i]; i++) {
    //處理 item
}
</pre>

<p>這裡設定了兩個變數。<code>for</code> 迴圈中間指定變數值的部分會被測試是否為「真的」(truthy)--如果成功了,迴圈便會繼續。由於 <code>i</code> 每次都會加一,陣列內的每個項目會被照順序指定到變數 item。當偵測到「假的」(falsy) 項目時(如 <code>undefined</code>)迴圈便會停止。</p>

<p>注意--這個小技巧只該用在你確定不會含有「假的」值的陣列(比如說一陣列的物件或 <a href="/zh_tw/DOM" title="zh_tw/DOM">DOM</a> 節點)。假如你在可能含有 0 的數字資料或可能含有空字串的字串資料上做迴圈,最好還是用 <code>i, j</code> 的方式。</p>

<p>另外一個做迴圈的方法是用 <code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Statements/for...in" title="zh_tw/Core_JavaScript_1.5_Reference/Statements/for...in">for...in</a></code> 迴圈。不過,假如有人用 <code>Array.prototype</code> 新增新的屬性,那些屬性也會被這種迴圈讀到:</p>

<pre class="brush: js">for (var i in a) {
  //處理 a[i]
}
</pre>

<p>假如你要在陣列結尾加入項目,最安全的方法是這樣:</p>

<pre class="brush: js">a[a.length] = item;                 //同 a.push(item);
</pre>

<p>由於 <code>a.length</code> 一定是最高索引數加一,你可以很確定你指定到的是陣列結尾的空間。</p>

<p>陣列附有一些方法 (method):</p>

<pre class="brush: js">a.toString(), a.toLocaleString(), a.concat(item, ..), a.join(sep),
a.pop(), a.push(item, ..), a.reverse(), a.shift(), a.slice(start, end),
a.sort(cmpfn), a.splice(start, delcount, [item]..), a.unshift([item]..)
</pre>

<ul>
 <li><code>concat</code> 結合,會傳回加入了新項目的新陣列</li>
 <li><code>pop</code> 會移除最後一個項目並將其傳回</li>
 <li><code>push</code> 會在結尾加入一或多個項目(就像前面提的 <code>ar.length</code> 方法)</li>
 <li><code>slice</code> 傳回副陣列</li>
 <li><code>sort</code> 進行排序,可選擇性的接受「比較性函數」(comparison function)</li>
 <li><code>splice</code> 讓你透過刪除一個區塊並以更多項目代替來修改陣列</li>
 <li><code>unshift</code> 會在開頭加入一或多個項目</li>
</ul>

<h3 id=".E5.87.BD.E5.BC.8F" name=".E5.87.BD.E5.BC.8F">函式</h3>

<p>如同物件,函式 (function) 是瞭解 JavaScript 的核心元件。最基本的函式再簡單不過了:</p>

<pre class="brush: js">function add(x, y) {
    var total = x + y;
    return total;
}
</pre>

<p>這示範了基本函式的一切。一個 JavaScript 函式可以接受零或多個有名 (named) 參數。函式內文 (body) 要有多少陳述式就有多少陳述式,且可以宣告對於函式而言本地 (local) 的變數。<code>return</code> 陳述式可以在任何時候傳回一值並終止函式。如果沒有 return 陳述式(或者光是 return,沒有值),JavaScript 便會傳回 <code>undefined</code></p>

<p>有名參數比較像是做為參考,而非強制性的。你可以呼叫一個函式但不提供其要求的參數,引此傳入的便是 <code>undefined</code></p>

<pre class="brush: js">&gt; add()
NaN // undefined 不能進行加法
</pre>

<p>你也可以傳入超過函式要求的參數數目:</p>

<pre class="brush: js">&gt; add(2, 3, 4)
5 // 加了前兩數,不理 4
</pre>

<p>這或許有些可笑,但函式在內文內還可以存取一個叫做 <a href="/zh_tw/Core_JavaScript_1.5_Reference/Functions/arguments" title="zh_tw/Core_JavaScript_1.5_Reference/Functions/arguments"><code>arguments</code></a> 的變數,一個類似陣列的物件,內含所有遞給函式的值。改寫一下 add 函式便可以使其接受無限量的值:</p>

<pre class="brush: js">function add() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i &lt; j; i++) {
        sum += arguments[i];
    }
    return sum;
}

&gt; add(2, 3, 4, 5)
14
</pre>

<p>這樣並沒有比直接寫 <code>2 + 3 + 4 + 5</code> 來得有用。寫個算平均的函式吧:</p>

<pre class="brush: js">function avg() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i &lt; j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
&gt; avg(2, 3, 4, 5)
3.5
</pre>

<p>這樣滿有用的,不過又有新問題了。<code>avg()</code> 函式接受的是個逗號分隔的參數清單--不過如果你要取一陣列的平均值呢?你可以把函式重寫成這樣:</p>

<pre class="brush: js">function avgArray(arr) {
    var sum = 0;
    for (var i = 0, j = arr.length; i &lt; j; i++) {
        sum += arr[i];
    }
    return sum / arr.length;
}
&gt; avgArray([2, 3, 4, 5])
3.5
</pre>

<p>但是最好是可以重複利用已經建立好的函式。幸運的是,JavaScript 可以讓你以一陣列的參數來呼叫一個函式。這靠的是使用任何函式物件的 <a href="/zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Function/apply" title="zh_tw/Core_JavaScript_1.5_Reference/Global_Objects/Function/apply"><code>apply()</code></a> 方法。</p>

<pre class="brush: js">&gt; avg.apply(null, [2, 3, 4, 5])
3.5
</pre>

<p><code>apply()</code> 的第二個參數便是做為一系列參數的陣列;第一個參數稍後才會討論。重點是,函式也是物件。</p>

<p>JavaScript 可以讓你建立匿名 (anonymous) 函式。</p>

<pre class="brush: js">var avg = function() {
    var sum = 0;
    for (var i = 0, j = arguments.length; i &lt; j; i++) {
        sum += arguments[i];
    }
    return sum / arguments.length;
}
</pre>

<p>這個語法上和 <code>function avg()</code> 形式相等。這是非常強大的功能,因為你可以藉此在平常該放表達式的地方塞入一個完整的函式定義。這可以讓你用在各種令人拍案叫絕的技巧上。下列可以把本地 (local) 參數「藏」起來--像 C 的 block scope 一樣:</p>

<pre class="brush: js">&gt; var a = 1;
&gt; var b = 2;
&gt; (function() {
    var b = 3;
    a += b;
})();
&gt; a
4
&gt; b
2
</pre>

<p>JavaScript 能讓你遞迴地呼叫函式。這在處理樹狀結構的時候特別有用,比如瀏覽器的 <a href="/zh_tw/DOM" title="zh_tw/DOM">DOM</a>。 </p>

<pre class="brush: js">function countChars(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += countChars(child);
    }
    return count;
}
</pre>

<p>以上揭露了一個使用匿名函式的潛在問題:如果匿名函式沒有名稱,那要怎麼樣遞迴地自我呼叫?答案是使用 <code>arguments</code> 物件。該物件除了提供一系列的參數以外,還提供了一個叫做 <code>arguments.callee</code> 的屬性。這個屬性所指向的是目前的函式,因此可以用來做遞迴的呼叫:</p>

<pre class="brush: js">var charsInBody = (function(elm) {
    if (elm.nodeType == 3) { // TEXT_NODE
        return elm.nodeValue.length;
    }
    var count = 0;
    for (var i = 0, child; child = elm.childNodes[i]; i++) {
        count += arguments.callee(child);
    }
    return count;
})(document.body);
</pre>

<p>由於 <code>arguments.callee</code> 是目前的函式,而所有的函式都是物件,你可以因此用 <code>arguments.callee</code> 來在多次呼叫同個函式之間儲存資料。這個函式會記得它被呼叫過多少次:</p>

<pre class="brush: js">function counter() {
    if (!arguments.callee.count) {
        arguments.callee.count = 0;
    }
    return arguments.callee.count++;
}

&gt; counter()
0
&gt; counter()
1
&gt; counter()
2
</pre>

<h3 id=".E8.87.AA.E8.A8.82.E7.89.A9.E4.BB.B6" name=".E8.87.AA.E8.A8.82.E7.89.A9.E4.BB.B6">自訂物件</h3>

<p>就典型的物件導向程式設計而言,物件是資料 (data) 以及運算該資料的方法 (method) 所構成的集合體 (collection)。我們以一個含有姓與名兩個欄位的「person」(人)物件來做為例子。在英文,一個人的姓名有兩種寫法:「名 姓」或「姓, 名」。利用之前探討的函式與物件,寫法如下:</p>

<pre class="brush: js">function makePerson(first, last) {
    return {
        first: first,
        last: last
    }
}
function personFullName(person) {
    return person.first + ' ' + person.last;
}
function personFullNameReversed(person) {
    return person.last + ', ' + person.first
}
&gt; s = makePerson("Simon", "Willison");
&gt; personFullName(s)
Simon Willison
&gt; personFullNameReversed(s)
Willison, Simon
</pre>

<p>雖然這樣行得通,可是這樣很醜,在全域命名空間 (global namespace) 裡灑了一堆函式。我們需要把函式「附著」(attach) 到物件上。由於函式也是物件,這麼做並不難:</p>

<pre class="brush: js">function makePerson(first, last) {
    return {
        first: first,
        last: last,
        fullName: function() {
            return this.first + ' ' + this.last;
        },
        fullNameReversed: function() {
            return this.last + ', ' + this.first;
        }
    }
}
&gt; s = makePerson("Simon", "Willison")
&gt; s.fullName()
Simon Willison
&gt; s.fullNameReversed()
Willison, Simon
</pre>

<p>這裡出現了之前沒有提過的 '<code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/Special_Operators/this_Operator" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/Special_Operators/this_Operator">this</a></code>' 關鍵字。在一個函式內,「<code>this</code>」 指的是目前的物件。其真正的意義是經由你呼叫函數的方式來指定。如果你透過在物件上使用<a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/Member_Operators" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/Member_Operators">點或中括號記號</a>來呼叫它,這物件就成為「<code>this</code>」。如果在呼叫中沒有使用點記號,「<code>this</code>」就會參考到全域物件. 這經常造成錯誤。舉例來說:</p>

<pre class="brush: js">&gt; s = makePerson("Simon", "Willison")
&gt; var fullName = s.fullName;
&gt; fullName()
undefined undefined
</pre>

<p>當我們呼叫<code>fullName()</code>,「<code>this</code>」被繫結到全域物件。 既然沒有叫做 <code>first</code><code>last</code> 的全域變數,我們取得的都是 <code>undefined</code> 。  </p>

<p>我們可以利用「<code>this</code>」關鍵字的好處來改進我們的 <code>makePerson</code> 函式:</p>

<pre class="brush: js">function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = function() {
        return this.first + ' ' + this.last;
    }
    this.fullNameReversed = function() {
        return this.last + ', ' + this.first;
    }
}
var s = new Person("Simon", "Willison");
</pre>

<p>我們引入了另一個關鍵字:「<code><a href="/zh_tw/Core_JavaScript_1.5_Reference/Operators/Special_Operators/new_Operator" title="zh_tw/Core_JavaScript_1.5_Reference/Operators/Special_Operators/new_Operator">new</a></code>」。 「<code>new」</code>與「<code>this</code>」有強烈的關係。 他的作用是產生一個全新的空物件,然後呼叫指定的函式,使用新物件的作為函式的「<code>this</code>」。 被「new」呼叫的函式叫做「建構子函數」(constructor functions) 。 </p>

<p>此時我們可以發現,每次產生一個新的 Person 物件都會在其內部重新產生兩個新的 function 物件。如果這兩個 function 物件只有一份而讓大家共用不是更好嗎?所以</p>

<pre class="brush: js">function personFullName() {
    return this.first + ' ' + this.last;
}
function personFullNameReversed() {
    return this.last + ', ' + this.first;
}
function Person(first, last) {
    this.first = first;
    this.last = last;
    this.fullName = personFullName;
    this.fullNameReversed = personFullNameReversed;
}
</pre>

<p>好了,如此一來我們達到每一個 Person 物件都共用同一組 function 物件了。</p>

<p>那能不能做的更好呢?</p>

<pre class="brush: js">function Person(first, last) {
    this.first = first;
    this.last = last;
}
Person.prototype.fullName = function() {
    return this.first + ' ' + this.last;
}
Person.prototype.fullNameReversed = function() {
    return this.last + ', ' + this.first;
}
</pre>

<p>Person.prototype 是一個由所有 Person 物件共享的物件。他將產生一個可供查看的關係鍊 (有個特殊的名字 <span style="color: #0000cd;">prototype chain</span>)。任何時候當我們想要使用某個不在 Person 中定義的 property 時,JavaScript 就會到 Person.prototype 裡頭尋找。因此, Person.prototype 就成為一個所有 Person 物件共用且可視的一個共享空間(物件)。這是一個提供強大工具,允許你可以在執行的任何一刻增加物件的相關函式。</p>

<pre class="brush: js">&gt; s = new Person("Simon", "Willison");
&gt; s.firstNameCaps();
TypeError on line 1: s.firstNameCaps is not a function
&gt; Person.prototype.firstNameCaps = function() {
    return this.first.toUpperCase()
}
&gt; s.firstNameCaps()
SIMON
</pre>

<p>另外,有趣的是,在 JavaScript 中,你不只能加入東西到你自創的物件,甚至可以加入到 JavaScript 原生的物件中!如下面舉例:</p>

<pre class="brush: js">&gt; var s = "Simon";
&gt; s.reversed()
TypeError on line 1: s.reversed is not a function
&gt; String.prototype.reversed = function() {
    var r = "";
    for (var i = this.length - 1; i &gt;= 0; i--) {
        r += this[i];
    }
    return r;
}
&gt; s.reversed()
nomiS
</pre>

<p>我們新加入 String 的 reversed 函式亦可反應在字串物件上!如下所示:</p>

<pre class="brush: js">&gt; "This can now be reversed".reversed()
desrever eb won nac sihT
</pre>

<p>承接以前提過的,prototype 會是 chain 的一部分,是以很直觀的,根就是 Object.prototype 了。在 Object.prototype 提供的眾多函式中包含一個 toString() - 他在當你創建一個字串物件時被呼叫。因此我們可以利用他來對我們的 Person 物件 debug:</p>

<pre class="brush: js">&gt; var s = new Person("Simon", "Willison");
&gt; s.toString()
[object Object]
&gt; Person.prototype.toString = function() {
    return '&lt;Person: ' + this.fullName() + '&gt;';
}
&gt; s
&lt;Person: Simon Willison&gt;
</pre>

<p>還記得前面提過的 apply() 嗎?當時我們在 avg.apply() 的第一個參數送進 null。現在我們回過頭來解釋 apply()。其實 apply() 的第一個參數會被當成 "this"。舉例來說,我們可以利用 apply() 實作一個 trivialNew() 使他的效果等同於平時使用的 new():</p>

<pre class="brush: js">function trivialNew(constructor) {
    var o = {}; // Create an object
    constructor.apply(o, arguments);
    return o;
}
</pre>

<p>當我們這樣做時,並不會產生一個類似於 new() 的函式在 prototype 中。此外,apply() 有一個姊妹函式叫做 <span style="color: #0000cd;">call</span><span style="color: #000000;">,差異在於 call() 接受一個可被擴展的參數串列而非一個陣列。</span></p>

<pre class="brush: js">function lastNameCaps() {
    return this.last.toUpperCase();
}
var s = new Person("Simon", "Willison");
lastNameCaps.call(s);
// Is the same as:
s.lastNameCaps = lastNameCaps;
s.lastNameCaps();
</pre>

<h3 id=".E5.B7.A2.E7.8B.80.E5.87.BD.E5.BC.8F" name=".E5.B7.A2.E7.8B.80.E5.87.BD.E5.BC.8F">巢狀函式</h3>

<p>JavaScript 函式宣告可以放在其他函式內。我們之前有在 <code>makePerson()</code> 函式見過這個。巢狀函式的一樣重要的功能是:它們可以存取其母函式 (parent function) 的領域 (scope) 內的變數:</p>

<pre class="brush: js">function betterExampleNeeded() {
    var a = 1;
    function oneMoreThanA() {
        return a + 1;
    }
    return oneMoreThanA();
}
</pre>

<p>這對於寫更容易維護的程式碼來說很有用。假如某函式需要其他一兩個函式,而這一兩個函式在整個程式的其他部分都不需要用到,你便可以把這些所謂「工具函式」(utility function) 給巢狀性地包在需要它們的函式內,這個主要函式再從其他地方呼叫。這樣便能保持全域領域 (global scope) 的函式不會太多。不在全域領界內塞太多函式是件好事情。</p>

<p>這也能反制全域變數 (global variable) 的誘惑。在撰寫複雜的程式碼的時候,常常會有想利用全域變數來在多個函式之間傳遞數值的這種誘惑--但這麼做,便會導致程式碼非常難以維護。巢狀函式可與其母函式一起共用變數,因此你可以用這個原理來在適當的時機將好幾個函式配在一起,而不用「汙染」全域命名空間 (global namespace)--這可以稱做「本地變數」 (local variable)。使用此技巧時應當小心,不過,此技巧相當有用。</p>

<h3 id="Closures" name="Closures">閉包</h3>

<p>接著我們介紹一種十分強大卻也常使人困惑的機制:閉包 (closure)。在解釋之前,我們先看看下面這段程式,猜猜執行的結果是什麼。</p>

<pre class="brush: js">function makeAdder(a) {
    return function(b) {
        return a + b;
    }
}
x = makeAdder(5);
y = makeAdder(20);
x(6)
?
y(7)
?
</pre>

<p>答案揭曉,makeAdder 創造了一個 "adder" 函式,這個新的 "adder" 接受一個參數並將這個參數和 "adder" 被創造時的參數加在一起。</p>

<p>這裡發生的事情很類似先前提過的「內部函式」(inner function):一個新的函式被創造在別的函式內部,並且可以接觸到外面函式的變數。但不同的地方在於,內部函式中一旦回到上層函式,其先前創造的內部函式中的本地變數就消滅了(因為作用域結束了)。但在閉包中,這些本地變數卻<strong>依然存在</strong>(有一種本地變數在離開作用域的那一刻被凍結的感覺!)--否則我們上面例子中的 adder 就無法運作了。說到這裡,你應該知道結果了。x(6) 是 11 而 y(7) 是 27。</p>

<p>更進一步解釋,在 JavaScript 中,只要你開始執行一個函式,那麼就會出現一個「作用域」物件。作用域物件可以根據傳入的參數來做初始化的動作。這聽起來有點類似存放所有全域變數和全域函數的全域物件,但他們卻有幾點不同。首先,一個新的作用域物件是在任何該函式被執行時才產生,而全域物件直接的被使用,而必須進入該作用域才能操作。</p>

<p>再論 makeAdder。當它被呼叫時,一個新的作用域物件被產生,並且帶著一個 property a 在其中(這是由呼叫 makeAdder 者傳入的)。隨後 makeAdder 回傳一個新創的函式。正常情況下,JavaScript 的垃圾回收機制理應回收這個作用域物件,但在我們的例子中,回傳的函式卻保留了一個可以再次訪問此作用域物件的參照。因此,這個作用域物件便沒被真正回收--必須等到未來沒有任何參照可以指向它,方能回收。</p>

<p>多個作用域物件會組成作用域鏈 (scope chain),類似於 JavaScript 物件系統中的原型鏈。</p>

<p>簡單地做個結論,閉包是作用域物件和一個函式的組合,反映了其被創造之時的狀態。閉包允許你保留狀態,而這是很常用的功能。</p>

<h3 id="Memory_leaks" name="Memory_leaks">記憶體流失</h3>

<p>雖然閉包很方便,但卻容易在 IE 中造成記憶體流失。</p>

<p>JavaScript 是具有垃圾回收特性的語言,物件的生滅都是由執行環境(如瀏覽器)所控制。</p>

<ol>
 <li>記憶體配置:創造物件時 </li>
 <li>記憶體釋放:當沒有參照可以指向該物件時</li>
</ol>

<p>當瀏覽器在執行的時候,需要維護來自<span style="color: #0000cd;"> DOM</span> 的大量物件。IE 使用自己的垃圾回收機制,而這個管理機制和 JavaScript 的不同--這就造成了記憶體流失。</p>

<p>在我們這裡的例子中,記憶體流失源自於環狀的相互參照 (circular reference)-- JavaScript 物件及原生物件之間的相互參照。以下面的程式為例:</p>

<pre class="brush: js">function leakMemory() {
    var el = document.getElementById('el');
    var o = { 'el': el };
    el.o = o;
}
</pre>

<p>此時便發生了記憶體流失。除非 IE 重新啟動,否則 el 和 o 使用的記憶體皆無法被釋放。這種例子容易因不小心而出現。</p>

<p>其實記憶體流失很難引起注意,除非</p>

<ol>
 <li>程式需要長期執行</li>
 <li>因記憶體流失而消耗大量的記憶體</li>
 <li>迴圈中明顯地浪費記憶體</li>
</ol>

<p>閉包容易產生記憶體流失。如下舉例 : </p>

<pre class="brush: js">function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
}
</pre>

<p>在這段程式中,我們設定了一個 element,讓它可以在被點選時變紅。這裡形成了記憶體流失。你發現了嗎?因為指向 el 的參照不經意地被由匿名內部函式產生的閉包連結了。這就在 JavaScript 物件 (function) 和原生物件 (el) 之間產生了相互參照。</p>

<p>在現實生活中充滿了類似的案例,但我們其實可以很容易地處理。看一個簡單的示範:</p>

<pre class="brush: js">function addHandler() {
    var el = document.getElementById('el');
    el.onclick = function() {
        this.style.backgroundColor = 'red';
    }
    el = null;
}
</pre>

<p>如此一來我們解除了相互參照。</p>

<p>此外,尚有一種巧妙的方式可以藉由增加一個新的閉包來消除相互參照:</p>

<pre class="brush: js">function addHandler() {
    var clickHandler = function() {
        this.style.backgroundColor = 'red';
    }
    (function() {
        var el = document.getElementById('el');
        el.onclick = clickHandler;
    })();
}
</pre>

<p>在此例子中,隱藏了來自 clickHandler 產生的內容,因而消除了相互參照。</p>

<p>避免閉包還有另外一種方法:利用 <code>window.onunload</code> 事件消除相互參照。許多事件函示庫都會自動這麼做。</p>

<div class="originaldocinfo">
<h2 id=".E5.8E.9F.E5.A7.8B.E6.96.87.E4.BB.B6.E8.B3.87.E8.A8.8A" name=".E5.8E.9F.E5.A7.8B.E6.96.87.E4.BB.B6.E8.B3.87.E8.A8.8A">原始文件資訊</h2>

<ul>
 <li>作者:<a class="external" href="http://simon.incutio.com/">Simon Willison</a></li>
 <li>最後更新日期:2006 年 3 月 7 日</li>
 <li>著作權:© 2006 Simon Willison,Creative Commons: Attribute-Sharealike 2.0 (創用:姓名標示-相同方式分享 2.0)授權。</li>
 <li>更多資訊:其他關於這份教學的資訊(以及原作者所用的投影片)請前往原作者的網誌:<a class="external" href="http://simon.incutio.com/archive/2006/03/07/etech">Etech weblog</a></li>
</ul>
</div>

<p> </p>