forked from BonsaiDen/JavaScript-Garden
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
1531 lines (1059 loc) · 128 KB
/
index.html
File metadata and controls
1531 lines (1059 loc) · 128 KB
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
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html><html lang="ru"><head><title>JavaScript Гарден</title><meta charset="utf-8"><meta name="description" content="Руководство по хитростям и трюкам JavaScript."><link rel="stylesheet" href="../style/garden.css" media="all"><link rel="stylesheet" href="../style/print.css" media="print"><!--[if lt IE 9]>
<script src="javascript/html5.js"></script>
<![endif]-->
</head><body><!-- Navigation--><nav id="nav_main"><div><ul><li><a href="/JavaScript-Garden/pl" title="JavaScript Garden - ogród JavaScript po polsku">pl</a></li><li><a href="/JavaScript-Garden/" title="JavaScript Garden in English">en</a></li><li class="active"><a href="/JavaScript-Garden/ru" title="JavaScript Гарден по-русски">ru</a></li><li><a href="/JavaScript-Garden/zh" title="JavaScript Garden 中文翻译">zh</a></li><li><a href="/JavaScript-Garden/tr" title="JavaScript Garden Türkçe">tr</a></li><li><a href="/JavaScript-Garden/es" title="JavaScript Garden es Español">es</a></li><li><a href="/JavaScript-Garden/ja" title="JavaScript Garden in Japanese">ja</a></li><li><a href="/JavaScript-Garden/ko" title="JavaScript Garden">ko</a></li><li><a href="/JavaScript-Garden/fi" title="JavaScript-puutarha suomeksi">fi</a></li></ul><a id="top" href="#intro" title="Back to top">#top</a><a id="hide_menu" class="tablet">Hide Menu</a></div><ul><li class="nav_intro"><h1><a href="#intro">Вступление</a></h1><ul><li><a href="#intro.authors">Авторы</a></li><li><a href="#intro.contributors">Участники</a></li><li><a href="#intro.translators">Переводчики</a></li><li><a href="#intro.license">Лицензия</a></li></ul></li><li class="nav_object"><h1><a href="#object">Объекты</a></h1><ul><li><a href="#object.general">Объекты и их свойства</a></li><li><a href="#object.prototype">Великий Прототип</a></li><li><a href="#object.hasownproperty">Функция <code>hasOwnProperty</code></a></li><li><a href="#object.forinloop">Цикл <code>for in</code></a></li></ul></li><li class="nav_function"><h1><a href="#function">Функции</a></h1><ul><li><a href="#function.general">Выражения и объявление функций</a></li><li><a href="#function.this">Как работает <code>this</code></a></li><li><a href="#function.closures">Замыкания и ссылки</a></li><li><a href="#function.arguments">Объект <code>arguments</code></a></li><li><a href="#function.constructors">Конструктор</a></li><li><a href="#function.scopes">Области видимости и пространства имён</a></li></ul></li><li class="nav_array"><h1><a href="#array">Массивы</a></h1><ul><li><a href="#array.general"> Итерации по массивам и свойства</a></li><li><a href="#array.constructor">Конструктор <code>Array</code></a></li></ul></li><li class="nav_types"><h1><a href="#types">Типы</a></h1><ul><li><a href="#types.equality"> Равенство и сравнение</a></li><li><a href="#types.typeof">Оператор <code>typeof</code></a></li><li><a href="#types.instanceof">Оператор <code>instanceof</code></a></li><li><a href="#types.casting">Приведение типов</a></li></ul></li><li class="nav_core"><h1><a href="#core">Нативности</a></h1><ul><li><a href="#core.eval">Почему нельзя использовать <code>eval</code></a></li><li><a href="#core.undefined"><code>undefined</code> и <code>null</code></a></li><li><a href="#core.semicolon">Автоматическая вставка точек с запятой</a></li></ul></li><li class="nav_other"><h1><a href="#other">Другое</a></h1><ul><li><a href="#other.timeouts"><code>setTimeout</code> и <code>setInterval</code></a></li></ul></li><li class="nav_appendix"><h1><a href="#appendix">Пояснения</a></h1><ul><li><a href="#appendix.fromtranslators">От переводчиков</a></li></ul></li></ul></nav><!-- Mobile navigation--><nav id="nav_mobile"><a id="nav_prev_section" href="#">prev section<span class="nav_section_name">section name</span></a><a id="nav_next_section" href="#">next section<span class="nav_section_name">section name</span></a><a id="show_menu">show menu</a></nav><!-- Sections--><section id="intro"><!-- Introduction--><header id="intro.intro"><h1>Вступление</h1><div><p><strong>JavaScript Гарден</strong> — это постоянно обновляющаяся и растущая документация по самым заковыристым темам языка JavaScript. В ней вы найдёте советы о том, как избежать распространённых ошибок и предсказать появление тех или иных багов. В документации подробно освещены проблемы оптимизации и нерекомендуемые практики с которыми, продираясь к глубинам языка, могут столкнуться даже просвещённые JavaScript-программисты.</p>
<p>JavaScript Гарден <strong>не</strong> имеет цели научить вас языку JavaScript. Вам понадобится реальный опыт работы с языком чтобы понимать темы, рассматриваемые в этом руководстве. Если вам требуется изучить основы языка, пожалуйста внимательно ознакомьтесь с замечательным <a href="https://developer.mozilla.org/en/JavaScript/Guide">руководством</a> на сайте Mozilla Developer Network.</p></div></header><!-- Articles--><article id="intro.authors"><h2>Авторы</h2><div><p>Это руководство является результатом работы двух заядлых пользователей Stack Overflow: <a href="http://stackoverflow.com/users/170224/ivo-wetzel">Иво Ветцель /Ivo Wetzel/</a> (автора текста) и <a href="http://stackoverflow.com/users/313758/yi-jiang">Чжан И Цзян /Zhang Yi Jiang/</a> (дизайнера).</p></div></article><article id="intro.contributors"><h2>Участники</h2><div><ul>
<li><a href="https://github.com/caio">Кайо Ромао /Caio Romão/</a> (проверка орфографии)</li>
<li><a href="https://github.com/blixt">Андреас Бликст /Andreas Blixt/</a> (проверка грамматики)</li>
</ul></div></article><article id="intro.translators"><h2>Переводчики</h2><div><ul>
<li><a href="http://shamansir.madfire.net/">'shaman.sir'</a></li>
<li><a href="http://anton.shevchuk.name/">Антон Шевчук</a></li>
<li><a href="http://nixsolutions.com/">Максим Лозовой</a></li>
<li><a href="http://nixsolutions.com/">Елена Пашкова</a></li>
</ul></div></article><article id="intro.license"><h2>Лицензия</h2><div><p>JavaScript Гарден распространяется под <a href="https://github.com/BonsaiDen/JavaScript-Garden/blob/next/LICENSE">лицензией MIT</a> и располагается на <a href="https://github.com/BonsaiDen/JavaScript-Garden">GitHub</a>. Если вы найдёте ошибку или опечатку, пожалуйста <a href="https://github.com/BonsaiDen/JavaScript-Garden/issues">сообщите нам о ней</a> или запросите права на загрузку в репозиторий. Кроме того, вы можете найти нас в <a href="http://chat.stackoverflow.com/rooms/17/javascript">комнате JavaScript</a> среди чатов Stack Overflow.</p></div></article></section><section id="object"><!-- Introduction--><header id="object.intro"><h1>Объекты</h1></header><!-- Articles--><article id="object.general"><h2>Объекты и их свойства</h2><div><p>В JavaScript всё является объектом, лишь за двумя исключениями — <a href="#core.undefined"><code>null</code></a> и <a href="#core.undefined"><code>undefined</code></a>.</p>
<pre><code>false.toString() // 'false'
[1, 2, 3].toString(); // '1,2,3'
function Foo(){}
Foo.bar = 1;
Foo.bar; // 1
</code></pre>
<p>Неверно считать, что числовые литералы нельзя использовать в качестве объектов — это распространённое заблуждение. Его причиной является упущение в парсере JavaScript, благодаря которому применение <em>точечной нотации</em> к числу воспринимается им как литерал числа с плавающей точкой.</p>
<pre><code>2.toString(); // вызывает SyntaxError
</code></pre>
<p>Есть несколько способов обойти этот недостаток и любой из них можно использовать для того, чтобы работать с числами, как с объектами:</p>
<pre><code>2..toString(); // вторая точка распознаётся корректно
2 .toString(); // обратите внимание на пробел перед точкой
(2).toString(); // двойка вычисляется заранее
</code></pre>
</div><div><h3>Объекты как тип данных</h3>
<p>Объекты в JavaScript могут использоваться как <a href="http://ru.wikipedia.org/wiki/%D0%A5%D0%B5%D1%88-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D0%B0"><em>хеш-таблицы</em></a>: подавляющей частью состоят из именованных свойств (ключей), привязанных к значениям.</p>
<p>Используя объектный литерал — нотацию <code>{}</code> — можно создать простой объект. Новый объект <a href="#object.prototype">наследуется</a> от <code>Object.prototype</code> и не имеет <a href="#object.hasownproperty">собственных свойств</a>.</p>
<pre><code>var foo = {}; // новый пустой объект
// новый объект со свойством 'test', имеющим значение 12
var bar = {test: 12};
</code></pre>
</div><div><h3>Доступ к свойствам</h3>
<p>Получить доступ к свойствам объекта можно двумя способами: используя либо точечную нотацию, либо запись квадратными скобками.</p>
<pre><code>var foo = {name: 'Kitten'}
foo.name; // kitten
foo['name']; // kitten
var get = 'name';
foo[get]; // kitten
foo.1234; // SyntaxError
foo['1234']; // работает
</code></pre>
<p>Обе нотации идентичны по принципу работы — одна лишь разница в том, что использование квадратных скобок позволяет устанавливать свойства динамически и использовать такие имена свойств, какие в других случаях могли бы привести к синтаксической ошибке.</p>
</div><div><h3>Удаление свойств</h3>
<p>Единственный способ удалить свойство у объекта — использовать оператор <code>delete</code>; устанавливая свойство в <code>undefined</code> или <code>null</code>, вы только заменяете связанное с ним <em>значение</em>, но не удаляете <em>ключ</em>.</p>
<aside>
<p><strong>Замечание</strong> от перев.: Если ссылок на значение больше нет, то сборщиком мусора удаляется и само значение, но ключ объекта при этом всё так же имеет новое значение.</p>
</aside>
<pre><code>var obj = {
bar: 1,
foo: 2,
baz: 3
};
obj.bar = undefined;
obj.foo = null;
delete obj.baz;
for(var i in obj) {
if (obj.hasOwnProperty(i)) {
console.log(i, '' + obj[i]);
}
}
</code></pre>
<p>Приведённый код выведет две строки: <code>bar undefined</code> и <code>foo null</code> — на самом деле удалено было только свойство <code>baz</code> и посему только оно будет отсутствовать в выводе.</p>
</div><div><h3>Запись ключей</h3>
<pre><code>var test = {
'case': 'Я — ключевое слово, поэтому меня надо записывать строкой',
delete: 'Я тоже ключевое слово, так что я' // бросаю SyntaxError
};
</code></pre>
<p>Свойства объектов могут записываться как явно символами, так и в виде закавыченных строк. В связи с другим упущением в парсере JavaScript, этот код выбросит <code>SyntaxError</code> во всех версиях ранее ECMAScript 5.</p>
<p>Источником ошибки является факт, что <code>delete</code> — это <em>ключевое слово</em> и поэтому его необходимо записывать как <em>строчный литерал</em>: ради уверенности в том, что оно будет корректно опознано более старыми движками JavaScript.</p>
<p><em>От перев.:</em> И еще один пример в пользу строковой нотации, это относится к <a href="http://ru.wikipedia.org/wiki/JSON">JSON</a>:</p>
<pre><code>// валидный JavaScript и валидный JSON
{
'foo': 'oof',
'bar': 'rab'
}
// валидный JavaScript и НЕ валидный JSON
{
foo: 'oof',
bar: 'rab'
}
</code></pre></div></article><article id="object.prototype"><h2>Великий Прототип</h2><div><p>В JavaScript отсутствует классическая модель наследования — вместо неё используется <a href="http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%82%D0%BE%D1%82%D0%B8%D0%BF%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5"><em>прототипная модель</em></a>.</p>
<p>Хотя её часто расценивают как один из недостатков JavaScript, на самом деле прототипная модель наследования намного мощнее классической. К примеру, поверх неё можно предельно легко реализовать классическое наследование, а попытки совершить обратное вынудят вас попотеть.</p>
<p>Из-за того, что JavaScript — практически единственный широко используемый язык с прототипным наследованием, придётся потратить некоторое время на осознание различий между этими двумя моделями.</p>
<p>Первое важное отличие заключается в том, что наследование в JavaScript выполняется с использованием так называемых <em>цепочек прототипов</em>.</p>
<aside>
<p><strong>Замечание:</strong> В результате выполнения конструкции <code>Bar.prototype = Foo.prototype</code> оба объекта будут делить друг с другом <strong>один и тот же</strong> прототип. Так что изменение прототипа одного из объектов повлечёт за собой изменение прототипа другого и наоборот — вряд ли это окажется тем, чего вы ожидали.</p>
</aside>
<pre><code>function Foo() {
this.value = 42;
}
Foo.prototype = {
method: function() {}
};
function Bar() {}
// Установим значением прототипа Bar новый экземпляр Foo
Bar.prototype = new Foo();
Bar.prototype.foo = 'Hello World';
// Убедимся, что Bar является действующим конструктором
Bar.prototype.constructor = Bar;
var test = new Bar() // создадим новый экземпляр bar
// Цепочка прототипов, которая получится в результате
test [instance of Bar]
Bar.prototype [instance of Foo]
{ foo: 'Hello World' }
Foo.prototype
{ method: ... }
Object.prototype
{ toString: ... /* и т.д. */ }
</code></pre>
<p>В приведённом коде объект <code>test</code> наследует оба прототипа: <code>Bar.prototype</code> и <code>Foo.prototype</code>; следовательно, он имеет доступ к функции <code>method</code> которую мы определили в прототипе <code>Foo</code>. Также у него есть доступ к свойству <code>value</code> <strong>одного уникального</strong> экземпляра <code>Foo</code>, который является его протипом. Важно заметить, что код <code>new Bar()</code> <strong>не</strong> создаёт новый экземпляр <code>Foo</code>, а повторно вызывает функцию, которая была назначен его прототипом: таким образом все новые экземпляры <code>Bar</code> будут иметь <strong>одинаковое</strong> свойство <code>value</code>.</p>
<aside>
<p><strong>Замечание:</strong> Никогда <strong>не</strong> используйте конструкцию <code>Bar.prototype = Foo</code>, поскольку ссылка будет указывать не на прототип <code>Foo</code>, а на объект функции <code>Foo</code>. Из-за этого цепочка прототипов будет проходить через <code>Function.prototype</code>, а не через <code>Foo.prototype</code> и в результате функция <code>method</code> не будет содержаться в цепочке прототипов.</p>
</aside>
</div><div><h3>Поиск свойств</h3>
<p>При обращении к какому-либо свойству объекта, JavaScript проходит <strong>вверх</strong> по цепочке прототипов этого объекта, пока не найдет свойство c запрашиваемым именем.</p>
<p>Если он достигнет верхушки этой цепочки (<code>Object.prototype</code>) и при этом так и не найдёт указанное свойство, вместо него вернётся значение <a href="#core.undefined">undefined</a>.</p>
</div><div><h3>Свойство <code>prototype</code></h3>
<p>То, что свойство <code>prototype</code> используется языком для построения цепочек прототипов, даёт нам возможность присвоить <strong>любое</strong> значение этому свойству. Однако обычные примитивы, если назначать их в качестве прототипа, будут просто-напросто игнорироваться.</p>
<pre><code>function Foo() {}
Foo.prototype = 1; // ничего не произойдёт
Foo.prototype = {
"foo":"bar"
};
</code></pre>
<p>При этом присвоение объектов, как в примере выше, позволит вам динамически создавать цепочки прототипов.</p>
</div><div><h3>Производительность</h3>
<p>Поиск свойств, располагающихся относительно высоко по цепочке прототипов, может негативно сказаться на производительности, особенно в критических местах кода. Если же мы попытаемся найти несуществующее свойство, то поиск будет осуществлён вообще по всей цепочке, со всеми вытекающими последствиями.</p>
<p>Вдобавок, при <a href="#object.forinloop">циклическом переборе</a> свойств объекта, будет обработано <strong>каждое</strong> свойство, существующее в цепочке прототипов.</p>
</div><div><h3>Расширение встроенных прототипов</h3>
<p>Часто встречается неверное применение прототипов — расширение прототипа <code>Object.prototype</code> или прототипов одного из встроенных объектов JavaScript.</p>
<p>Подобная практика нарушает принцип <em>инкапсуляции</em>, и имеет соответствующее название — <a href="http://en.wikipedia.org/wiki/Monkey_patch">monkey patching</a>. К сожалению, в основу многих широко распространенных фреймворков, например Prototype, положен принцип изменения базовых прототипов. Вам же стоит запомнить — от хорошей жизни прототипы встроенных объектов не меняют.</p>
<p><strong>Единственным</strong> оправданием для расширения встроенных прототипов может быть только воссоздание возможностей более новых движков JavaScript, например функции <a href="https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach"><code>Array.forEach</code></a>, которая появилась в версии 1.6.</p>
</div><div><h3>Заключение</h3>
<p>Перед тем, как вы приступите к разработке сложных приложений на JavaScript, вы <strong>должны</strong> полностью осознать как работают прототипы, и как организовывать наследование на их основе. Также, помните о зависимости между длиной цепочек прототипов и производительностью — разрывайте их при необходимости. Кроме того — <strong>никогда</strong> не расширяйте прототипы встроенных объектов (ну, если только для совместимости с новыми возможностями Javascript).</p></div></article><article id="object.hasownproperty"><h2>Функция <code>hasOwnProperty</code></h2><div><p>Если вам необходимо проверить, определено ли свойство у <em>самого объекта</em>, а <strong>не</strong> в его <a href="#object.prototype">цепочке прототипов</a>, вы можете использовать метод <code>hasOwnProperty</code>, который все объекты наследуют от <code>Object.prototype</code>.</p>
<aside>
<p><strong>Примечание:</strong> Для проверки наличия свойства <strong>недостаточно</strong> проверять, эквивалентно ли оно <code>undefined</code>. Свойство может вполне себе существовать, но при этом ему может быть присвоено значение <code>undefined</code>.</p>
</aside>
<p><code>hasOwnProperty</code> — единственная функция в JavaScript, которая позволяет получить свойства объекта <strong>без обращения</strong> к цепочке его прототипов.</p>
<pre><code>// испортим Object.prototype
Object.prototype.bar = 1;
var foo = {goo: undefined};
foo.bar; // 1
'bar' in foo; // true
foo.hasOwnProperty('bar'); // false
foo.hasOwnProperty('goo'); // true
</code></pre>
<p>Только используя `hasOwnProperty` можно гарантировать правильный результат при переборе свойств объекта. И <strong>нет</strong> иного способа для определения свойств, которые определены в <em>самом</em> объекте, а не где-то в цепочке его прототипов.</p>
</div><div><h3><code>hasOwnProperty</code> как свойство</h3>
<p>JavaScript <strong>не</strong> резервирует свойство с именем <code>hasOwnProperty</code>. Так что, если есть потенциальная возможность, что объект может содержать свойство с таким именем, требуется использовать <em>внешний</em> вариант функции <code>hasOwnProperty</code> чтобы получить корректные результаты.</p>
<pre><code>var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Да прилетят драконы'
};
foo.hasOwnProperty('bar'); // всегда возвращает false
// Используем метод hasOwnProperty пустого объекта
// и передаём foo в качестве this
({}).hasOwnProperty.call(foo, 'bar'); // true
</code></pre>
</div><div><h3>Заключение</h3>
<p><strong>Единственным</strong> способом проверить существование свойства у объекта является использование метода <code>hasOwnProperty</code>. При этом, рекомендуется использовать этот метод в <strong>каждом</strong> <a href="#object.forinloop">цикле <code>for in</code></a> вашего проекта, чтобы избежать возможных ошибок с ошибочным заимствованием свойств из <a href="#object.prototype">прототипов</a> родительских объектов. Также вы можете использовать конструкцию <code>{}.hasOwnProperty.call(...)</code> на случай, если кто-то вздумает расширить <a href="#object.prototype">прототипы</a> встроенных объектов.</p></div></article><article id="object.forinloop"><h2>Цикл <code>for in</code></h2><div><p>Как и оператор <code>in</code>, цикл <code>for in</code> проходит по всей цепочке прототипов обходя свойства объекта.</p>
<aside>
<p><strong>Примечание:</strong> Цикл <code>for in</code> <strong>не</strong> обходит те свойства объекта, у которых атрибут <code>enumerable</code> установлен в <code>false</code>; как пример - свойство <code>length</code> у массивов</p>
</aside>
<pre><code>// Испортим Object.prototype
Object.prototype.bar = 1;
var foo = {moo: 2};
for(var i in foo) {
console.log(i); // печатает и bar и moo
}
</code></pre>
<p>Так как изменить поведение цикла <code>for in</code> как такового не представляется возможным, то для фильтрации нежелательных свойств объекта внутри этого цикла используют метод <a href="#object.hasownproperty"><code>hasOwnProperty</code></a> из <code>Object.prototype</code>.</p>
<aside>
<p><strong>Примечание:</strong> Цикл <code>for in</code> всегда обходит всю цепочку прототипов полностью: таким образом, чем больше прототипов (слоёв наследования) в цепочке, тем медленнее работает цикл.</p>
</aside>
</div><div><h3>Использование <code>hasOwnProperty</code> в качестве фильтра</h3>
<pre><code>// возьмём foo из примера выше
for(var i in foo) {
if (foo.hasOwnProperty(i)) {
console.log(i);
}
}
</code></pre>
<p>Это единственная версия правильного использования цикла. Благодаря использованию <code>hasOwnPropery</code> будет выведено <strong>только</strong> свойство <code>moo</code>. Если же убрать <code>hasOwnProperty</code>, код становится нестабилен и могут возникнуть ошибки, особенно если кто-то изменил встроенные прототипы, такие как <code>Object.prototype</code>.</p>
<p>Один из самых популярных фреймворков <a href="http://www.prototypejs.org/">Prototype</a> как раз этим и славится, и если вы его подключаете, то не забудьте использовать <code>hasOwnProperty</code> внутри цикла <code>for in</code>, иначе у вас гарантированно возникнут проблемы.</p>
</div><div><h3>Рекомендации</h3>
<p>Рекомендация одна — <strong>всегда</strong> используйте <code>hasOwnProperty</code>. Пишите код, который будет в наименьшей мере зависеть от окружения, в котором он будет запущен — не стоит гадать, расширял кто-то прототипы или нет и используется ли в ней та или иная библиотека.</p></div></article></section><section id="function"><!-- Introduction--><header id="function.intro"><h1>Функции</h1></header><!-- Articles--><article id="function.general"><h2>Выражения и объявление функций</h2><div><p>Функции в JavaScript тоже являются объектами (шок, сенсация) — следовательно, их можно передавать и присваивать точно так же, как и любой другой объект. Одним из вариантов использования такой возможности является передача <em>анонимной функции</em> как функции обратного вызова в другую функцию — к примеру, для асинхронных вызовов.</p>
</div><div><h3>Объявление <code>function</code></h3>
<pre><code>// всё просто и привычно
function foo() {}
</code></pre>
<p>В следующем примере описанная функция <a href="#function.scopes">резервируется</a> перед запуском всего скрипта; за счёт этого она доступна <em>в любом месте</em> кода, вне зависимости от того где она <em>определена</em> — даже если функция вызывается до её фактического объявления в коде.</p>
<pre><code>foo(); // сработает, т.к. функция будет создана до выполнения кода
function foo() {}
</code></pre>
</div><div><h3><code>function</code> как выражение</h3>
<pre><code>var foo = function() {};
</code></pre>
<p>В этом примере безымянная и <em>анонимная</em> функция присваивается переменной <code>foo</code>.</p>
<pre><code>foo; // 'undefined'
foo(); // вызовет TypeError
var foo = function() {};
</code></pre>
<p>Так как в данном примере выражение <code>var</code> — это определение функции, переменная с именем <code>foo</code> будет заранее зарезервирована перед запуском скрипта (таким образом, <code>foo</code> уже будет определена во время его работы).</p>
<p>Но поскольку присвоения исполняются непосредственно во время работы кода, <code>foo</code> по умолчанию будет присвоено значение <a href="#core.undefined"><code>undefined</code></a> (до обработки строки с определением функции):</p>
<pre><code>var foo; // переменная неявно резервируется
foo; // 'undefined'
foo(); // вызовет TypeError
foo = function() {};
</code></pre>
</div><div><h3>Выражения с именованными фунциями</h3>
<p>Существует еще ньюанс, касающийся именованных функций создающихся через присваивание:</p>
<pre><code>var foo = function bar() {
bar(); // работает
}
bar(); // получим ReferenceError
</code></pre>
<p>Здесь объект <code>bar</code> не доступен во внешней области, так как имя <code>bar</code> используется только для присвоения переменной <code>foo</code>; однако <code>bar</code> можно вызвать внутри функции. Такое поведение связано с особенностью работы JavaScript с <a href="#function.scopes">пространствами имен</a> - имя функции <em>всегда</em> доступно в локальной области видимости самой функции.</p></div></article><article id="function.this"><h2>Как работает <code>this</code></h2><div><p>В JavaScript область ответственности специальной переменной <code>this</code> концептуально отличается от того, за что отвечает <code>this</code> в других языках программирования. Различают ровно <strong>пять</strong> вариантов того, к чему привязывается <code>this</code> в языке.</p>
</div><div><h3>1. Глобальная область видимости</h3>
<pre><code>this;
</code></pre>
<p>Когда мы используем <code>this</code> в глобальной области, она будет просто ссылаться на <em>глобальный</em> объект.</p>
</div><div><h3>2. Вызов функции</h3>
<pre><code>foo();
</code></pre>
<p>Тут <code>this</code> также ссылается на <em>глобальный</em> объект.</p>
<aside class="es5"><p><strong>ES5 Замечание:</strong> В strict-режиме <strong>теряется</strong> понятие глобальности, поэтому в этом случае <code>this</code> будет иметь значение <code>undefined</code>.</p>
</aside>
</div><div><h3>3. Вызов метода</h3>
<pre><code>test.foo();
</code></pre>
<p>В данном примере <code>this</code> ссылается на <code>test</code>.</p>
</div><div><h3>4. Вызов конструктора</h3>
<pre><code>new foo();
</code></pre>
<p>Если перед вызовом функции присутствует ключевое слово <code>new</code> то данная функция будет действовать как <a href="#function.constructors">конструктор</a>. Внутри такой функции <code>this</code> будет указывать на <em>новосозданный</em> <code>Object</code>.</p>
</div><div><h3>5. Переопределение <code>this</code></h3>
<pre><code>function foo(a, b, c) {}
var bar = {};
foo.apply(bar, [1, 2, 3]); // массив развернётся в a = 1, b = 2, c = 3
foo.call(bar, 1, 2, 3); // аналогично
</code></pre>
<p>Когда мы используем методы <code>call</code> или <code>apply</code> из <code>Function.prototype</code>, то внутри вызваемой функции <code>this</code> <strong>явным образом</strong> будет присвоено значение первого передаваемого параметра.</p>
<p>Исходя из этого, в предыдущем примере (строка с <code>apply</code>) правило #3 <em>вызов метода</em> <strong>не</strong> будет применёно, и <code>this</code> внутри <code>foo</code> будет присвоено <code>bar</code>.</p>
<aside>
<p><strong>Замечание:</strong> <code>this</code> <strong>нельзя</strong> использовать внутри литералов <code>{}</code> (<code>Object</code>) для ссылки на сам объект. Т.е. если мы напишем <code>var obj = {me: this}</code>, то <code>me</code> не будет ссылаться на <code>obj</code>, поскольку <code>this</code> присваивается только по одному из пяти описанных правил.</p>
</aside>
</div><div><h3>Наиболее распространенные ошибки</h3>
<p>Хотя большинство из примеров ниже наполнены глубоким смыслом, первый из них можно считать ещё одним упущением в самом языке, поскольку он <strong>вообще</strong> не имеет практического применения.</p>
<pre><code>Foo.method = function() {
function test() {
// this ссылается на глобальный объект
}
test();
}
</code></pre>
<p>Распространенным заблуждением будет то, что <code>this</code> внутри <code>test</code> ссылается на <code>Foo</code>, но это <strong>не так</strong>.</p>
<p>Для того, чтобы получить доступ к <code>Foo</code> внутри функции <code>test</code>, необходимо создать локальную перменную внутри <code>method</code>, которая и будет ссылаться на <code>Foo</code>.</p>
<pre><code>Foo.method = function() {
var that = this;
function test() {
// Здесь используем that вместо this
}
test();
}
</code></pre>
<p>Подходящее имя для переменной - <code>that</code>, его часто используют для ссылки на внешний <code>this</code>. В комбинации с <a href="#function.closures">замыканиями</a> <code>this</code> можно пробрасывать в глобальную область, или в любой другой объект.</p>
<aside>
<p><strong>Замечание</strong> от перев. Кроме <code>that</code> также часто встречаются <code>this_</code>, <code>self_</code> и другие варианты, но лучше принять для себя <code>that</code> как стандарт и тогда, возможно, все вокруг тоже будут им пользоваться.</p>
</aside>
</div><div><h3>Назначение методов</h3>
<p>Еще одной фичей, которая <strong>не</strong> работает в <code>JavaScript</code>, является создание псевдонимов для методов, т.е. <strong>присвоение</strong> метода объекта переменной.</p>
<pre><code>var test = someObject.methodTest;
test();
</code></pre>
<p>Следуя первому правилу <code>test</code> вызывается как обычная функция; следовательно <code>this</code> внутри него больше не ссылается на <code>someObject</code>.</p>
<p>Хотя позднее связывание <code>this</code> на первый взгляд может показаться плохой идеей, но на самом деле именно благодаря этому работает <a href="#object.prototype">наследование прототипов</a>.</p>
<pre><code>function Foo() {}
Foo.prototype.method = function() {};
function Bar() {}
Bar.prototype = Foo.prototype;
new Bar().method();
</code></pre>
<p>В момент, когда будет вызван <code>method</code> нового экземляра <code>Bar</code>, <code>this</code> будет ссылаться на этот самый экземпляр.</p></div></article><article id="function.closures"><h2>Замыкания и ссылки</h2><div><p>Одним из самых мощных инструментов JavaScript'а считаются возможность создавать <em>замыкания</em> — это такой приём, когда наша область видимости <strong>всегда</strong> имеет доступ к внешней области, в которой она была объявлена. Собственно, единственный механизм работы с областями видимости в JavaScript — это <a href="#function.scopes">функции</a>: т.о. объявляя функцию, вы автоматически реализуете замыкания.</p>
</div><div><h3>Эмуляция приватных свойств</h3>
<pre><code>function Counter(start) {
var count = start;
return {
increment: function() {
count++;
},
get: function() {
return count;
}
}
}
var foo = Counter(4);
foo.increment();
foo.get(); // 5
</code></pre>
<p>В данном примере <code>Counter</code> возвращает <strong>два</strong> замыкания: функции <code>increment</code> и <code>get</code>. Обе эти функции сохраняют <strong>ссылку</strong> на область видимости <code>Counter</code> и, соответственно, имеют доступ к переменной <code>count</code> из этой самой области.</p>
</div><div><h3>Как это работает</h3>
<p>Поскольку в JavaScript нельзя присваивать или ссылаться на области видимости, заполучить <code>count</code> извне <strong>не</strong> представляется возможным. Единственным способом взаимодействовать с ним остается использование двух замыканий.</p>
<pre><code>var foo = new Counter(4);
foo.hack = function() {
count = 1337;
};
</code></pre>
<p>В приведенном примере мы <strong>не</strong> изменяем переменную <code>count</code> в области видимости <code>Counter</code>, т.к. <code>foo.hack</code> не объявлен в <strong>данной</strong> области. Вместо этого будет создана или перезаписана <em>глобальная</em> переменная <code>count</code>;</p>
</div><div><h3>Замыкания внутри циклов</h3>
<p>Часто встречается ошибка, когда замыкания используют внутри циклов, передавая переменную индекса внутрь.</p>
<pre><code>for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
</code></pre>
<p>Данный код <strong>не</strong> будет выводить числа с <code>0</code> до <code>9</code>, вместо этого число <code>10</code> будет выведено десять раз.</p>
<p><em>Анонимная</em> функция сохраняет <strong>ссылку</strong> на <code>i</code> и когда будет вызвана функция <code>console.log</code>, цикл <code>for</code> уже закончит свою работу, а в <code>i</code> будет содержаться <code>10</code>.</p>
<p>Для получения желаемого результата необходимо создать <strong>копию</strong> переменной <code>i</code>.</p>
</div><div><h3>Во избежание ошибок</h3>
<p>Для того, чтобы скопировать значение индекса из цикла, лучше всего использовать <a href="#function.scopes">анонимную функцию</a> как обёртку.</p>
<pre><code>for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
</code></pre>
<p>Анонимная функция-обертка будет вызвана сразу же, и в качестве первого аргумента получит <code>i</code>, <strong>значение</strong> которой будет скопировано в параметр <code>e</code>.</p>
<p>Анонимная функция, которая передается в <code>setTimeout</code>, теперь содержит ссылку на <code>e</code>, значение которой <strong>не</strong> изменяется циклом.</p>
<p>Еще одним способом реализации является возврат функции из анонимной функции-обертки, поведение этого кода будет таким же, как и в коде из предыдущего примера.</p>
<pre><code>for(var i = 0; i < 10; i++) {
setTimeout((function(e) {
return function() {
console.log(e);
}
})(i), 1000)
}
</code></pre>
<aside>
<p><strong>Замечание</strong> от перев. Переменную <code>e</code> можно тоже назвать <code>i</code>, если вы хотите: это не поменяет поведения кода — внутренняя переменная <code>i</code> всё так же будет <em>копией</em> внешней переменной</p>
</aside></div></article><article id="function.arguments"><h2>Объект <code>arguments</code></h2><div><p>В области видимости любой функции в JavaScript есть доступ к специальной переменной <code>arguments</code>. Эта переменная содержит в себе список всех аргументов, переданных данной функции.</p>
<aside>
<p><strong>Замечание:</strong> В случае, если переменная <code>arguments</code> уже была объявлена в области видимости функции либо путём присвоения через выражение <code>var</code>, либо являясь формальным параметром, объект <code>arguments</code> не будет создан.</p>
</aside>
<p>Объект <code>arguments</code> <strong>не</strong> является наследником <code>Array</code>. Он, конечно же, очень похож на массив, и даже содержит свойство <code>length</code> — но он не наследует <code>Array.prototype</code>, а представляет собой <code>Object</code>.</p>
<p>По этой причине, у объекта <code>arguments</code> <strong>отсутствуют</strong> стандартные методы массивов, такие как <code>push</code>, <code>pop</code> или <code>slice</code>. Хотя итерация с использованием обычного цикла <code>for</code> по агрументам работает вполне корректно, вам придётся конвертировать этот объект в настоящий массив типа <code>Array</code>, чтобы применять к нему стандартные методы массивов.</p>
</div><div><h3>Конвертация в массив</h3>
<p>Указанный код вернёт новый массив типа <code>Array</code>, содержащий все элементы объекта <code>arguments</code>.</p>
<pre><code>Array.prototype.slice.call(arguments);
</code></pre>
<p>Эта конвертация занимает <strong>много времени</strong> и использовать её в критических частях кода <strong>не рекомендуется</strong>.</p>
</div><div><h3>Передача аргументов</h3>
<p>Ниже представлен рекомендуемый способ передачи аргументов из одной функции в другую.</p>
<pre><code>function foo() {
bar.apply(null, arguments);
}
function bar(a, b, c) {
// делаем здесь что-нибудь
}
</code></pre>
<p>Другой трюк — использовать и <code>call</code> и <code>apply</code> вместе, чтобы быстро создать несвязанную обёртку:</p>
<pre><code>function Foo() {}
Foo.prototype.method = function(a, b, c) {
console.log(this, a, b, c);
};
// Создаём несвязанную версию "method"
// Она принимает параметры: this, arg1, arg2...argN
Foo.method = function() {
// Результат: Foo.prototype.method.call(this, arg1, arg2... argN)
Function.call.apply(Foo.prototype.method, arguments);
};
</code></pre>
</div><div><h3>Формальные аргументы и индексы аргументов</h3>
<p>Объект <code>arguments</code> создаёт по <em>геттеру</em> и <em>сеттеру</em> и для всех своих свойств и для формальных параметров функции.</p>
<p>В результате, изменение формального параметра также изменит значение соответствующего свойства объекта <code>arguments</code> и наоборот.</p>
<pre><code>function foo(a, b, c) {
arguments[0] = 2;
a; // 2
b = 4;
arguments[1]; // 4
var d = c;
d = 9;
c; // 3
}
foo(1, 2, 3);
</code></pre>
</div><div><h3>Мифы и правда о производительности</h3>
<p>Объект <code>arguments</code> создаётся во всех случаях, лишь за двумя исключениями — когда он переопределён внутри функции (по имени) или когда одним из её параметров является переменная с таким именем. Неважно, используется при этом сам объект или нет.</p>
<p><em>Геттеры</em> и <em>сеттеры</em> создаются <strong>всегда</strong>; так что, их использование практически никак не влияет на производительность.</p>
<aside class="es5"><p><strong>ES5 Замечание:</strong> Эти <em>геттеры</em> и <em>сеттеры</em> не создаются в strict-режиме.</p>
</aside>
<p>Однако, есть один момент, который может радикально понизить производительность современных движков JavaScript. Этот момент — использование <code>arguments.callee</code>.</p>
<pre><code>function foo() {
arguments.callee; // сделать что-либо с этим объектом функции
arguments.callee.caller; // и с вызвавшим его объектом функции
}
function bigLoop() {
for(var i = 0; i < 100000; i++) {
foo(); // При обычных условиях должна бы была быть развёрнута...
}
}
</code></pre>
<p>В коде выше, функция <code>foo</code> не может <a href="http://en.wikipedia.org/wiki/Inlining">быть развёрнута</a> (а могла бы), потому что для корректной работы ей необходима ссылка и на себя и на вызвавший её объект. Это не только кладёт на лопатки механизм развёртывания, но и нарушает принцип инкапсуляции, поскольку функция становится зависима от конкретного контекста вызова.</p>
<p><strong>Крайне не рекомендуется</strong> использовать <code>arguments.callee</code> или какое-либо из его свойств. <strong>Никогда</strong>.</p>
<aside class="es5"><p><strong>ES5 Замечание:</strong> В strict-режиме использование <code>arguments.callee</code> породит <code>TypeError</code>, поскольку его использование принято устаревшим.</p>
</aside></div></article><article id="function.constructors"><h2>Конструктор</h2><div><p>Создание конструкторов в JavaScript также отличается от большинства других языков. Любая функция, вызванная с использованием ключевого слова <code>new</code>, будет конструктором.</p>
<p>Внутри конструктора (вызываемой функции) <code>this</code> будет указывать на новосозданный <code>Object</code>. <a href="#object.prototype">Прототипом</a> этого <strong>нового</strong> объекта будет <code>prototype</code> функции, которая была вызвана в качестве коструктора.</p>
<p>Если вызываемая функция не имеет явного возврата посредством <code>return</code>, то вернётся <code>this</code> — этот новый объект.</p>
<pre><code>function Foo() {
this.bla = 1;
}
Foo.prototype.test = function() {
console.log(this.bla);
};
var test = new Foo();
</code></pre>
<p>В этом примере <code>Foo</code> вызывается в виде конструктора, следовательно прототип созданного объекта будет привязан к <code>Foo.prototype</code>.</p>
<p>В случае, когда функция в явном виде возвращает некое значение используя <code>return</code>, то в результате выполнения конструктора мы получим именно его, <strong>но только</strong> если возвращаемое значение представляет собой <code>Object</code>.</p>
<pre><code>function Bar() {
return 2;
}
new Bar(); // новый объект
function Test() {
this.value = 2;
return {
foo: 1
};
}
new Test(); // возвращённый объект
</code></pre>
<p>Если же опустить ключевое слово <code>new</code>, то функция <strong>не</strong> будет возвращать никаких объектов.</p>
<pre><code>function Foo() {
this.bla = 1; // устанавливается глобальному объекту
}
Foo(); // undefined
</code></pre>
<p>Этот пример в некоторых случаях всё-таки может сработать: это связано с поведением <a href="#function.this"><code>this</code></a> в JavaScript — он будет восприниматься парсером как <em>глобальный объект</em>.</p>
</div><div><h3>Фабрики</h3>
<p>Если хотите избавится от необходимости использования <code>new</code>, напишите конструктор, возвращающий значение посредством <code>return</code>.</p>
<pre><code>function Bar() {
var value = 1;
return {
method: function() {
return value;
}
}
}
Bar.prototype = {
foo: function() {}
};
new Bar();
Bar();
</code></pre>
<p>В обоих случаях при вызове <code>Bar</code> мы получим один и тот же результат — новый объект со свойством <code>method</code> (спасибо <a href="#function.closures">замыканию</a> за это).</p>
<p>Также следует заметить, что вызов <code>new Bar()</code> никак <strong>не</strong> связан с прототипом возвращаемого объекта. Хоть прототип и назначается всем новосозданным объектам, но <code>Bar</code> никогда не возвращает этот новый объект.</p>
<p>В предыдущем примере нет функциональных отличий между вызовом конструктора с оператором <code>new</code> или без него.</p>
</div><div><h3>Создание объектов с использованием фабрик</h3>
<p>Часто рекомендуют <strong>не</strong> использовать <code>new</code>, поскольку если вы его забудете, это может привести к ошибкам.</p>
<p>Чтобы создать новый объект, лучше использовать фабрику и создать новый объект <em>внутри</em> этой фабрики.</p>
<pre><code>function Foo() {
var obj = {};
obj.value = 'blub';
var private = 2;
obj.someMethod = function(value) {
this.value = value;
}
obj.getPrivate = function() {
return private;
}
return obj;
}
</code></pre>
<p>Хотя данный пример и сработает, если вы забыли ключевое слово <code>new</code> и благодаря ему легче работать с <a href="#function.closures">приватными переменными</a>, у него есть несколько недостатков</p>
<ol>
<li>Он использует больше памяти, поскольку созданные объекты <strong>не</strong> хранят методы в прототипе и соответственно для каждого нового объекта создаётся копия каждого метода.</li>
<li>Чтобы эмулировать наследование, фабрике нужно скопировать все методы из другого объекта или установить прототипом нового объекта старый.</li>
<li>Разрыв цепочки прототипов просто по причине забытого ключевого слова <code>new</code> идёт в разрез с духом языка.</li>
</ol>
</div><div><h3>Заключение</h3>
<p>Хотя забытое ключевое слово <code>new</code> и может привести к багам, это точно <strong>не</strong> причина отказываться от использования прототипов. В конце концов, полезнее решить какой из способов лучше совпадает с требованиями приложения: очень важно выбрать один из стилей создания объектов и после этого <strong>не изменять</strong> ему.</p></div></article><article id="function.scopes"><h2>Области видимости и пространства имён</h2><div><p>Хотя JavaScript нормально понимает синтаксис двух фигурных скобок, окружающих блок, он <strong>не</strong> поддерживает блочную область видимости; всё что остаётся на этот случай в языке — <em>область видимости функций</em>.</p>
<pre><code>function test() { // область видимости
for(var i = 0; i < 10; i++) { // не область видимости
// считаем
}
console.log(i); // 10
}
</code></pre>
<aside>
<p><strong>Замечание:</strong> Нотация <code>{...}</code> будет интерпретирована как блочное выражение, а <strong>не</strong> как литерал объекта, если она не используется в присваивании, операторе <code>return</code> или в качестве функции. Это замечание, вкупе с <a href="#core.semicolon">автоматической расстановкой точек с запятой</a>, может привести к чрезвычайно хитрым ошибкам.</p>
</aside>
<p>Также JavaScript не знает ничего о различиях в пространствах имён: всё определяется в <em>глобально доступном</em> пространстве имён.</p>
<p>Каждый раз, когда JavaScript обнаруживает ссылку на переменную, он будет искать её всё выше и выше по областям видимости, пока не найдёт её. В случае, если он достигнет глобальной области видимости и не найдет запрошенное имя и там тоже, он ругнётся <code>ReferenceError</code>.</p>
</div><div><h3>Проклятие глобальных переменных</h3>
<pre><code>// скрипт A
foo = '42';
// скрипт B
var foo = '42'
</code></pre>
<p>Вышеприведённые два скрипта <strong>не</strong> приводят к одному результату. Скрипт A определяет переменную по имени <code>foo</code> в <em>глобальной</em> области видимости, а скрипт B определяет <code>foo</code> в текущей области видимости.</p>
<p>Повторимся, это вообще <strong>не</strong> <em>тот же самый эффект</em>. Если вы не используете <code>var</code> — то вы в большой опасности.</p>
<pre><code>// глобальная область видимости
var foo = 42;
function test() {
// локальная область видимости
foo = 21;
}
test();
foo; // 21
</code></pre>
<p>Из-за того что оператор <code>var</code> опущен внутри функции, фунция <code>test</code> перезапишет значение <code>foo</code>. Это поначалу может показаться не такой уж и большой проблемой, но если у вас имеется тысяча строк JavaScript-кода и вы не используете <code>var</code>, то вам на пути встретятся страшные и трудноотлаживаемые ошибки — и это не шутка.</p>
<pre><code>// глобальная область видимости
var items = [/* какой-то список */];
for(var i = 0; i < 10; i++) {
subLoop();
}
function subLoop() {
// область видимости subLoop
for(i = 0; i < 10; i++) { // пропущенный оператор var
// делаем волшебные вещи!
}
}
</code></pre>
<p>Внешний цикл прекратит работу сразу после первого вызова <code>subLoop</code>, поскольку <code>subLoop</code> перезаписывает глобальное значение переменной <code>i</code>. Использование <code>var</code> во втором цикле <code>for</code> могло бы вас легко избавить от этой ошибки. <strong>Никогда</strong> не забывайте использовать <code>var</code>, если только влияние на внешнюю область видимости не является тем, что вы <em>намерены получить</em>.</p>
</div><div><h3>Локальные переменные</h3>
<p>Единственный источник локальных переменных в JavaScript - это параметры <a href="#function.general">функций</a> и переменные, объявленные с использованием оператора <code>var</code>.</p>
<pre><code>// глобальная область видимости
var foo = 1;
var bar = 2;
var i = 2;
function test(i) {
// локальная область видимости для функции test
i = 5;
var foo = 3;
bar = 4;
}
test(10);
</code></pre>
<p>В то время как <code>foo</code> и <code>i</code> — локальные переменные в области видимости функции <code>test</code>, присвоение <code>bar</code> переопределит значение одноимённой глобальной переменной.</p>
</div><div><h3>Высасывание</h3>
<p>JavaScript <strong>высасывает</strong> определения. Это значит, что оба определения с использованием <code>var</code> и определение <code>function</code> будут перенесены наверх заключающей их области видимости.</p>
<pre><code>bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}
</code></pre>
<p>Этот код трансформируется ещё перед исполнением. JavaScript перемещает операторы <code>var</code> и определение <code>function</code> наверх ближайшей оборачивающей области видимости.</p>
<pre><code>// выражения с var переместились сюда
var bar, someValue; // по умолчанию - 'undefined'
// определение функции тоже переместилось
function test(data) {
var goo, i, e; // потерянная блочная область видимости
// переместилась сюда
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // вылетает с ошибкой TypeError,
// поскольку bar всё ещё 'undefined'
someValue = 42; // присвоения не подвержены высасыванию
bar = function() {};
test();
</code></pre>
<p>Потерянная область видимости блока не только переместит операторы <code>var</code> вовне циклов и их тел, но и сделает результаты некоторых конструкций с <code>if</code> неинтуитивными.</p>
<p>В исходном коде оператор <code>if</code> изменял <em>глобальную переменную</em> <code>goo</code>, когда, как оказалось, он изменяет <em>локальную переменную</em> — в результате работы высасывания.</p>
<p>Если вы не знакомы с <em>высасываниями</em>, то можете посчитать, что нижеприведённый код должен породить
<code>ReferenceError</code>.</p>
<pre><code>// проверить, проинициализована ли SomeImportantThing
if (!SomeImportantThing) {
var SomeImportantThing = {};
}
</code></pre>
<p>Но конечно же этот код работает: из-за того, что оператор <code>var</code> был перемещён наверх <em>глобальной области видимости</em></p>
<pre><code>var SomeImportantThing;
// другой код может инициализировать здесь переменную SomeImportantThing,
// а может и нет
// убедиться, что она всё ещё здесь
if (!SomeImportantThing) {
SomeImportantThing = {};
}
</code></pre>
</div><div><h3>Порядок разрешения имён</h3>
<p>Все области видимости в JavaScript, включая <em>глобальную области видимости</em>, содержат специальную, определённую внутри них, переменную <a href="#function.this"><code>this</code></a>, которая ссылается на <em>текущий объект</em>.</p>
<p>Области видимости функций также содержат внутри себя переменную <a href="#function.arguments"><code>arguments</code></a>, которая содержит аргументы, переданные в функцию.</p>
<p>Например, когда JavaScript пытается получить доступ к переменной <code>foo</code> в области видимости функции, он будет искать её по имени в такой последовательности:</p>
<ol>
<li>Если в текущей области видимости есть выражение <code>var foo</code>, использовать его.</li>
<li>Если один из параметров функции называется <code>foo</code>, использовать его.</li>
<li>Если функциия сама называется <code>foo</code>, использовать её.</li>
<li>Перейти на одну область видимости выше и начать с <strong>п. 1</strong></li>
</ol>
<aside>
<p><strong>Замечание:</strong> Наличие параметра функции с именем <code>arguments</code> <strong>не позволит</strong> движку создать объект <code>arguments</code>, создающийся по умолчанию.</p>
</aside>
</div><div><h3>Пространства имён</h3>
<p>Нередкое последствие наличия только одного глобального пространства имён — проблемы с перекрытием имён переменных. В JavaScript эту проблему легко избежать, используя <em>анонимные обёртки</em>.</p>
<pre><code>(function() {
// самостоятельно созданное "пространство имён"
window.foo = function() {
// открытое замыкание
};
})(); // сразу же выполнить функцию
</code></pre>
<p>Безымянные функции являются <a href="#function.general">выражениями</a>; поэтому, чтобы вы имели возможность их выполнить, они сперва должны быть разобраны.</p>
<pre><code>( // разобрать функцию внутри скобок
function() {}
) // и вернуть объект функции
() // вызвать результат разбора
</code></pre>
<p>Есть другие способы разбора и последующего вызова выражения с функцией; они, хоть и различаются в синтаксисе, но действуют одинаково.</p>
<pre><code>// Два других способа
+function(){}();
(function(){}());
</code></pre>
</div><div><h3>Заключение</h3>
<p>Рекомендуется всегда использовать <em>анонимную обёртку</em> для заключения кода в его собственное пространство имён. Это не только защищает код от совпадений имён, но и позваоляет создавать более модульные программы.</p>
<p>Важно добавить, что использование глобальных переменных считается <strong>плохой практикой</strong>. <strong>Любое</strong> их использование демонстрирует плохое качество кода и может привести к трудноуловимым ошибкам.</p></div></article></section><section id="array"><!-- Introduction--><header id="array.intro"><h1>Массивы</h1></header><!-- Articles--><article id="array.general"><h2> Итерации по массивам и свойства</h2><div><p>Несмотря на то, что массивы в JavaScript являются объектами, нет достаточных оснований для использования <a href="#object.forinloop">цикла <code>for in</code></a> для итерации по элементам массива. Фактически, существует несколько весомых причин <strong>против</strong> использования <code>for in</code> в массивах.</p>
<aside>
<p><strong>Замечание:</strong> Массивы в JavaScript <strong>не</strong> являются <em>ассоциативными массивами</em>. Для связывания ключей и значений в JavaScript есть только <a href="#object.general">объекты</a>. И при том, что ассоциативные массивы <strong>сохраняют</strong> заданный порядок, объекты <strong>не</strong> делают этого.</p>
</aside>
<p>Во время выполнения <code>for in</code> циклически перебираются все свойства объекта, находящиеся в цепочке прототипов. Единственный способ исключить ненужные свойства — использовать <a href="#object.hasownproperty"><code>hasOwnProperty</code></a>, а это <strong>в 20 раз</strong> медленнее обычного цикла <code>for</code>.</p>
</div><div><h3>Итерирование</h3>
<p>Для достижения лучшей производительности при итерации по массивам, лучше всего использовать обычный цикл <code>for</code>.</p>
<pre><code>var list = [1, 2, 3, 4, 5, ...... 100000000];
for(var i = 0, l = list.length; i < l; i++) {
console.log(list[i]);
}
</code></pre>
<p>В примере выше есть один дополнительный приём, с помощью которого кэшируется величина длины массива: <code>l = list.length</code>.</p>
<p>Несмотря на то, что свойство <code>length</code> определено в самом массиве, поиск этого свойства накладывает дополнительные расходы на каждой итерации цикла. Пусть в этом случае новые движки JavaScript теоретически <strong>могут</strong> применить оптимизацию, но нет никакого способа узнать, будет оптимизирован код на новом движке или нет.</p>
<p>Фактически, отсутствие кэширования может привести к выполнению цикла в <strong>два раза медленнее</strong>, чем при кэшировании длины</p>
</div><div><h3>Свойство <code>length</code></h3>
<p>Хотя <em>геттер</em> свойства <code>length</code> просто возвращает количество элементов содежащихся в массиве, <em>сеттер</em> можно использовать для <strong>обрезания</strong> массива.</p>
<pre><code>var foo = [1, 2, 3, 4, 5, 6];
foo.length = 3;
foo; // [1, 2, 3]
foo.length = 6;
foo; // [1, 2, 3]
</code></pre>
<p>Присвоение свойству <code>length</code> меньшей величины урезает массив, однако присвоение большего значения не даст никакого эффекта.</p>
</div><div><h3>Заключение</h3>
<p>Для оптимальной работы кода рекомендуется всегда использовать простой цикл <code>for</code> и кэшировать свойство <code>length</code>. Использование <code>for in</code> с массивами является признаком плохого кода, обладающего предпосылками к ошибкам и может привести к низкой скорости его выполнения.</p></div></article><article id="array.constructor"><h2>Конструктор <code>Array</code></h2><div><p>Так как в конструкторе <code>Array</code> есть некоторая двусмысленность касающаяся его параметров, настоятельно рекомендуется при создании массивов всегда использовать синтаксис литеральной нотации — <code>[]</code>.</p>
<pre><code>[1, 2, 3]; // Результат: [1, 2, 3]
new Array(1, 2, 3); // Результат: [1, 2, 3]
[3]; // Результат: [3]
new Array(3); // Результат: []
new Array('3') // Результат: ['3']
</code></pre>
<p>В случае, когда в конструктор <code>Array</code> передаётся один аргумент и этот аргумент имеет тип <code>Number</code>, конструктор возвращает новый, <em>заполненный случайными значениями</em>, массив, имеющий длину равную значению переданного аргумента. Стоит заметить, что в этом случае будет установлено только свойство <code>length</code> нового массива, индексы массива фактически не будут проинициализированы.</p>
<pre><code>var arr = new Array(3);
arr[1]; // не определён, undefined
1 in arr; // false, индекс не был установлен
</code></pre>
<p>Поведение, которое позволяет изначально установить только размер массива может пригодиться лишь в нескольких случаях, таких как повторение строк, за счёт чего избегается использование цикла <code>for loop</code>.</p>
<pre><code>new Array(count + 1).join(stringToRepeat);
</code></pre>
</div><div><h3>Заключение</h3>
<p>Использование конструктора <code>Array</code> нужно избегать, насколько это возможно. Литералы определённо предпочтительнее — это краткая запись и она имеет более понятный синтаксис, так что при этом даже улучшается читабельность кода.</p></div></article></section><section id="types"><!-- Introduction--><header id="types.intro"><h1>Типы</h1></header><!-- Articles--><article id="types.equality"><h2> Равенство и сравнение</h2><div><p>JavaScript имеет 2 различных способа сравнения значений объектов на равенство.</p>
</div><div><h3>Оператор сравнения</h3>
<p>Оператор сравнения состоит из <strong>двух</strong> символов равенства: <code>==</code></p>
<p><em>Слабая типизированность</em> языка JavaScript подразумевает <strong>приведение</strong> обеих переменных к <strong>одному типу</strong> для того, чтобы произвести сравнение.</p>
<pre><code>"" == "0" // false
0 == "" // true
0 == "0" // true
false == "false" // false
false == "0" // true
false == undefined // false
false == null // false
null == undefined // true
" \t\r\n" == 0 // true
</code></pre>
<p>В таблице выше показаны результаты приведения типов и это главная причина почему использование <code>==</code> повсеместно считается плохой практикой: оно приводит к трудностям в отслеживании ошибок из-за сложных правил преобразования типов.</p>
<p>Кроме того, приведение типов во время сравнения также влияет на производительность; например, строка должна быть преобразована в число перед сравнением с другим числом.</p>