本文将详细讲解DOM与BOM的基础概念,包括文档对象模型(DOM)和浏览器对象模型(BOM),适合初学者阅读。

1.简述 DOM(文档对象模型)的概念及 DOM 树的结构

DOM(Document Object Model,文档对象模型)是一套处理HTML和XML文档的编程接口,它将文档内容转换为一个结构化的对象树(DOM树),使程序(如JavaScript)能够访问、修改文档的内容、结构和样式。简单来说,DOM是文档与脚本语言(如JavaScript)之间的“桥梁”,让静态的文档具备了动态交互的能力。

DOM的核心特点

  • 平台无关:DOM定义了通用的文档操作规范,与具体编程语言(如JavaScript、Python)和平台无关。
  • 树状结构:文档的所有内容(标签、文本、属性等)被抽象为“节点”,按层级关系组织成树,便于遍历和操作。
  • 动态性:通过DOM提供的API,可实时修改文档的结构(如添加/删除标签)、内容(如修改文本)和样式(如改变颜色),且修改会即时反映到页面上。

DOM树的结构

DOM树是节点(Node)的层次化集合,每个节点代表文档中的一个部分(如标签、文本、注释等)。节点之间通过“父子关系”“兄弟关系”形成树状结构,核心特点如下:

1. 节点类型(常见)

DOM树中的节点主要有以下几种类型(每种节点有对应的属性和方法):

  • 文档节点(Document Node):整个文档的根节点,代表整个HTML/XML文档,是DOM树的起点(通过document对象访问)。
  • 元素节点(Element Node):对应HTML标签(如<html><div><p>),是DOM树的核心,可包含子节点(其他元素、文本等)。
  • 文本节点(Text Node):包含在元素节点中的文本内容(如<p>Hello</p>中的“Hello”)。
  • 属性节点(Attribute Node):元素的属性(如<img src="pic.jpg">中的src属性),依附于元素节点存在。
  • 注释节点(Comment Node):文档中的注释(<!-- 这是注释 -->)。

2. 层次关系(以HTML为例)

DOM树的结构严格对应HTML文档的嵌套关系,最顶层是文档节点(document),其唯一的直接子节点是<html>(称为“文档元素”),再往下按HTML的嵌套层级延伸,形成父子、兄弟关系:

  • 父节点(Parent Node):包含其他节点的节点(如<body><p>的父节点)。
  • 子节点(Child Node):被其他节点包含的节点(如<p><body>的子节点)。
  • 兄弟节点(Sibling Node):同一父节点下的同级节点(如<h1><p>若同属<body>,则互为兄弟节点)。

3. 示例:HTML文档对应的DOM树

以下是一个简单的HTML文档:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>
<head>
<title>DOM示例</title>
</head>
<body>
<h1>Hello</h1>
<p>这是一个段落</p>
</body>
</html>
```

对应的DOM树结构如下(简化):

document(文档节点)
└── html(元素节点,文档元素)
├── head(元素节点,html的子节点)
│ └── title(元素节点,head的子节点)
│ └── “DOM示例”(文本节点,title的子节点)
└── body(元素节点,html的子节点,与head是兄弟节点)
├── h1(元素节点,body的子节点)
│ └── “Hello”(文本节点,h1的子节点)
└── p(元素节点,body的子节点,与h1是兄弟节点)
└── “这是一个段落”(文本节点,p的子节点)

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

### 总结

- **DOM**是连接文档与程序的接口,将文档转换为可操作的对象树。
- **DOM树**以节点为基本单位,按层级关系组织(文档节点→元素节点→文本节点等),反映了HTML/XML的嵌套结构。
- 通过DOM树,JavaScript可以便捷地访问和修改文档内容(如`document.getElementById()`获取元素、`element.textContent`修改文本),是前端交互的核心基础。

## 2.如何获取 DOM 元素?常见的 DOM 选择器方法有哪些(如 getElementById、querySelector)?

在 JavaScript 中,获取 DOM 元素主要通过 `document` 对象或元素节点提供的**选择器方法**实现。常见方法可分为「传统选择器」和「现代 CSS 选择器」两类,核心区别在于支持的语法、返回值类型及是否“实时更新”。以下是常用 DOM 选择器方法的详细说明:

### 一、按返回结果分类:单个元素 vs 元素集合

#### 1. 返回单个元素的方法(匹配则返回第一个/唯一元素,否则返回 `null`)

- **`document.getElementById(id)`**
- 作用:根据元素的 `id` 属性获取唯一元素(`id` 在文档中需唯一)。
- 特点:
- 仅 `document` 上有此方法(元素节点无),因 `id` 全局唯一;
- 参数为 `id` 字符串(无需加 `#`);
- 效率最高(DOM 树对 `id` 有索引优化)。
- 示例:

```html
<div id="box">内容</div>
<script>
const box = document.getElementById("box"); // 正确获取 div#box
</script>
  • document.querySelector(selector)
    • 作用:根据 CSS 选择器语法(如类、标签、后代选择器等)获取第一个匹配的元素

    • 特点:

      • 支持所有 CSS 选择器(如 .class#iddiv p:hover 等);
      • 可在 document 或任意元素节点上调用(如 parent.querySelector('.child'));
      • 返回第一个匹配元素,无匹配则返回 null
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      <div class="container">
      <p class="text">第一段</p>
      <p class="text">第二段</p>
      </div>
      <script>
      const firstText = document.querySelector(".container .text"); // 获取第一个 .text 元素(第一段)
      </script>

2. 返回元素集合的方法(匹配所有元素,返回类数组对象)

  • document.getElementsByClassName(className)

    • 作用:根据元素的 class 属性获取所有匹配的元素。

    • 特点:

      • 返回 **HTMLCollection**(类数组,需转换为数组后使用 forEach 等方法);
      • 实时更新:文档中新增/删除匹配类的元素时,集合会自动同步变化;
      • 参数为类名字符串(多个类名用空格分隔,如 getElementsByClassName("a b") 匹配同时有 ab 类的元素);
      • 可在 document 或元素节点上调用。
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      <div class="item">1</div>
      <div class="item">2</div>
      <script>
      const items = document.getElementsByClassName("item"); // HTMLCollection(2)
      console.log(items.length); // 2
      // 转换为数组后遍历
      Array.from(items).forEach(item => console.log(item.textContent)); // 1, 2
      </script>
  • document.getElementsByTagName(tagName)

    • 作用:根据 HTML 标签名(如 divp)获取所有匹配的元素。

    • 特点:

      • 返回 **HTMLCollection**(类数组,实时更新);
      • 参数为标签名字符串(不区分大小写,如 getElementsByTagName("DIV") 等效于 div);
      • 特殊值 * 表示获取文档中所有元素(document.getElementsByTagName("*"));
      • 可在 document 或元素节点上调用。
    • 示例:

      1
      2
      3
      4
      5
      <p>文本1</p>
      <p>文本2</p>
      <script>
      const paras = document.getElementsByTagName("p"); // HTMLCollection(2)
      </script>
  • document.getElementsByName(name)

    • 作用:根据元素的 name 属性获取所有匹配的元素(常用于表单元素,如 inputradio)。

    • 特点:

      • 返回 **NodeList**(类数组,非实时更新);
      • document 上有此方法;
    • 示例:

      1
      2
      3
      4
      5
      <input type="radio" name="gender" value="male">
      <input type="radio" name="gender" value="female">
      <script>
      const genders = document.getElementsByName("gender"); // NodeList(2)
      </script>
  • document.querySelectorAll(selector)

    • 作用:根据 CSS 选择器语法获取所有匹配的元素

    • 特点:

      • 支持所有 CSS 选择器(同 querySelector);
      • 返回 **NodeList**(类数组,非实时更新);
      • 可在 document 或元素节点上调用;
      • NodeList 支持 forEach 方法(无需转换数组),但仍需通过 Array.from() 转换后使用 mapfilter 等数组方法。
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      <ul>
      <li class="list-item">1</li>
      <li class="list-item">2</li>
      </ul>
      <script>
      const listItems = document.querySelectorAll(".list-item"); // NodeList(2)
      listItems.forEach(item => console.log(item.textContent)); // 1, 2(直接用 forEach)
      </script>

二、特殊元素的快捷获取方法

  • **document.documentElement**:获取 <html> 根元素。
  • **document.body**:获取 <body> 元素。
  • element.children:获取元素的直接子元素(返回 HTMLCollection,实时更新,仅包含元素节点,排除文本/注释节点)。

三、核心区别对比表

方法名 返回值类型 是否实时更新 支持语法 适用场景
getElementById 单个元素(Element) - id 字符串 按唯一 id 获取元素
querySelector 单个元素(Element) - CSS 选择器 按 CSS 规则获取第一个元素
getElementsByClassName HTMLCollection 类名字符串 批量获取同 class 元素(需实时同步)
getElementsByTagName HTMLCollection 标签名字符串 批量获取同标签元素(需实时同步)
getElementsByName NodeList name 字符串 批量获取同 name 元素(表单常用)
querySelectorAll NodeList CSS 选择器 按 CSS 规则批量获取元素(非实时)

四、选择建议

  1. id 获取:优先用 getElementById(效率最高);
  2. 按 CSS 规则获取单个元素:用 querySelector(灵活,支持复杂选择器);
  3. 批量获取且需实时更新:用 getElementsByClassName/getElementsByTagName(如动态渲染的列表);
  4. 批量获取且无需实时更新:用 querySelectorAll(灵活,支持所有 CSS 选择器,NodeList 直接支持 forEach)。

通过以上方法,可覆盖绝大多数 DOM 元素获取场景,结合类数组转换(Array.from())可便捷地对集合进行遍历和操作。

3.DOM 元素的常用属性有哪些(如 innerHTML、innerText、textContent)?它们的区别是什么?

DOM 元素的属性用于访问或修改元素的内容、样式、结构等信息,其中与内容操作相关的属性最为常用,核心包括 innerHTMLinnerTexttextContent 等,此外还有 outerHTMLvalueclassName 等用于特定场景的属性。以下详细说明这些属性的作用及核心区别:

一、常用 DOM 元素属性及作用

1. 内容操作核心属性

(1)innerHTML
  • 作用:设置或获取元素内部的 HTML 结构(包括所有子元素的标签、文本、属性等)。
  • 特点
    • 解析 HTML 标签(若设置内容包含 <div><span> 等标签,会被浏览器渲染为对应元素);
    • 读写时均包含元素内的所有 HTML 代码(包括子元素的标签)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
<div id="box"><span>Hello</span></div>
<script>
const box = document.getElementById("box");
console.log(box.innerHTML); // '<span>Hello</span>'(获取内部HTML)
box.innerHTML = '<p>New Content</p>'; // 设置后,box 内变为 <p>New Content</p>
</script>
```

##### (2)`innerText`

- **作用**:设置或获取元素**内部的文本内容**(自动忽略 HTML 标签,仅保留纯文本)。
- **特点**:
- **不解析 HTML 标签**(若设置内容包含 `<p>` 等标签,会被当作纯文本显示);
- 受 CSS 样式影响:**不会获取隐藏元素的文本**(如 `display: none` 或 `visibility: hidden` 的元素内容);
- 会自动处理换行和空格(根据元素的布局规则,如块级元素的换行)。

```html
<div id="box">
<span style="display: none;">隐藏文本</span>
可见文本
</div>
<script>
const box = document.getElementById("box");
console.log(box.innerText); // '可见文本'(忽略隐藏元素的内容)
box.innerText = '<p>New Text</p>'; // 页面显示 '<p>New Text</p>'(标签被当作文本)
</script>
```

##### (3)`textContent`

- **作用**:设置或获取元素**内部的所有文本内容**(包括所有子节点的文本,忽略 HTML 标签)。
- **特点**:
- **不解析 HTML 标签**(与 `innerText` 一致);
- **不受 CSS 样式影响**:会获取所有文本,包括 `display: none` 隐藏的元素内容;
- 包含 `<script>`、`<style>` 标签内的文本(`innerText` 会忽略这些标签的内容);
- 是 W3C 标准属性,兼容性更好(除非常旧的 IE 浏览器)。

```html
<div id="box">
<span style="display: none;">隐藏文本</span>
<script>console.log('脚本')</script>
可见文本
</div>
<script>
const box = document.getElementById("box");
console.log(box.textContent);
// 输出:'隐藏文本 console.log('脚本') 可见文本'(包含隐藏内容和script内文本)
</script>
```

#### 2. 其他常用属性

##### (4)`outerHTML`

- **作用**:设置或获取元素**自身及内部的完整 HTML 结构**(包括元素自身的标签)。
- 区别于 `innerHTML`:`innerHTML` 仅包含内部内容,`outerHTML` 包含元素自身。

```html
<div id="box"><span>内容</span></div>
<script>
const box = document.getElementById("box");
console.log(box.outerHTML); // '<div id="box"><span>内容</span></div>'(包含自身标签)
box.outerHTML = '<p>替换整个元素</p>'; // 原 div 被替换为 p 元素
</script>
```

##### (5)`value`

- **作用**:获取或设置**表单元素**(如 `input`、`textarea`、`select`)的用户输入值。

```html
<input type="text" id="username" value="默认值">
<script>
const input = document.getElementById("username");
console.log(input.value); // '默认值'(获取输入值)
input.value = "新值"; // 设置输入框内容为 '新值'
</script>
```

##### (6)`className` 与 `classList`

- **作用**:操作元素的 `class` 属性(CSS 类名)。
- `className`:直接读写完整的类名字符串(如 `box active`);
- `classList`:提供方法(`add`、`remove`、`toggle`)更灵活地操作单个类名。

```html
<div id="box" class="box active"></div>
<script>
const box = document.getElementById("box");
console.log(box.className); // 'box active'(获取类名)

box.classList.remove("active"); // 移除 active 类
box.classList.add("new-class"); // 添加 new-class 类
console.log(box.className); // 'box new-class'
</script>
```

##### (7)`id`

- **作用**:获取或设置元素的 `id` 属性(唯一标识,常用于选择元素)。

```html
<div id="old-id"></div>
<script>
const div = document.getElementById("old-id");
div.id = "new-id"; // 修改 id 为 new-id
</script>
```

##### (8)`style`

- **作用**:获取或设置元素的**行内样式**(`style` 属性中的 CSS 样式),返回一个 `CSSStyleDeclaration` 对象。

```html
<div id="box" style="color: red; font-size: 16px;"></div>
<script>
const box = document.getElementById("box");
console.log(box.style.color); // 'red'(获取行内样式)
box.style.fontSize = "20px"; // 设置行内样式(注意驼峰命名,如 fontSize 对应 font-size)
</script>
```

### 二、`innerHTML`、`innerText`、`textContent` 的核心区别

| 对比维度 | `innerHTML` | `innerText` | `textContent` |
|-------------------------|----------------------------|------------------------------|------------------------------|
| **是否解析 HTML 标签** | 是(会渲染标签为元素) | 否(标签被当作纯文本) | 否(标签被当作纯文本) |
| **是否受 CSS 样式影响** | 否(直接读写 HTML 结构) | 是(忽略 `display: none` 内容) | 否(包含所有文本,包括隐藏内容) |
| **包含 `<script>`/`<style>` 内容** | 是(包含标签及内部代码) | 否(忽略这些标签的内容) | 是(包含内部文本) |
| **性能影响** | 可能触发重排/重绘(因解析 HTML) | 可能触发重排(因考虑样式布局) | 性能更好(不解析、不考虑样式) |
| **XSS 风险** | 高(若插入不可信内容,可能注入恶意脚本) | 低(仅文本,无标签解析) | 低(仅文本) |

### 三、适用场景总结

- **`innerHTML`**:需动态插入 HTML 结构时(如渲染列表、富文本),但需注意过滤不可信内容以避免 XSS 攻击。
- **`innerText`**:需获取“用户可见”的文本(如爬虫提取页面可见内容),或设置纯文本且需遵循布局样式时。
- **`textContent`**:需获取元素内所有文本(包括隐藏内容),或纯文本操作(性能更优,推荐优先使用)。

通过理解这些属性的差异,可根据具体需求选择合适的方法,平衡功能、性能和安全性。

## 4.如何创建和插入 DOM 元素?(如 createElement、appendChild、insertBefore)

在 JavaScript 中,创建和插入 DOM 元素是动态修改页面结构的核心操作,主要通过以下步骤实现:**先创建元素节点 **→** 配置元素属性/内容 **→** 插入到文档中**。常用 API 包括 `createElement`(创建元素)、`appendChild`(末尾插入)、`insertBefore`(指定位置插入)等,以下详细说明:

### 一、创建 DOM 元素

通过 `document.createElement(tagName)` 方法创建新的元素节点,返回创建的元素对象(未插入文档前,仅存在于内存中)。

#### 步骤

1. **创建元素**:`const element = document.createElement('标签名')`(如 `'div'`、`'p'`、`'span'`)。
2. **配置元素**:设置属性(`id`、`class` 等)、内容(文本、HTML)或样式。

#### 示例:创建并配置元素

```javascript
// 1. 创建一个 div 元素
const newDiv = document.createElement('div');

// 2. 配置属性
newDiv.id = 'new-div'; // 设置 id
newDiv.className = 'box'; // 设置 class
newDiv.setAttribute('data-id', '123'); // 设置自定义属性

// 3. 设置内容(文本或 HTML)
newDiv.textContent = '这是新创建的 div'; // 设置纯文本
// 或 newDiv.innerHTML = '<span>带标签的内容</span>'; // 设置 HTML 结构

// 4. 设置样式(行内样式)
newDiv.style.color = 'blue';
newDiv.style.fontSize = '16px';
```

### 二、插入 DOM 元素

创建元素后,需通过插入方法将其添加到文档的 DOM 树中(否则不会显示在页面上)。常用插入方法如下:

#### 1. `appendChild()`:插入到父元素的末尾

**语法**:`parentElement.appendChild(newElement)`

- 作用:将 `newElement` 插入到 `parentElement` 的**子节点列表的最后**。
- 特点:若 `newElement` 已存在于 DOM 树中,会**从原位置移动到新位置**(DOM 元素不能同时存在于多个地方)。

#### 示例:使用 `appendChild`

```html
<!-- 初始结构 -->
<div id="container">
<p>已存在的 p 元素</p>
</div>

<script>
// 创建新元素
const newP = document.createElement('p');
newP.textContent = '新插入的 p 元素';

// 获取父容器
const container = document.getElementById('container');

// 插入到 container 的末尾
container.appendChild(newP);
</script>

<!-- 插入后结构 -->
<div id="container">
<p>已存在的 p 元素</p>
<p>新插入的 p 元素</p> <!-- 新元素被添加到末尾 -->
</div>
```

#### 2. `insertBefore()`:插入到指定元素之前

**语法**:`parentElement.insertBefore(newElement, referenceElement)`

- 作用:将 `newElement` 插入到 `parentElement` 中 `referenceElement`(参考元素)的**前面**。
- 参数:
- `newElement`:要插入的新元素;
- `referenceElement`:参考元素(必须是 `parentElement` 的子节点),若为 `null`,则效果等同于 `appendChild`(插入到末尾)。

#### 示例:使用 `insertBefore`

```html
<!-- 初始结构 -->
<div id="container">
<p id="existing">已存在的 p 元素</p>
</div>

<script>
// 创建新元素
const newP = document.createElement('p');
newP.textContent = '插入到 existing 前面的 p 元素';

// 获取父容器和参考元素
const container = document.getElementById('container');
const existingP = document.getElementById('existing');

// 插入到 existingP 之前
container.insertBefore(newP, existingP);
</script>

<!-- 插入后结构 -->
<div id="container">
<p>插入到 existing 前面的 p 元素</p> <!-- 新元素在前面 -->
<p id="existing">已存在的 p 元素</p>
</div>
```

#### 3. 其他常用插入方法(ES6+)

除了传统的 `appendChild` 和 `insertBefore`,现代浏览器还支持更灵活的方法:

- **`append()`**:类似 `appendChild`,但可同时插入**多个节点或字符串**(字符串会被转为文本节点)。

```javascript
container.append(newP, '直接插入文本'); // 同时插入元素和文本
```

- **`prepend()`**:插入到父元素的**子节点列表的最前面**(与 `append` 相反)。

```javascript
container.prepend(newP); // 新元素成为第一个子节点
```

- **`insertAdjacentElement(position, element)`**:根据相对当前元素的位置插入(更精细的位置控制)。
- `position` 可选值:
- `'beforebegin'`:当前元素的前面(作为兄弟节点);
- `'afterbegin'`:当前元素的内部开头(作为第一个子节点);
- `'beforeend'`:当前元素的内部末尾(作为最后一个子节点);
- `'afterend'`:当前元素的后面(作为兄弟节点)。

```javascript
const existing = document.getElementById('existing');
existing.insertAdjacentElement('afterend', newP); // 插入到 existing 后面(兄弟节点)
```

### 三、批量插入:使用文档片段(DocumentFragment)

当需要插入**多个元素**时,直接循环调用 `appendChild` 会频繁触发页面重排(影响性能)。推荐使用 `DocumentFragment`(文档片段)临时存储元素,最后一次性插入,减少重排次数。

#### 示例:批量插入优化

```javascript
// 创建文档片段(内存中的临时容器)
const fragment = document.createDocumentFragment();

// 批量创建元素并添加到片段
for (let i = 1; i <= 5; i++) {
const item = document.createElement('li');
item.textContent = `列表项 ${i}`;
fragment.appendChild(item); // 添加到片段(不触发重排)
}

// 一次性插入到页面(仅触发一次重排)
document.querySelector('ul').appendChild(fragment);
```

### 四、注意事项

1. **元素移动特性**:若插入的元素已存在于 DOM 树中,插入操作会将其从原位置**移动**到新位置(而非复制)。

```javascript
const p = document.querySelector('p');
document.body.appendChild(p); // p 会从原父元素移动到 body 中
```

2. **参考元素的有效性**:`insertBefore` 的 `referenceElement` 必须是父元素的子节点,否则会抛出错误。

3. **性能优化**:批量插入时优先使用 `DocumentFragment`,减少重排次数;避免频繁使用 `innerHTML`(可能导致 XSS 风险和全量重绘)。

### 总结

- **创建元素**:用 `document.createElement` 创建,通过 `id`、`className`、`textContent` 等配置属性和内容。
- **插入元素**:
- `appendChild`:插入到父元素末尾;
- `insertBefore`:插入到指定元素之前;
- 现代方法(`append`、`prepend`、`insertAdjacentElement`)提供更灵活的插入方式。
- **批量插入**:使用 `DocumentFragment` 优化性能。

这些操作是动态生成页面内容(如列表渲染、表单动态添加)的基础,掌握后可灵活控制页面结构。

## 5.如何删除和替换 DOM 元素?(如 removeChild、replaceChild)

在 JavaScript 中,删除和替换 DOM 元素是动态修改页面结构的重要操作,主要通过 `removeChild`(删除)、`replaceChild`(替换)等方法实现,现代浏览器还支持更简洁的 `remove` 方法(直接删除元素)。以下详细说明具体用法和区别:

### 一、删除 DOM 元素

删除元素的核心是将目标元素从 DOM 树中移除,使其不再显示在页面上(但仍可在内存中被引用或重新插入)。

#### 1. `removeChild()`:通过父元素删除子元素

**语法**:`parentElement.removeChild(childElement)`

- 作用:从 `parentElement` 的子节点中删除 `childElement`。
- 返回值:被删除的 `childElement`(仍可被重新使用,如插入到其他位置)。
- 注意:
- 必须通过**父元素**调用,且 `childElement` 必须是 `parentElement` 的直接子节点,否则会报错;
- 若不确定父元素,可通过 `childElement.parentNode` 获取其父元素。

**示例:使用 `removeChild` 删除元素**

```html
<!-- 初始结构 -->
<ul id="list">
<li>项目1</li>
<li id="item2">项目2</li>
<li>项目3</li>
</ul>

<script>
// 1. 获取要删除的元素
const item2 = document.getElementById('item2');

// 2. 获取其父元素(或直接使用已知的父元素)
const parent = item2.parentNode; // 这里父元素是 ul#list

// 3. 删除子元素
const deletedItem = parent.removeChild(item2);

console.log(deletedItem); // <li id="item2">项目2</li>(被删除的元素仍在内存中)
</script>

<!-- 删除后结构 -->
<ul id="list">
<li>项目1</li>
<li>项目3</li>
</ul>
```

#### 2. `remove()`:直接删除元素自身(现代方法)

**语法**:`element.remove()`

- 作用:直接删除 `element` 本身(无需通过父元素),更简洁。
- 特点:
- 是 ES6 新增方法,现代浏览器(Chrome 54+、Firefox 49+ 等)支持;
- 无需获取父元素,直接调用元素自身的 `remove` 方法即可。

**示例:使用 `remove` 删除元素**

```html
<!-- 初始结构 -->
<div id="box">要删除的元素</div>

<script>
const box = document.getElementById('box');
box.remove(); // 直接删除元素,无需父元素
</script>

<!-- 删除后结构:div#box 已被移除 -->
```

### 二、替换 DOM 元素

替换元素是用新元素替换现有元素,被替换的元素会从 DOM 树中移除,新元素占据其位置。

#### `replaceChild()`:用新元素替换旧元素

**语法**:`parentElement.replaceChild(newElement, oldElement)`

- 作用:在 `parentElement` 中,用 `newElement` 替换 `oldElement`。
- 返回值:被替换的 `oldElement`(仍可在内存中被引用)。
- 注意:
- `oldElement` 必须是 `parentElement` 的直接子节点;
- 若 `newElement` 已存在于 DOM 树中,会先从原位置移除,再插入到新位置(移动而非复制)。

**示例:使用 `replaceChild` 替换元素**

```html
<!-- 初始结构 -->
<div id="container">
<p id="old-p">旧段落</p>
</div>

<script>
// 1. 创建新元素(或使用已存在的元素)
const newP = document.createElement('p');
newP.textContent = '新段落';
newP.id = 'new-p';

// 2. 获取父元素和要被替换的旧元素
const container = document.getElementById('container');
const oldP = document.getElementById('old-p');

// 3. 替换元素
const replacedElement = container.replaceChild(newP, oldP);

console.log(replacedElement); // <p id="old-p">旧段落</p>(被替换的元素)
</script>

<!-- 替换后结构 -->
<div id="container">
<p id="new-p">新段落</p> <!-- 新元素替换了旧元素 -->
</div>
```

### 三、注意事项

1. **元素的内存引用**:
被删除或替换的元素只是从 DOM 树中移除,并未被垃圾回收(仍在内存中)。若保留其引用,可重新插入到 DOM 树中:

```javascript
const deletedItem = parent.removeChild(item2);
// 一段时间后重新插入
parent.appendChild(deletedItem); // 元素会重新显示在页面上
```

2. **`removeChild` 的兼容性**:
所有浏览器(包括旧版 IE)均支持 `removeChild`,而 `remove` 方法在 IE 中不支持(需用 `removeChild` 替代)。

3. **替换元素的来源**:
`replaceChild` 的 `newElement` 可以是新创建的元素,也可以是页面中已存在的元素(此时会执行“移动”操作):

```javascript
// 用页面中已存在的元素替换
const existingElement = document.getElementById('other-element');
parent.replaceChild(existingElement, oldElement); // existingElement 会从原位置移动到新位置
```

4. **避免报错**:
使用 `removeChild` 或 `replaceChild` 时,需确保:
- 父元素存在且正确;
- 被删除/替换的元素确实是父元素的直接子节点(可通过 `parent.contains(child)` 提前检查)。

### 总结

- **删除元素**:
- 传统方法:`parent.removeChild(child)`(兼容性好,需父元素);
- 现代方法:`child.remove()`(简洁,无需父元素,现代浏览器支持)。
- **替换元素**:
- 核心方法:`parent.replaceChild(newElement, oldElement)`(用新元素替换旧元素,返回被替换的旧元素)。

这些操作是动态维护页面结构的基础,适用于场景如“删除列表项”“替换用户头像”“动态更新内容”等。

## 6.简述 DOM 事件的类型(如鼠标事件、键盘事件、表单事件、加载事件)

DOM 事件是用户与页面交互(如点击、输入)或页面状态变化(如加载、尺寸改变)时触发的信号,JavaScript 通过监听这些事件实现动态交互。DOM 事件可按触发场景分为多个类型,核心类型及常见事件如下:

### 一、鼠标事件(Mouse Events)

用户通过鼠标与页面元素交互时触发,是最常用的事件类型之一。

| 事件名 | 触发时机 | 示例场景 |
|---------------|--------------------------------------------------------------------------|-----------------------------------|
| `click` | 鼠标左键单击元素(按下并释放) | 点击按钮提交表单 |
| `dblclick` | 鼠标左键双击元素 | 双击文本编辑 |
| `mousedown` | 鼠标按下(任意键,包括左键、右键) | 按住鼠标拖动元素 |
| `mouseup` | 鼠标按键释放(与 `mousedown` 对应) | 拖动结束释放鼠标 |
| `mousemove` | 鼠标在元素上移动(持续触发) | 跟踪鼠标位置显示坐标 |
| `mouseover` | 鼠标从元素外部移入元素内部(会冒泡,子元素触发时父元素也会触发) | 鼠标移入导航栏显示下拉菜单 |
| `mouseout` | 鼠标从元素内部移出到外部(会冒泡) | 鼠标移出导航栏隐藏下拉菜单 |
| `mouseenter` | 鼠标从元素外部移入元素内部(**不冒泡**,仅元素自身触发) | 鼠标移入卡片高亮显示 |
| `mouseleave` | 鼠标从元素内部移出到外部(**不冒泡**,仅元素自身触发) | 鼠标移出卡片取消高亮 |
| `wheel` | 鼠标滚轮滚动(或触摸板滚动) | 滚动页面时加载更多内容 |

### 二、键盘事件(Keyboard Events)

用户操作键盘时触发,用于监听按键输入(如表单输入、快捷键)。

| 事件名 | 触发时机 | 注意事项 |
|---------------|--------------------------------------------------------------------------|-----------------------------------|
| `keydown` | 键盘按键按下(任意键,包括功能键如 `Ctrl`、`Shift`,持续按住会重复触发) | 最常用,可监听所有按键 |
| `keyup` | 键盘按键释放(与 `keydown` 对应) | 适合监听“单次按键完成”操作 |
| `keypress` | 按下字符键(如字母、数字、符号,不包括 `Shift`、`F1` 等功能键) | **已不推荐使用**,建议用 `keydown` 替代 |

### 三、表单事件(Form Events)

与表单元素(`input`、`select`、`textarea` 等)交互时触发,用于处理用户输入。

| 事件名 | 触发时机 | 示例场景 |
|---------------|--------------------------------------------------------------------------|-----------------------------------|
| `input` | 表单元素内容实时变化(如输入框打字、粘贴、删除) | 实时验证输入内容(如手机号格式) |
| `change` | 表单元素内容改变且**失去焦点**(`input`/`textarea`),或选择项变化(`select`) | 输入完成后验证(如用户名查重) |
| `submit` | 表单提交(点击提交按钮或按回车) | 阻止默认提交,改用 Ajax 提交 |
| `reset` | 表单被重置(点击重置按钮) | 重置后提示用户 |
| `focus` | 元素获取焦点(如点击输入框、按 `Tab` 键切换) | 输入框获取焦点时显示提示文字 |
| `blur` | 元素失去焦点(与 `focus` 对应) | 输入框失去焦点时验证内容 |

### 四、加载事件(Load Events)

与文档或资源(图片、脚本等)加载状态相关的事件,用于处理页面初始化。

| 事件名 | 触发时机 | 区别与用途 |
|---------------------|--------------------------------------------------------------------------|-----------------------------------|
| `load` | 目标资源完全加载完成(文档:整个 HTML 解析+所有资源(图片、CSS、脚本)加载完成;图片:图片加载完成) | 页面全部资源加载后执行初始化(如轮播图启动) |
| `DOMContentLoaded` | 文档的 DOM 树构建完成(无需等待图片、CSS 等资源加载) | 优先执行 DOM 相关初始化(如绑定事件) |
| `unload` | 页面卸载时(如关闭标签页、导航到新页面) | 保存用户状态(兼容性较差) |
| `beforeunload` | 页面即将卸载前(用户确认离开前) | 提示用户“是否离开”(如未保存的表单) |
| `error` | 资源加载失败(如图片、脚本加载出错) | 显示错误提示(如“图片加载失败”) |

### 五、其他常用事件类型

#### 1. 触摸事件(Touch Events,移动设备)

针对触摸屏设备的交互,替代鼠标事件:

- `touchstart`:手指触摸屏幕时触发;
- `touchend`:手指离开屏幕时触发;
- `touchmove`:手指在屏幕上滑动时触发。

#### 2. 拖拽事件(Drag Events)

用于元素拖拽交互(需先设置元素 `draggable="true"`):

- `dragstart`:开始拖拽元素时触发;
- `drag`:拖拽过程中持续触发;
- `dragend`:结束拖拽时触发;
- `dragover`:拖拽元素经过目标元素时触发;
- `drop`:拖拽元素释放到目标元素上时触发。

#### 3. 窗口事件(Window Events)

与浏览器窗口状态相关:

- `resize`:窗口尺寸改变时触发(如拉伸窗口);
- `scroll`:页面或元素滚动时触发(如监听滚动位置加载更多)。

### 总结

DOM 事件按交互场景分为鼠标、键盘、表单、加载等类型,每种事件对应特定的用户操作或状态变化。理解事件类型是实现页面交互的基础(如用 `click` 处理按钮点击、`input` 监听输入变化、`DOMContentLoaded` 初始化页面),后续可通过事件监听(`addEventListener`)绑定处理逻辑。

## 7.DOM 中的事件对象(Event)有哪些常用属性和方法(如 target、currentTarget、preventDefault)?

在 DOM 事件处理中,**事件对象(Event)** 是一个自动传递给事件处理函数的特殊对象,它包含了与当前事件相关的所有信息(如触发事件的元素、事件类型、鼠标位置等),并提供了控制事件行为的方法(如阻止默认行为、阻止冒泡)。以下是其常用属性和方法的详细说明:

### 一、常用属性(事件信息相关)

#### 1. 事件目标相关属性(最核心)

- **`target`**
- 作用:返回**触发事件的具体元素**(事件的原始触发点)。
- 示例:点击按钮内部的 `<span>`,`target` 是 `<span>`(即使事件绑定在按钮上)。

- **`currentTarget`**
- 作用:返回**当前绑定事件的元素**(事件处理函数所在的元素),与 `this` 指向一致(箭头函数中 `this` 不绑定,需用 `currentTarget`)。
- 示例:按钮上绑定事件,点击按钮内部的 `<span>`,`currentTarget` 是按钮(绑定事件的元素)。

- **`srcElement`**
- 作用:`target` 的旧版别名,主要用于早期 IE 浏览器,现代浏览器已支持 `target`,建议优先使用 `target`。

**示例:`target` vs `currentTarget`**

```html
<button id="btn">
<span>点击我</span>
</button>

<script>
const btn = document.getElementById('btn');
btn.addEventListener('click', function(e) {
console.log(e.target); // <span>点击我</span>(实际触发事件的元素)
console.log(e.currentTarget); // <button id="btn">...</button>(绑定事件的元素)
console.log(this === e.currentTarget); // true(this 指向 currentTarget)
});
</script>
```

#### 2. 事件类型与状态属性

- **`type`**
- 作用:返回事件的类型(字符串),如 `'click'`、`'input'`、`'keydown'` 等。
- 用途:一个处理函数处理多种事件时,通过 `type` 判断事件类型。

```javascript
element.addEventListener('click', handleEvent);
element.addEventListener('mouseover', handleEvent);

function handleEvent(e) {
if (e.type === 'click') {
console.log('点击事件');
} else if (e.type === 'mouseover') {
console.log('鼠标移入事件');
}
}
```

- **`bubbles`**
- 作用:返回布尔值,表示事件是否会冒泡(如 `click` 会冒泡,`focus` 不会冒泡)。

- **`cancelable`**
- 作用:返回布尔值,表示事件的默认行为是否可以被 `preventDefault()` 阻止(如 `submit` 可取消,`load` 不可取消)。

#### 3. 鼠标事件专属属性

- **`clientX` / `clientY`**
- 作用:返回鼠标触发事件时,相对于**浏览器可视区域左上角**的 X/Y 坐标(忽略滚动条位置)。

- **`pageX` / `pageY`**
- 作用:返回鼠标触发事件时,相对于**文档左上角**的 X/Y 坐标(包含滚动条滚动的距离)。

- **`screenX` / `screenY`**
- 作用:返回鼠标触发事件时,相对于**电脑屏幕左上角**的 X/Y 坐标。

- **`button`**
- 作用:返回数值,表示触发事件的鼠标按键(0:左键,1:中键,2:右键)。

#### 4. 键盘事件专属属性

- **`key`**
- 作用:返回按下的键对应的字符(如 `'a'`、`'Enter'`、`'ArrowUp'`),直观易懂。

- **`keyCode`**
- 作用:返回按下的键的 ASCII 码(已逐渐被 `key` 替代,如 `Enter` 对应 13,`a` 对应 65)。

- **`ctrlKey` / `shiftKey` / `altKey` / `metaKey`**
- 作用:返回布尔值,表示事件触发时 `Ctrl`/`Shift`/`Alt`/`Meta`(Windows 键或 Command 键)是否被按下(用于监听快捷键,如 `Ctrl+S`)。

### 二、常用方法(事件行为控制)

#### 1. **`preventDefault()`**

- 作用:**阻止事件的默认行为**(如表单提交、链接跳转、右键菜单弹出等)。
- 适用场景:需要自定义事件逻辑时(如用 Ajax 提交表单,而非默认刷新)。

**示例:阻止链接跳转**

```html
<a href="https://example.com" id="link">示例链接</a>

<script>
const link = document.getElementById('link');
link.addEventListener('click', function(e) {
e.preventDefault(); // 阻止链接默认跳转行为
console.log('链接被点击,但不跳转');
});
</script>
```

#### 2. **`stopPropagation()`**

- 作用:**阻止事件冒泡**(事件不会从当前元素向上传播到父元素,避免父元素的事件处理函数被触发)。
- 事件冒泡:事件触发后,会从触发元素(`target`)向上传递到父元素、祖父元素……直到 `document`(如点击子元素,父元素的同类型事件也会被触发)。

**示例:阻止事件冒泡**

```html
<div id="parent" style="padding: 20px; background: red;">
父元素
<div id="child" style="padding: 20px; background: blue;">子元素</div>
</div>

<script>
// 父元素点击事件
parent.addEventListener('click', () => console.log('父元素被点击'));

// 子元素点击事件
child.addEventListener('click', (e) => {
e.stopPropagation(); // 阻止事件冒泡到父元素
console.log('子元素被点击');
});
</script>
<!-- 点击子元素时,仅输出 "子元素被点击"(父元素事件不触发) -->
```

#### 3. **`stopImmediatePropagation()`**

- 作用:**同时阻止事件冒泡和当前元素上的其他同类型事件处理函数**(比 `stopPropagation` 更彻底)。
- 场景:同一元素绑定了多个同类型事件处理函数时,阻止后续函数执行。

```javascript
element.addEventListener('click', (e) => {
e.stopImmediatePropagation();
console.log('第一个处理函数');
});

element.addEventListener('click', () => {
console.log('第二个处理函数'); // 不会执行(被 stopImmediatePropagation 阻止)
});
```

#### 4. **`composedPath()`**

- 作用:返回事件传播路径的数组(从触发元素 `target` 到 `window` 的所有元素),直观展示事件冒泡的层级。

```javascript
element.addEventListener('click', (e) => {
console.log(e.composedPath()); // [子元素, 父元素, body, html, document, window]
});
```

### 三、核心区别总结

| 属性/方法 | 核心作用 | 典型场景 |
|-------------------|-------------------------------------------|-----------------------------------|
| `target` | 事件的原始触发元素 | 委托事件中定位实际操作的元素 |
| `currentTarget` | 绑定事件的当前元素(与 `this` 一致) | 区分事件绑定者与触发者 |
| `preventDefault()`| 阻止事件默认行为(如跳转、提交) | 自定义表单提交、链接点击逻辑 |
| `stopPropagation()`| 阻止事件冒泡 | 避免父元素事件被误触发 |
| `type` | 获取事件类型(如 `'click'`) | 一个函数处理多种事件类型 |

事件对象是 DOM 事件处理的核心,通过其属性可获取事件细节,通过其方法可控制事件行为,是实现交互逻辑(如委托事件、快捷键、自定义表单)的基础。

## 8.什么是事件委托?为什么要使用事件委托?举例说明实现方式

事件委托(Event Delegation)是 JavaScript 中一种高效处理 DOM 事件的模式,其核心思想是**利用事件冒泡机制,将子元素的事件处理逻辑委托给父元素(或更上层的祖先元素)**,让父元素统一接收并处理所有子元素的事件,而无需为每个子元素单独绑定事件。

### 一、为什么需要事件委托?

事件委托的优势主要体现在**性能优化**和**动态元素处理**两方面:

1. **减少事件绑定次数,优化性能**
若页面中有大量子元素(如列表中的 1000 个 `<li>`),传统方式需要为每个子元素绑定事件(如 `click`),会创建大量事件处理函数,占用更多内存。而事件委托只需给父元素绑定**一次事件**,即可处理所有子元素的事件,大幅减少内存消耗。

2. **自动支持动态添加的子元素**
传统方式中,动态添加的子元素(如通过 JavaScript 新增的 `<li>`)需要重新绑定事件,否则事件无法触发。而事件委托绑定在父元素上,**新添加的子元素会自动继承事件处理逻辑**,无需额外操作。

### 二、事件委托的实现原理

事件委托依赖 DOM 的**事件冒泡机制**:当子元素触发事件(如 `click`),事件会从子元素向上传播(冒泡)到父元素、祖父元素……直至 `document`。因此,父元素可以捕获到子元素触发的事件,并通过事件对象的 `target` 属性识别具体是哪个子元素触发了事件,从而执行对应逻辑。

### 三、实现方式(示例)

以“列表项点击事件”为例,传统方式需要为每个 `<li>` 绑定事件,而事件委托只需绑定 `<ul>` 即可。

#### 示例场景:点击列表项,输出其内容

##### 1. 传统方式(无事件委托)

```html
<ul id="list">
<li>项目1</li>
<li>项目2</li>
<li>项目3</li>
</ul>

<script>
// 传统方式:为每个 li 绑定事件
const items = document.querySelectorAll('#list li');
items.forEach(item => {
item.addEventListener('click', function() {
console.log('点击了:', this.textContent);
});
});

// 问题:动态添加的 li 无法触发事件
const newLi = document.createElement('li');
newLi.textContent = '项目4';
document.getElementById('list').appendChild(newLi); // 点击项目4 无反应
</script>
```

##### 2. 事件委托方式(委托给父元素)

```html
<ul id="list">
<li>项目1</li>
<li>项目2</li>
<li>项目3</li>
</ul>

<script>
// 事件委托:给父元素 ul 绑定一次事件
const list = document.getElementById('list');
list.addEventListener('click', function(e) {
// 关键:通过事件对象的 target 识别触发事件的子元素
// 检查触发事件的是否是 li 元素(避免父元素自身或其他子元素触发)
if (e.target.tagName === 'LI') {
console.log('点击了:', e.target.textContent);
}
});

// 动态添加的 li 自动支持事件(无需重新绑定)
const newLi = document.createElement('li');
newLi.textContent = '项目4';
list.appendChild(newLi); // 点击项目4 会正常输出内容
</script>
```

#### 更灵活的判断:使用 `closest()` 匹配选择器

若子元素结构复杂(如 `<li>` 内部有 `<span>` 等嵌套元素),`e.target` 可能是内部嵌套元素(而非 `<li>` 本身)。此时可使用 `closest(selector)` 方法,向上查找最近的匹配选择器的祖先元素(包括自身),确保准确识别目标元素。

```html
<ul id="list">
<li>项目1 <span>(点击我)</span></li>
<li>项目2 <span>(点击我)</span></li>
</ul>

<script>
const list = document.getElementById('list');
list.addEventListener('click', function(e) {
// 查找最近的 li 元素(即使点击的是内部的 span)
const targetLi = e.target.closest('li');
if (targetLi) { // 确保找到 li 元素
console.log('点击了:', targetLi.textContent.trim());
}
});
</script>
```

### 四、注意事项

1. **事件必须支持冒泡**:事件委托依赖事件冒泡,不冒泡的事件(如 `focus`、`blur`)无法使用委托(可改用 `focusin`、`focusout` 等冒泡版事件)。
2. **精准判断目标元素**:需通过 `tagName`、`classList`、`closest()` 等方式准确识别目标子元素,避免父元素的其他子元素(非目标元素)触发逻辑。
3. **避免过度委托**:不宜将事件委托到过上层的元素(如 `document` 或 `body`),可能导致事件处理函数被过多无关事件触发,影响性能。

### 总结

- **事件委托**:利用事件冒泡,将子元素事件委托给父元素处理,减少绑定次数。
- **核心优势**:优化性能(减少内存占用)、支持动态元素(无需重复绑定)。
- **实现关键**:通过事件对象的 `target` 识别触发元素,结合 `closest()` 等方法精准匹配目标。

事件委托是处理大量子元素或动态元素事件的最佳实践,广泛应用于列表、表格、动态表单等场景。

## 9.简述 DOM 的重绘(Repaint)和重排(Reflow/Layout)的概念及区别。如何减少重排和重绘?

在浏览器渲染DOM的过程中,**重绘(Repaint)** 和**重排(Reflow/Layout)** 是影响页面性能的两个关键概念,它们的本质是浏览器更新页面视觉表现的不同阶段,开销差异显著。

### 一、概念定义

#### 1. 重排(Reflow/Layout,也叫回流)

当DOM元素的**几何属性发生改变**(如尺寸、位置、布局结构)时,浏览器需要重新计算元素的位置和大小,并重新构建页面的布局树(Layout Tree),这个过程称为重排。

例如:

- 改变元素的`width`、`height`、`margin`、`padding`;
- 改变元素的`display`(如从`none`变为`block`);
- 滚动页面或改变浏览器窗口大小;
- 增加/删除DOM元素(影响整体布局结构)。

重排会导致浏览器重新计算整个页面或部分区域的布局,**开销较大**,因为一个元素的位置变化可能会连锁影响其他元素(如子元素、兄弟元素、父元素)。

#### 2. 重绘(Repaint)

当DOM元素的**外观属性发生改变**(不影响布局和几何结构)时,浏览器只需重新绘制元素的视觉表现(如颜色、背景),无需重新计算布局,这个过程称为重绘。

例如:

- 改变元素的`color`、`background-color`;
- 改变元素的`border-color`、`box-shadow`;
- 改变元素的`visibility`(从`visible`变为`hidden`,元素仍占据空间,不影响布局)。

重绘仅涉及元素的视觉样式更新,**开销小于重排**,因为无需重新计算布局。

### 二、核心区别

| 维度 | 重排(Reflow) | 重绘(Repaint) |
|--------------|------------------------------|--------------------------------|
| 触发原因 | 元素几何属性/布局结构改变 | 元素外观属性改变(不影响布局) |
| 处理过程 | 重新计算布局 → 重绘 | 直接重绘(无需重新计算布局) |
| 性能开销 | 大(可能连锁影响多个元素) | 小(仅影响单个元素外观) |
| 关联性 | 重排**必定会导致重绘** | 重绘**不一定导致重排** |

### 三、如何减少重排和重绘?

重排和重绘是浏览器渲染的自然过程,但频繁触发会导致页面卡顿(尤其是复杂页面)。优化核心是**减少触发频率**和**降低影响范围**,具体方法如下:

#### 1. 批量修改DOM,减少单次操作

频繁单独修改DOM(如循环中逐个修改`style`)会多次触发重排。建议先将DOM从文档流中“脱离”,批量修改后再重新插入,减少重排次数。

**常用技巧**:

- **隐藏元素后修改**:先设置元素`display: none`(触发1次重排),批量修改后再恢复`display`(再触发1次重排),总重排次数从多次变为2次。

```javascript
const element = document.getElementById('box');
element.style.display = 'none'; // 脱离文档流,触发1次重排
// 批量修改(不会触发重排)
element.style.width = '200px';
element.style.height = '100px';
element.style.margin = '10px';
element.style.display = 'block'; // 恢复显示,触发1次重排
```

- **使用文档片段(DocumentFragment)**:通过内存中的文档片段临时存储DOM修改,最后一次性插入文档,仅触发1次重排。

```javascript
const fragment = document.createDocumentFragment();
// 批量创建元素并添加到片段(不触发重排)
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
// 一次性插入文档(仅触发1次重排)
document.body.appendChild(fragment);
```

#### 2. 避免频繁读取“布局属性”,减少强制同步布局

浏览器为了优化性能,会将多次重排操作放入“队列”延迟执行。但如果在修改样式后**立即读取布局属性**(如`offsetWidth`、`scrollTop`、`getBoundingClientRect()`),浏览器会强制清空队列并执行重排(确保读取到最新值),导致“读写交替”触发多次重排。

**优化方式**:

- 集中读取布局属性(一次读取多个);
- 先完成所有写操作(修改样式),再进行读操作。

```javascript
// 错误示例:读写交替,触发多次重排
const box = document.getElementById('box');
for (let i = 0; i < 10; i++) {
box.style.width = `${i * 10}px`;
console.log(box.offsetWidth); // 读取时强制重排,共触发10次
}

// 正确示例:先写后读,仅触发1次重排
const box = document.getElementById('box');
// 先完成所有写操作(浏览器会暂存队列)
for (let i = 0; i < 10; i++) {
box.style.width = `${i * 10}px`;
}
// 最后一次读取(触发1次重排)
console.log(box.offsetWidth);
```

#### 3. 使用CSS触发重绘而非重排,或利用合成层

- **优先修改非布局属性**:如需要修改元素样式,优先改变`color`、`background`等仅触发重绘的属性,避免修改`width`、`margin`等触发重排的属性。

- **使用`transform`和`opacity`**:这两个属性的修改不会触发重排或重绘,而是由浏览器的“合成线程”直接处理(仅触发“合成”阶段),性能极高,适合动画场景。

```css
/* 推荐:仅触发合成,无重排/重绘 */
.animate {
transition: transform 0.3s;
}
.animate:hover {
transform: translateX(10px); /* 无重排/重绘 */
opacity: 0.8; /* 无重排/重绘 */
}
```

#### 4. 脱离文档流,缩小重排范围

将频繁重排的元素从正常文档流中脱离(如设置`position: absolute`或`fixed`),使其布局变化不影响其他元素,从而缩小重排范围。

例如:悬浮菜单、弹窗等组件,设置`position: absolute`后,其位置变化仅影响自身,不会触发父元素或兄弟元素的重排。

#### 5. 减少不必要的DOM深度和复杂性

DOM结构越复杂,重排时需要计算的元素越多,开销越大。建议:

- 简化DOM层级(避免过深嵌套);
- 减少不必要的子元素(如冗余的`<div>`);
- 使用CSS Grid/Flexbox替代复杂的浮动布局(现代布局方式重排效率更高)。

#### 6. 使用虚拟DOM或框架优化

现代前端框架(如React、Vue)通过“虚拟DOM”批量对比DOM变化,仅将必要的修改同步到真实DOM,减少实际重排/重绘次数。

### 总结

- **重排**是布局改变导致的重新计算,开销大;**重绘**是外观改变导致的重新绘制,开销小,且重排必定引发重绘。
- 优化核心:**批量操作DOM**、**避免强制同步布局**、**使用高效CSS属性**、**缩小重排范围**。

减少重排和重绘是前端性能优化的关键环节,尤其对动画密集型页面(如电商首页、数据可视化)至关重要。

## 10.什么是 BOM(浏览器对象模型)?BOM 包含哪些核心对象(如 window、document、location)?

BOM(Browser Object Model,浏览器对象模型)是**浏览器提供的一套用于操作浏览器窗口和与浏览器交互的对象集合**。它不像DOM(文档对象模型)那样有明确的W3C标准,但各浏览器都遵循一套通用的实现规范。

BOM的核心作用是**控制浏览器的行为和状态**(如窗口大小、导航、历史记录、浏览器信息等),而不是操作页面内容(页面内容由DOM控制)。BOM的所有对象都以浏览器窗口为核心,通过全局的 `window` 对象访问。

### BOM的核心对象及作用

BOM的核心对象都是 `window` 对象的属性(可直接访问,省略 `window.` 前缀),主要包括以下几种:

#### 1. `window`:BOM的核心对象

`window` 代表**浏览器窗口本身**,是BOM的顶层对象,所有其他BOM对象都是它的属性。同时,它也是JavaScript的全局对象,全局变量和函数都会成为 `window` 的属性。

**主要功能**:

- 控制窗口状态(如打开、关闭、调整大小);
- 提供全局方法(如定时器、弹窗);
- 包含其他BOM对象(如 `location`、`navigator` 等)。

**常用属性和方法**:

```javascript
// 窗口尺寸
window.innerWidth; // 浏览器可视区域宽度(含滚动条)
window.innerHeight; // 浏览器可视区域高度

// 打开/关闭窗口
const newWindow = window.open('https://example.com'); // 打开新窗口
newWindow.close(); // 关闭新窗口

// 定时器
const timer = window.setTimeout(() => console.log('延迟执行'), 1000); // 延迟1秒执行
window.clearTimeout(timer); // 清除定时器

// 弹窗
window.alert('提示信息'); // 警告弹窗
window.confirm('确认操作?'); // 确认弹窗(返回布尔值)
window.prompt('请输入内容:'); // 输入弹窗(返回输入值)
```

#### 2. `document`:文档对象(DOM与BOM的桥梁)

`document` 是 `window` 的属性,代表**当前页面的文档内容**,属于DOM的核心对象,但因被包含在 `window` 中,常被视为BOM与DOM的连接点。

**主要功能**:操作页面内容(如获取元素、修改HTML、监听事件)。

**常用操作**:

```javascript
document.getElementById('id'); // 通过ID获取元素
document.title; // 获取/设置页面标题
document.body; // 获取<body>元素
document.createElement('div'); // 创建DOM元素
```

#### 3. `location`:URL信息与导航对象

`location` 包含当前页面的**URL信息**,并提供修改URL和导航的方法。

**主要功能**:获取/修改URL、刷新页面、跳转页面。

**常用属性和方法**:

```javascript
// URL相关属性
location.href; // 完整URL(如 "https://example.com/path?name=test#hash")
location.protocol; // 协议(如 "https:")
location.host; // 主机名+端口(如 "example.com:8080")
location.pathname; // 路径(如 "/path")
location.search; // 查询参数(如 "?name=test")
location.hash; // 哈希值(如 "#hash")

// 导航方法
location.assign('https://baidu.com'); // 跳转到新URL(添加历史记录)
location.replace('https://baidu.com'); // 跳转到新URL(替换当前历史记录,无法后退)
location.reload(); // 刷新当前页面(参数true强制从服务器刷新)
```

#### 4. `navigator`:浏览器信息对象

`navigator` 提供**浏览器自身的信息**(如浏览器类型、版本、操作系统等),常用于浏览器兼容性判断。

**常用属性**:

```javascript
navigator.userAgent; // 浏览器用户代理字符串(如 "Mozilla/5.0 (Windows NT 10.0; ...)")
navigator.appName; // 浏览器名称(如 "Netscape",多数浏览器返回此值)
navigator.platform; // 操作系统平台(如 "Win32"、"MacIntel")
navigator.language; // 浏览器默认语言(如 "zh-CN")
navigator.onLine; // 布尔值,判断浏览器是否联网
```

#### 5. `screen`:屏幕信息对象

`screen` 包含**用户设备屏幕的信息**(如尺寸、分辨率),常用于适配不同屏幕。

**常用属性**:

```javascript
screen.width; // 屏幕宽度(像素)
screen.height; // 屏幕高度(像素)
screen.availWidth; // 屏幕可用宽度(减去任务栏等,像素)
screen.availHeight; // 屏幕可用高度(像素)
screen.colorDepth; // 屏幕颜色深度(如 24 位)
```

#### 6. `history`:历史记录对象

`history` 管理浏览器的**会话历史记录**(当前标签页的浏览记录),可实现前进、后退等操作。

**常用方法**:

```javascript
history.back(); // 后退到上一页(等同于浏览器的「后退」按钮)
history.forward(); // 前进到下一页(等同于浏览器的「前进」按钮)
history.go(n); // 跳转到历史记录中的第n页(n=1前进1页,n=-1后退1页,n=0刷新当前页)
history.length; // 历史记录的总条数
```

### BOM与DOM的区别

| 维度 | BOM(浏览器对象模型) | DOM(文档对象模型) |
|--------------|--------------------------------------|------------------------------------|
| 核心对象 | `window`(浏览器窗口) | `document`(页面文档) |
| 操作目标 | 浏览器窗口、导航、历史记录等浏览器相关功能 | 页面内容(标签、文本、样式等) |
| 标准 | 无正式标准,依赖浏览器实现 | 有W3C标准,各浏览器实现统一 |
| 包含关系 | BOM包含DOM(`window.document`) | 独立于BOM,专注于文档内容 |

### 总结

BOM是操作浏览器的核心工具,通过 `window` 及其属性(`location`、`navigator`、`history` 等)控制浏览器行为;其核心价值在于实现与浏览器的交互(如导航、获取设备信息、控制窗口),与DOM共同构成了前端页面交互的基础。

## 11.window 对象的常用属性和方法有哪些(如 window.innerWidth、window.open、window.alert)?

`window` 对象是浏览器 BOM(浏览器对象模型)的核心,代表浏览器窗口本身,同时也是 JavaScript 的全局对象(全局变量和函数默认挂载在 `window` 上)。它提供了大量属性和方法,用于控制窗口状态、操作浏览器行为、管理全局资源等。以下是其常用属性和方法的分类说明:

### 一、窗口尺寸与位置属性

用于获取或设置浏览器窗口的尺寸和位置(部分方法受浏览器安全限制,可能无法在所有环境使用)。

| 属性/方法 | 作用描述 |
|-------------------------|--------------------------------------------------------------------------|
| `innerWidth` / `innerHeight` | 获取浏览器**可视区域**的宽度/高度(包含滚动条,不包含浏览器边框、工具栏)。 |
| `outerWidth` / `outerHeight` | 获取整个浏览器**窗口**的宽度/高度(包含边框、工具栏等)。 |
| `screenLeft` / `screenTop` | 获取窗口左上角相对于**屏幕左上角**的水平/垂直距离(单位:像素)。 |
| `moveTo(x, y)` | 将窗口移动到屏幕的 `(x, y)` 坐标位置(部分浏览器因安全限制禁用)。 |
| `resizeTo(width, height)` | 将窗口调整为指定的 `width`(宽)和 `height`(高)(部分浏览器禁用)。 |

**示例**:

```javascript
// 获取可视区域尺寸(常用,用于响应式布局)
console.log('可视区域宽:', window.innerWidth); // 如 1920
console.log('可视区域高:', window.innerHeight); // 如 1080

// 获取窗口位置
console.log('窗口左上角X:', window.screenLeft);
console.log('窗口左上角Y:', window.screenTop);
```

### 二、窗口操作方法

用于打开、关闭窗口或控制窗口显示状态。

| 方法 | 作用描述 |
|---------------------|--------------------------------------------------------------------------|
| `open(url, name, features)` | 打开新窗口或标签页。<br>- `url`:新窗口的URL(可选,默认空白页);<br>- `name`:窗口名称(可选,用于标识窗口);<br>- `features`:窗口特征字符串(如 `width=500,height=300`,可选)。 |
| `close()` | 关闭当前窗口(通常只能关闭由 `open()` 打开的窗口,否则可能被浏览器阻止)。 |
| `focus()` | 使窗口获得焦点(前置显示)。 |
| `blur()` | 使窗口失去焦点(后置显示)。 |

**示例**:

```javascript
// 打开一个新窗口(宽500,高300,无菜单栏)
const newWindow = window.open(
'https://example.com',
'exampleWindow',
'width=500,height=300,menubar=no'
);

// 关闭新窗口
newWindow.close();

// 聚焦到当前窗口
window.focus();
```

### 三、全局弹窗方法

用于显示与用户交互的弹窗(因会阻塞代码执行,通常建议少用,可自定义弹窗替代)。

| 方法 | 作用描述 |
|---------------------|--------------------------------------------------------------------------|
| `alert(message)` | 显示警告弹窗,包含 `message` 文本和“确定”按钮(无返回值)。 |
| `confirm(message)` | 显示确认弹窗,包含 `message` 文本和“确定”“取消”按钮(返回布尔值:`true` 表示确定,`false` 表示取消)。 |
| `prompt(message, defaultValue)` | 显示输入弹窗,包含 `message` 文本、输入框和按钮(返回输入框的值,取消则返回 `null`;`defaultValue` 为输入框默认值)。 |

**示例**:

```javascript
// 警告弹窗
window.alert('操作成功!');

// 确认弹窗
const isDelete = window.confirm('确定要删除吗?');
if (isDelete) {
console.log('用户确认删除');
}

// 输入弹窗
const username = window.prompt('请输入用户名:', 'guest');
if (username) {
console.log('用户输入:', username);
}
```

### 四、定时器方法

用于延迟执行或周期性执行代码(异步执行,不阻塞后续代码)。

| 方法 | 作用描述 |
|-----------------------|--------------------------------------------------------------------------|
| `setTimeout(callback, delay)` | 延迟 `delay` 毫秒(1秒=1000毫秒)后执行 `callback` 函数,返回定时器ID(用于清除)。 |
| `clearTimeout(timerId)` | 清除由 `setTimeout` 创建的定时器(传入定时器ID)。 |
| `setInterval(callback, interval)` | 每隔 `interval` 毫秒重复执行 `callback` 函数,返回定时器ID。 |
| `clearInterval(timerId)` | 清除由 `setInterval` 创建的定时器。 |

**示例**:

```javascript
// 延迟1秒执行
const timer1 = window.setTimeout(() => {
console.log('1秒后执行');
}, 1000);

// 清除延迟定时器(若在1秒内执行,则不会触发)
window.clearTimeout(timer1);

// 每隔2秒执行一次
const timer2 = window.setInterval(() => {
console.log('每2秒执行一次');
}, 2000);

// 5秒后停止周期性执行
window.setTimeout(() => {
window.clearInterval(timer2);
}, 5000);
```

### 五、全局对象与属性

`window` 作为全局对象,包含以下重要属性(常用作全局访问入口):

| 属性 | 作用描述 |
|---------------------|--------------------------------------------------------------------------|
| `document` | 指向当前页面的DOM文档对象(核心,用于操作页面内容)。 |
| `location` | 包含当前URL信息,用于导航和修改地址(如 `window.location.href` 获取完整URL)。 |
| `navigator` | 包含浏览器信息(如 `userAgent` 可判断浏览器类型)。 |
| `history` | 管理浏览器历史记录(如 `history.back()` 后退一页)。 |
| `screen` | 包含屏幕信息(如 `screen.width` 获取屏幕宽度)。 |
| 全局变量/函数 | 用 `var` 声明的全局变量、全局函数默认成为 `window` 的属性(如 `var a=1` 等价于 `window.a=1`)。 |

**示例**:

```javascript
// 访问全局对象
console.log(window.document === document); // true(document 是 window 的属性)
console.log(window.location.href); // 当前页面URL

// 全局变量自动成为 window 属性
var globalVar = '我是全局变量';
console.log(window.globalVar); // '我是全局变量'
```

### 六、其他常用方法

| 方法 | 作用描述 |
|---------------------|--------------------------------------------------------------------------|
| `scrollTo(x, y)` | 滚动页面到指定坐标 `(x, y)`(`x` 水平滚动,`y` 垂直滚动)。 |
| `scrollBy(x, y)` | 相对于当前位置滚动页面(`x` 水平滚动距离,`y` 垂直滚动距离)。 |
| `getComputedStyle(element)` | 获取元素的计算样式(最终应用的CSS样式,包括继承和默认样式)。 |

**示例**:

```javascript
// 滚动到页面顶部(y=0)
window.scrollTo(0, 0);

// 向下滚动100px
window.scrollBy(0, 100);

// 获取元素的计算样式
const box = document.getElementById('box');
const style = window.getComputedStyle(box);
console.log('实际宽度:', style.width); // 如 "200px"
```

### 总结

`window` 对象是浏览器交互的核心入口,其属性和方法覆盖了**窗口控制**(尺寸、位置、打开/关闭)、**用户交互**(弹窗)、**代码调度**(定时器)、**全局资源访问**(`document`、`location` 等)等场景。掌握这些常用API是实现浏览器级交互(如响应式布局、页面导航、定时任务)的基础。

## 12.location 对象的常用属性和方法有哪些(如 href、pathname、reload、replace)?

`location` 对象是 BOM(浏览器对象模型)的核心成员之一,封装了当前页面的 URL 信息,并提供了修改 URL、导航、刷新页面等方法。它是 `window` 对象的属性(可通过 `window.location` 访问,也可直接简写为 `location`)。以下是其常用属性和方法的详细说明:

### 一、常用属性(URL 信息相关)

`location` 的属性用于获取或修改 URL 的各个组成部分,修改大部分属性会触发页面导航(跳转或刷新)。

| 属性名 | 作用描述 | 示例(假设 URL 为 `https://example.com:8080/path/page?name=test#top`) |
|---------------|--------------------------------------------------------------------------|-----------------------------------------------------------------------|
| `href` | 获取或设置**完整 URL**(最常用的属性)。 | `location.href` → `"https://example.com:8080/path/page?name=test#top"` |
| `protocol` | 获取或设置 URL 的**协议**(如 `http:`、`https:`,末尾带冒号)。 | `location.protocol` → `"https:"` |
| `host` | 获取或设置 URL 的**主机名 + 端口**(若端口为默认值,可能省略端口)。 | `location.host` → `"example.com:8080"` |
| `hostname` | 获取或设置 URL 的**主机名**(不含端口)。 | `location.hostname` → `"example.com"` |
| `port` | 获取或设置 URL 的**端口号**(若为默认端口,返回空字符串)。 | `location.port` → `"8080"` |
| `pathname` | 获取或设置 URL 的**路径部分**(以 `/` 开头)。 | `location.pathname` → `"/path/page"` |
| `search` | 获取或设置 URL 的**查询参数**(以 `?` 开头,包含所有 `key=value`)。 | `location.search` → `"name=test"`(注意:部分浏览器会保留 `?`) |
| `hash` | 获取或设置 URL 的**哈希值**(以 `#` 开头,常用于页面内锚点)。 | `location.hash` → `"#top"` |

**示例:操作 URL 属性**

```javascript
// 1. 获取完整 URL
console.log(location.href); // 输出当前页面完整 URL

// 2. 修改 href 实现页面跳转(最常用的导航方式)
location.href = "https://baidu.com"; // 跳转到百度

// 3. 修改 pathname(会触发页面导航到新路径)
location.pathname = "/new-page"; // 当前域名下跳转至 /new-page

// 4. 修改 search(更新查询参数,页面会刷新)
location.search = "?page=2&size=10"; // URL 变为 ...?page=2&size=10

// 5. 修改 hash(仅改变哈希值,不刷新页面,常用于单页应用路由)
location.hash = "#section1"; // 页面滚动到 id="section1" 的元素
```

### 二、常用方法(导航与刷新相关)

`location` 的方法用于主动控制页面导航、刷新或替换历史记录,功能更灵活。

| 方法名 | 作用描述 | 与属性的区别 |
|---------------|--------------------------------------------------------------------------|------------------------------------------------------------------------------|
| `assign(url)` | 导航到指定 `url`(与修改 `href` 效果类似),会在历史记录中添加新条目(可通过 `history.back()` 后退)。 | 等价于 `location.href = url`,但语义更清晰(明确表示“导航”操作)。 |
| `replace(url)`| 导航到指定 `url`,但**替换当前历史记录条目**(不会添加新记录,无法通过 `history.back()` 回到原页面)。 | 适合“跳转后不允许后退”的场景(如登录后跳转到首页,不希望退回登录页)。 |
| `reload(force)`| 刷新当前页面。<br>- 参数 `force` 为 `true` 时:强制从服务器重新加载(忽略缓存);<br>- 省略或 `false` 时:优先使用缓存刷新。 | 模拟浏览器的“刷新”按钮功能,`force=true` 等价于“强制刷新”(Ctrl+F5)。 |
| `toString()` | 返回当前页面的完整 URL(等价于 `location.href`)。 | 常用于简化代码(如 `console.log(location.toString())` 等价于 `console.log(location.href)`)。 |

**示例:使用 location 方法**

```javascript
// 1. 导航到新页面(保留历史记录)
location.assign("https://example.com"); // 可通过 history.back() 退回当前页

// 2. 导航到新页面(替换历史记录,无法后退)
location.replace("https://example.com"); // 原页面不会保留在历史记录中

// 3. 刷新页面(使用缓存)
location.reload(); // 普通刷新

// 4. 强制刷新(忽略缓存,从服务器重新获取资源)
location.reload(true); // 强制刷新(类似 Ctrl+F5)
```

### 三、核心特点与注意事项

1. **属性修改触发导航**:除 `hash` 外,修改 `href`、`pathname`、`search` 等属性都会触发页面导航(跳转或刷新);修改 `hash` 仅会滚动到页面锚点,不触发全页刷新。
2. **历史记录影响**:`assign()` 和修改 `href` 会添加新历史记录,`replace()` 会替换当前记录,这直接影响用户能否通过“后退”按钮返回原页面。
3. **缓存控制**:`reload(true)` 强制刷新时,浏览器会重新请求所有资源(忽略本地缓存),适合需要获取最新数据的场景,但性能开销较大。

### 总结

`location` 对象是控制 URL 和页面导航的核心工具:

- **属性**用于拆分或修改 URL 的各个部分(如 `href` 操作完整 URL、`search` 处理查询参数);
- **方法**用于主动导航(`assign`/`replace`)或刷新(`reload`),提供更精细的页面控制。

掌握这些 API 对于实现页面跳转、参数传递、刷新控制等功能至关重要,尤其在单页应用(SPA)和多页面导航中频繁使用。

## 13.history 对象的常用方法有哪些(如 pushState、replaceState、go、back)?它们的作用是什么?

`history` 对象是 BOM(浏览器对象模型)的核心成员,用于管理浏览器的**会话历史记录**(当前标签页的浏览记录)。它允许开发者通过编程方式控制页面的前进、后退,甚至在不刷新页面的情况下添加或修改历史记录(配合单页应用路由使用)。以下是其常用方法及作用:

### 一、传统导航方法(控制页面前进/后退)

这类方法模拟浏览器的“前进”“后退”按钮功能,基于已有的历史记录进行导航。

| 方法名 | 作用描述 | 示例场景 |
|-----------------|--------------------------------------------------------------------------|-----------------------------------|
| `back()` | 导航到**上一条历史记录**(等同于点击浏览器的“后退”按钮)。 | 用户点击“返回”按钮回到上一页 |
| `forward()` | 导航到**下一条历史记录**(等同于点击浏览器的“前进”按钮)。 | 用户点击“前进”按钮进入下一页 |
| `go(n)` | 导航到历史记录中**相对当前位置的第 n 条记录**:<br>- `n > 0`:前进 n 页;<br>- `n < 0`:后退 n 页;<br>- `n = 0`:刷新当前页。 | 实现“前进 2 页”“后退 1 页”等操作 |

**示例:传统导航方法**

```javascript
// 后退到上一页(如从 page2 回到 page1)
history.back();

// 前进到下一页(如从 page1 回到 page2)
history.forward();

// 后退 2 页(若存在足够的历史记录)
history.go(-2);

// 前进 1 页
history.go(1);

// 刷新当前页(等价于 location.reload())
history.go(0);
```

### 二、HTML5 新增方法(修改历史记录,不刷新页面)

HTML5 新增了 `pushState()` 和 `replaceState()` 方法,允许在**不触发页面刷新**的情况下添加或修改历史记录条目。这是单页应用(SPA)实现“无刷新路由”的核心技术(如 React Router、Vue Router 底层依赖)。

#### 1. `pushState(state, title, url)`

- **作用**:向历史记录栈**添加一条新记录**,不会触发页面刷新。
- **参数**:
- `state`:状态对象(任意类型,会被序列化存储),可在 `popstate` 事件中获取,用于保存与该历史记录相关的数据(如路由参数)。
- `title`:历史记录的标题(目前多数浏览器忽略此参数,可传空字符串)。
- `url`(可选):新历史记录的 URL(必须与当前页面同域,否则报错),会显示在地址栏,但不会触发导航。
- **特点**:新增记录后,`history.length`(历史记录总数)会 +1。

#### 2. `replaceState(state, title, url)`

- **作用**:用新记录**替换当前历史记录**,不会触发页面刷新,也不会改变历史记录总数。
- **参数**:与 `pushState()` 完全相同。
- **特点**:替换后,`history.length` 不变(用新记录覆盖了当前记录)。

**示例:添加/修改历史记录(单页应用场景)**

```javascript
// 假设当前页面 URL 为 https://example.com/home

// 1. 添加新历史记录(地址栏显示为 /about,不刷新页面)
history.pushState(
{ page: 'about', id: 1 }, // 状态对象(存储页面相关数据)
'关于我们', // 标题(多数浏览器忽略)
'/about' // 新 URL(同域)
);
// 此时地址栏显示 https://example.com/about,但页面内容未变(需手动更新)

// 2. 替换当前历史记录(地址栏显示为 /contact,不刷新页面)
history.replaceState(
{ page: 'contact' },
'联系我们',
'/contact'
);
// 此时地址栏显示 https://example.com/contact,历史记录总数不变

// 3. 结合页面更新逻辑(实际开发中需手动更新 DOM)
function updatePage(state) {
// 根据 state 中的数据更新页面内容(如渲染 about 页或 contact 页)
console.log('更新页面为:', state.page);
}
```

### 三、配合 `popstate` 事件监听历史记录变化

`pushState()` 和 `replaceState()` 仅修改历史记录,不会触发任何事件。当用户**点击浏览器前进/后退按钮**或调用 `back()`/`forward()`/`go()` 时,会触发 `popstate` 事件,开发者可在事件中获取历史记录的 `state` 对象,从而更新页面内容。

**示例:监听历史记录变化**

```javascript
// 监听 popstate 事件(用户点击前进/后退时触发)
window.addEventListener('popstate', (event) => {
// event.state 即为 pushState/replaceState 时传入的状态对象
if (event.state) {
console.log('当前历史记录状态:', event.state);
updatePage(event.state); // 根据状态更新页面内容
}
});

// 此时用户点击“后退”按钮,会触发上述事件,实现页面内容更新
```

### 四、核心区别与应用场景

| 方法/场景 | `back()`/`forward()`/`go(n)` | `pushState()` | `replaceState()` |
|---------------------|------------------------------------|------------------------------|--------------------------------|
| **作用** | 基于已有记录导航 | 新增历史记录(不导航) | 替换当前历史记录(不导航) |
| **是否刷新页面** | 是(跳转到历史记录对应的页面) | 否(仅修改 URL 和历史记录) | 否(仅修改 URL 和历史记录) |
| **历史记录数量** | 不变 | 增加 1 | 不变 |
| **典型应用** | 模拟浏览器前进/后退按钮 | 单页应用切换路由(如点击导航)| 替换当前路由(如登录后更新 URL)|

### 总结

- **传统方法**(`back()`/`forward()`/`go(n)`):用于控制页面在已有历史记录中导航,会触发页面刷新。
- **HTML5 方法**(`pushState()`/`replaceState()`):用于在不刷新页面的情况下添加/修改历史记录,是单页应用实现无刷新路由的核心,需配合 `popstate` 事件更新页面内容。

这些方法共同构成了浏览器历史记录的编程接口,使开发者能更灵活地控制页面导航和用户体验。

## 14.navigator 对象的作用是什么?如何通过 navigator 获取浏览器信息(如浏览器类型、版本)?

`navigator` 对象是 BOM(浏览器对象模型)的核心成员之一,其核心作用是**提供当前浏览器、运行环境(如操作系统)及设备的相关信息**,帮助开发者实现浏览器兼容性判断、设备适配、功能支持检测等需求。

### 一、navigator 对象的核心作用

`navigator` 本质是浏览器暴露给 JavaScript 的“信息接口”,主要用于:

1. **浏览器兼容性判断**:识别用户使用的浏览器类型(如 Chrome、Firefox、Safari)和版本,针对性处理不同浏览器的差异(如 CSS 前缀、API 支持)。
2. **设备与环境适配**:获取操作系统(如 Windows、macOS、iOS)、屏幕信息、默认语言等,适配不同设备的显示和交互逻辑。
3. **功能支持检测**:判断浏览器是否支持特定功能(如地理定位 `geolocation`、WebGL、Service Worker)。
4. **网络状态监测**:通过 `onLine` 属性判断浏览器是否联网,实现离线/在线状态下的功能切换。

### 二、通过 navigator 获取浏览器信息(核心属性与方法)

获取浏览器类型和版本的核心依赖 `navigator.userAgent` 属性(用户代理字符串),它包含了浏览器名称、版本、内核、操作系统等关键信息。其他辅助属性(如 `appName`、`appVersion`)因浏览器兼容性问题,准确性较低,通常作为补充。

#### 1. 核心属性:`navigator.userAgent`(用户代理字符串)

`userAgent` 是一个字符串,由浏览器厂商定义,格式通常为:
`浏览器标识/版本号 (操作系统信息) 内核信息 其他补充`

不同浏览器的 `userAgent` 示例(截至 2024 年主流浏览器):

- **Chrome(Windows 10)**:`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36`
- **Firefox(macOS)**:`Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0`
- **Safari(iOS)**:`Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Mobile/15E148 Safari/604.1`
- **Edge(Windows 11)**:`Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0`(Edge 基于 Chromium 内核,故包含 Chrome 标识)

#### 2. 从 `userAgent` 提取浏览器类型与版本(代码示例)

由于 `userAgent` 是字符串,需通过**正则表达式**匹配关键信息(如浏览器名称、版本号)。以下是通用实现,可识别主流浏览器(Chrome、Firefox、Safari、Edge、IE):

```javascript
// 1. 获取 userAgent 字符串
const userAgent = navigator.userAgent.toLowerCase(); // 转为小写,避免大小写问题

// 2. 定义浏览器检测函数
function getBrowserInfo() {
let browser = {
type: 'unknown', // 浏览器类型
version: 'unknown' // 浏览器版本
};

// 匹配 Chrome(含 Edge 基于 Chromium 的版本)
if (/chrome\/(\d+)/.test(userAgent)) {
// 区分 Edge 和 Chrome(Edge 含 "edg/" 标识)
if (/edg\/(\d+)/.test(userAgent)) {
browser.type = 'Edge';
browser.version = userAgent.match(/edg\/(\d+)/)[1];
} else {
browser.type = 'Chrome';
browser.version = userAgent.match(/chrome\/(\d+)/)[1];
}
}
// 匹配 Firefox
else if (/firefox\/(\d+)/.test(userAgent)) {
browser.type = 'Firefox';
browser.version = userAgent.match(/firefox\/(\d+)/)[1];
}
// 匹配 Safari(Safari 含 "version/" 标识)
else if (/safari\/(\d+)/.test(userAgent) && !/chrome/.test(userAgent)) {
browser.type = 'Safari';
browser.version = userAgent.match(/version\/(\d+)/)[1]; // Safari 版本在 "version/" 后
}
// 匹配 IE(仅支持 IE 11 及以下,IE 11 标识为 "trident/")
else if (/trident\/(\d+)/.test(userAgent)) {
browser.type = 'IE';
browser.version = '11'; // IE 11 统一标识为版本 11
}

return browser;
}

// 3. 调用函数获取浏览器信息
const browserInfo = getBrowserInfo();
console.log('浏览器类型:', browserInfo.type); // 如 "Chrome"
console.log('浏览器版本:', browserInfo.version); // 如 "124"
```

#### 3. 其他辅助属性(准确性较低,仅作补充)

- **`navigator.appName`**:理论上返回浏览器名称,但多数现代浏览器(Chrome、Firefox、Edge)均返回 `"Netscape"`(历史兼容原因),仅 IE 会返回 `"Microsoft Internet Explorer"`,实用性低。

```javascript
console.log(navigator.appName); // Chrome/Firefox 返回 "Netscape",IE 返回 "Microsoft Internet Explorer"
```

- **`navigator.appVersion`**:返回浏览器版本信息,但格式混乱(包含操作系统、内核等冗余信息),不如 `userAgent` 清晰,示例:

```javascript
console.log(navigator.appVersion);
// 输出:"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"
```

- **`navigator.vendor`**:返回浏览器厂商名称,如 Chrome 对应 `"Google Inc."`,Safari 对应 `"Apple Computer, Inc."`,可辅助判断浏览器类型:

```javascript
console.log(navigator.vendor); // Chrome 输出 "Google Inc.",Safari 输出 "Apple Computer, Inc."
```

### 三、注意事项

1. **`userAgent` 可被修改**:用户可通过浏览器插件(如 User-Agent Switcher)篡改 `userAgent`,导致检测结果不准确,因此**不能完全依赖 `userAgent` 进行核心功能判断**(如支付、权限控制)。
2. **浏览器更新导致标识变化**:浏览器版本更新可能调整 `userAgent` 格式(如 Edge 从 IE 内核切换为 Chromium 内核后,`userAgent` 新增 Chrome 标识),需定期维护正则表达式。
3. **优先使用功能检测而非浏览器检测**:对于 API 支持(如 `fetch`、`Promise`),建议直接检测功能是否存在,而非通过浏览器类型判断,示例:

```javascript
// 检测浏览器是否支持 fetch API(推荐)
if (window.fetch) {
console.log('支持 fetch API');
} else {
console.log('不支持 fetch API,需兼容');
}
```

### 总结

- `navigator` 的核心作用是提供浏览器、设备、环境信息,支撑兼容性适配和功能检测。
- 获取浏览器类型和版本的**核心方式是解析 `navigator.userAgent`**,需通过正则表达式提取关键信息;辅助属性(`appName`、`appVersion`)准确性低,仅作补充。
- 实际开发中,应优先通过“功能检测”判断 API 支持,而非单纯依赖浏览器类型,避免兼容性风险。

## 15.screen 对象的作用是什么?如何获取屏幕的尺寸信息(如 screen.width、screen.height)?

`screen` 对象是 BOM(浏览器对象模型)的核心成员之一,其核心作用是**提供用户设备屏幕的硬件信息**,包括屏幕尺寸、可用显示区域、颜色深度等。这些信息主要用于**页面适配不同设备屏幕**(如桌面端、移动端、平板),帮助开发者根据屏幕特性优化布局(如响应式设计、动态调整元素大小)。

### 一、screen 对象的核心作用

`screen` 对象聚焦于设备的物理屏幕属性,不依赖于浏览器窗口状态(如窗口大小、是否最大化),主要应用场景包括:

1. 判断设备类型(如通过屏幕宽度区分手机、平板、桌面设备);
2. 适配不同屏幕尺寸的布局(如移动端显示单列,桌面端显示多列);
3. 计算元素的最佳显示大小(如根据屏幕高度设置页面最大高度);
4. 获取屏幕颜色深度,优化图像渲染(如确保图片颜色与屏幕支持的深度匹配)。

### 二、获取屏幕尺寸信息(核心属性)

`screen` 对象提供了多个属性用于获取屏幕尺寸,其中最常用的是与“整体尺寸”和“可用尺寸”相关的属性:

#### 1. 屏幕整体尺寸(包含系统任务栏等区域)

- **`screen.width`**:返回屏幕的**整体宽度**(单位:像素,包含系统任务栏、菜单栏等不可用于网页显示的区域)。
- **`screen.height`**:返回屏幕的**整体高度**(单位:像素,同上)。

#### 2. 屏幕可用尺寸(排除系统任务栏等区域)

- **`screen.availWidth`**:返回屏幕的**可用宽度**(单位:像素,即整体宽度减去系统任务栏、侧边栏等不可用区域的宽度,代表网页可使用的最大水平空间)。
- **`screen.availHeight`**:返回屏幕的**可用高度**(单位:像素,即整体高度减去系统任务栏等区域的高度,代表网页可使用的最大垂直空间)。

#### 代码示例:获取屏幕尺寸信息

```javascript
// 1. 获取屏幕整体尺寸(包含系统任务栏等)
console.log('屏幕整体宽度:', screen.width); // 如 1920 像素(全高清屏幕)
console.log('屏幕整体高度:', screen.height); // 如 1080 像素

// 2. 获取屏幕可用尺寸(排除系统任务栏等,更实用)
console.log('屏幕可用宽度:', screen.availWidth); // 如 1920 像素(任务栏在底部时,宽度不受影响)
console.log('屏幕可用高度:', screen.availHeight); // 如 1040 像素(1080 - 任务栏高度 40)

// 3. 其他辅助属性
console.log('屏幕颜色深度:', screen.colorDepth); // 如 24(表示支持 2^24 种颜色,即真彩色)
console.log('屏幕方向类型:', screen.orientation.type); // 如 "landscape-primary"(横屏)或 "portrait-primary"(竖屏)
```

### 三、注意事项

1. **与浏览器窗口尺寸的区别**:
- `screen.width`/`screen.height` 是**设备屏幕的物理尺寸**(固定值,除非屏幕分辨率改变);
- `window.innerWidth`/`window.innerHeight` 是**浏览器可视区域的尺寸**(随窗口大小变化,包含滚动条)。
例如:在 1920×1080 的屏幕上,浏览器窗口最大化时 `window.innerWidth` 可能接近 `screen.availWidth`,但窗口缩小时会更小。

2. **设备适配的最佳实践**:
实际开发中,更推荐使用 `window.innerWidth` 结合 CSS 媒体查询(`@media`)适配浏览器窗口大小(而非固定屏幕尺寸),因为用户可能不会全屏显示浏览器。但 `screen` 属性可作为辅助判断(如区分手机屏幕和桌面屏幕的大致范围)。

3. **兼容性**:
所有主流浏览器(Chrome、Firefox、Safari、Edge)均支持 `screen` 对象的核心属性(`width`、`height`、`availWidth`、`availHeight`),兼容性良好。

### 总结

- `screen` 对象的核心作用是提供设备屏幕的硬件尺寸信息,辅助页面适配不同设备。
- 获取屏幕尺寸的核心属性是:
- `screen.width`/`screen.height`:整体尺寸(包含系统任务栏);
- `screen.availWidth`/`screen.availHeight`:可用尺寸(排除系统任务栏,更实用)。

这些属性是响应式设计和设备适配的基础工具,帮助开发者构建在不同屏幕上都能良好显示的页面。

## 16.简述浏览器的前进和后退功能的实现原理(基于 history 栈)

浏览器的前进、后退功能,本质是基于 **“历史记录栈”(History Stack)** 这一核心数据结构实现的——浏览器会维护一个有序的“历史记录列表”,并通过一个“当前指针”标记用户当前所在的记录位置,前进/后退本质就是移动该指针,并加载指针指向的历史记录对应的页面资源。

### 一、核心模型:历史记录栈(History Stack)与当前指针

浏览器为每个标签页(或窗口)单独维护一套 **历史记录系统**,核心由两部分组成:

1. **历史记录列表**:一个有序的“链表结构”(非严格LIFO栈,支持双向遍历),每个条目称为“历史记录项(History Entry)”,包含以下关键信息:
- 页面的 **URL**(跳转目标地址);
- 页面的 **状态数据(state)**(HTML5新增,由`pushState/replaceState`设置,用于单页应用无刷新导航);
- 页面的 **标题(title)**;
- 页面的 **滚动位置、表单状态** 等(用于恢复页面上下文)。

2. **当前指针(Current Pointer)**:一个指向“当前历史记录项”的标记,初始指向列表的第一个记录(用户打开的第一个页面)。

### 二、前进/后退的具体实现原理

以用户的浏览行为为例,逐步拆解前进/后退的过程:

#### 1. 正常浏览:添加历史记录,移动指针

假设用户的浏览路径是:`页面A → 页面B → 页面C`,对应的历史记录列表和指针变化如下:

- 打开`页面A`:历史记录列表初始化为 `[A]`,当前指针指向 `A`(指针位置:0)。
- 从`A`跳转到`B`(如点击链接、`location.href`跳转):浏览器在列表末尾 **新增一条B的记录**,列表变为 `[A, B]`,指针移动到 `B`(指针位置:1)。
- 从`B`跳转到`C`:同理,列表新增`C`的记录,变为 `[A, B, C]`,指针移动到 `C`(指针位置:2)。

此时历史记录列表的状态:
`[A(位置0), B(位置1), C(位置2)]` → 指针指向 **C(位置2)**。

#### 2. 后退操作(点击“后退”按钮 / 调用`history.back()`)

当用户点击浏览器“后退”按钮(或调用`history.back()`)时:

1. **移动当前指针**:指针从当前位置(如`C`的位置2)向左移动1位,指向 `B`(位置1)。
2. **加载目标记录**:浏览器读取指针指向的`B`记录中的URL,加载对应的页面资源——优先从 **浏览器缓存** 读取(如页面HTML、CSS、JS已缓存,则直接复用,避免重新请求服务器);若缓存失效,则重新向服务器发起请求。
3. **恢复页面上下文**:加载完成后,浏览器恢复`B`页面的滚动位置、表单输入状态等(确保用户看到的是之前离开时的页面状态)。

此时指针位置变为 **B(位置1)**,用户看到的页面切换为`B`。

#### 3. 前进操作(点击“前进”按钮 / 调用`history.forward()`)

若用户在`B`页面点击“前进”按钮(或调用`history.forward()`):

1. **移动当前指针**:指针从`B`的位置1向右移动1位,指向 `C`(位置2)。
2. **加载目标记录**:同理,读取`C`记录的URL,优先从缓存加载页面资源,恢复`C`页面的上下文。

此时指针回到 **C(位置2)**,用户看到的页面切换回`C`。

#### 4. 跨步导航(调用`history.go(n)`)

若用户调用`history.go(-2)`(后退2步),则指针从`C`(位置2)直接向左移动2位,指向 `A`(位置0),并加载`A`的页面;若调用`history.go(1)`,则等价于`forward()`,指针向右移动1位。

### 三、HTML5对历史记录栈的扩展(不影响前进/后退核心逻辑)

HTML5新增的`pushState()`和`replaceState()`方法,本质是“修改历史记录列表”,但不改变前进/后退的核心原理:

- `pushState()`:在历史记录列表 **末尾新增一条记录**,并将指针移动到该新记录(不刷新页面,仅修改URL和记录),例如单页应用从`/home`切换到`/about`,会新增`/about`的记录,后退时能回到`/home`。
- `replaceState()`:用新记录 **替换当前指针指向的记录**(不新增记录,指针位置不变),例如登录后将`/login`记录替换为`/dashboard`,避免后退时回到登录页。

这两个方法仅修改“历史记录列表的内容”,前进/后退时依然是移动指针、加载对应记录——核心逻辑不变,只是扩展了“无刷新修改历史记录”的能力。

### 四、关键总结

浏览器前进/后退的核心原理可概括为3句话:

1. 浏览器为每个标签页维护 **“历史记录列表”+“当前指针”**;
2. 前进/后退本质是 **移动当前指针**(前进向右移,后退向左移);
3. 指针移动后,浏览器 **加载指针指向的历史记录对应的页面资源**(优先缓存,恢复上下文)。

这一模型确保了用户能“无缝回溯浏览路径”,同时支持编程式控制(通过`history`对象方法),是浏览器交互体验的基础。

## 17.如何实现页面的刷新?location.reload () 的参数有什么作用?

在 JavaScript 中,实现页面刷新的核心方法是使用 `location.reload()`,此外还有一些间接方式(如修改 `location` 属性或使用 `history` 对象)。其中 `location.reload()` 是最直接且常用的方式,其参数用于控制刷新时是否忽略缓存。

### 一、实现页面刷新的常用方式

#### 1. `location.reload()`:直接刷新当前页面

这是最常用的刷新方法,语法为:

```javascript
location.reload(); // 普通刷新(默认使用缓存)
```

#### 2. 其他间接刷新方式

- **通过 `location.href` 重新加载当前 URL**:

```javascript
location.href = location.href; // 等价于普通刷新,会添加新的历史记录
```

- **通过 `history.go(0)` 刷新**:

```javascript
history.go(0); // 效果与 location.reload() 类似,利用历史记录刷新当前页
```

### 二、`location.reload(forceGet)` 的参数作用

`location.reload()` 可以接受一个可选的布尔值参数 `forceGet`,用于控制刷新时是否**强制从服务器重新加载资源**(忽略浏览器缓存):

- **参数为 `false` 或不传递参数(默认)**:
浏览器会**优先使用缓存中的资源**(如 HTML、CSS、JS、图片等)进行刷新。如果资源未过期,直接从缓存读取,减少网络请求,刷新速度更快。

```javascript
location.reload(); // 等价于 location.reload(false)
```

- **参数为 `true`**:
浏览器会**强制忽略缓存**,向服务器重新请求所有资源(包括 HTML、CSS、JS 等),确保获取最新内容,但刷新速度较慢(依赖网络请求)。

```javascript
location.reload(true); // 强制从服务器刷新
```

### 三、注意事项

1. **缓存机制的影响**:
- 默认刷新(`forceGet=false`)可能不会更新服务器已修改的资源(如果资源缓存未过期),适合对实时性要求不高的场景。
- 强制刷新(`forceGet=true`)会重新请求所有资源,适合需要获取最新数据的场景(如后台数据更新后)。

2. **浏览器兼容性**:
所有现代浏览器(Chrome、Firefox、Edge、Safari)均支持 `location.reload()`,但部分浏览器(如 Chrome 66+)对 `forceGet` 参数的支持有所调整——即使传递 `true`,也可能不会完全忽略缓存(受服务器响应头 `Cache-Control` 影响)。

3. **用户体验**:
刷新会导致页面重新加载,当前页面的脚本执行会中断,未保存的用户输入(如表单内容)会丢失,需谨慎使用(必要时可先提示用户保存)。

### 总结

- 页面刷新的核心方法是 `location.reload()`,其他方式(`location.href`、`history.go(0)`)本质上也是触发类似的重新加载逻辑。
- `location.reload(forceGet)` 的参数 `forceGet` 控制缓存策略:`false`(默认)使用缓存加速刷新,`true` 强制从服务器获取最新资源。

根据场景选择合适的刷新方式,平衡刷新速度与内容实时性。

## 18.什么是同源策略?它对 DOM 操作有什么限制(如 iframe 跨域访问 DOM)?

### 一、什么是同源策略?

同源策略(Same-Origin Policy)是浏览器的核心安全机制,由Netscape在1995年提出,目的是**防止不同源的网页之间进行未经授权的资源访问或数据窃取**,保护用户信息安全。

“同源”指的是两个页面的 **协议(Protocol)、域名(Domain)、端口(Port)三者完全相同**。只要其中一项不同,就视为“不同源”。

**示例**:
以 `http://www.example.com:8080/page.html` 为基准,判断以下URL是否同源:

- `http://www.example.com:8080/other.html` → 同源(协议、域名、端口均相同);
- `https://www.example.com:8080/page.html` → 不同源(协议不同:http vs https);
- `http://blog.example.com:8080/page.html` → 不同源(域名不同:www.example.com vs blog.example.com);
- `http://www.example.com:80/page.html` → 不同源(端口不同:8080 vs 80)。

### 二、同源策略对 DOM 操作的限制(以 iframe 为例)

同源策略对DOM操作的核心限制是:**不同源的页面之间,不允许直接访问或操作彼此的DOM**。这一限制在包含`iframe`的页面中尤为明显——父页面与`iframe`页面若不同源,则无法互相访问对方的DOM元素、文档内容或JavaScript对象。

#### 具体限制表现

假设页面A(`http://a.com`)中嵌入了一个`iframe`,其 src 指向页面B(`http://b.com`,与A不同源):

1. **父页面(A)无法访问 iframe 页面(B)的 DOM**
若A尝试通过`iframe.contentDocument`或`iframe.contentWindow.document`获取B的DOM,浏览器会抛出**跨域访问错误**:

```html
<!-- 页面A:http://a.com -->
<iframe id="myIframe" src="http://b.com"></iframe>
<script>
const iframe = document.getElementById('myIframe');
// 尝试访问不同源的iframe DOM → 报错
console.log(iframe.contentDocument); // 抛出错误:Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
</script>
```

2. **iframe 页面(B)无法访问父页面(A)的 DOM**
同理,B若尝试通过`parent.document`或`top.document`访问A的DOM,也会被浏览器阻止:

```html
<!-- 页面B:http://b.com -->
<script>
// 尝试访问不同源的父页面DOM → 报错
console.log(parent.document); // 抛出错误:Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
</script>
```

3. **同源页面无限制**
若A和B同源(如A为`http://a.com`,B为`http://a.com/iframe.html`),则可以自由访问彼此的DOM:

```html
<!-- 页面A:http://a.com -->
<iframe id="myIframe" src="http://a.com/iframe.html"></iframe>
<script>
const iframe = document.getElementById('myIframe');
// 同源,可正常访问iframe的DOM
iframe.onload = () => {
console.log(iframe.contentDocument.body); // 输出iframe的body元素
};
</script>
```

### 三、为什么限制跨域 DOM 访问?

核心目的是**防止恶意网站窃取敏感信息**。例如:

- 假设用户同时打开了银行页面(`https://bank.com`)和一个恶意页面(`https://evil.com`)。若没有同源限制,恶意页面可以通过`iframe`嵌入银行页面,并读取用户输入的账号密码(通过访问银行页面的DOM),导致信息泄露。
- 限制跨域DOM访问后,恶意页面无法获取其他源页面的DOM内容,从根本上阻断了这类攻击。

### 四、跨域场景下的替代方案(非DOM直接访问)

虽然跨域无法直接操作DOM,但可以通过**安全的跨域通信机制**交换数据,最常用的是 `window.postMessage()`:

1. **父页面向 iframe 发送消息**:

```javascript
// 页面A(http://a.com)
const iframe = document.getElementById('myIframe');
// 向iframe发送消息(参数:消息内容,目标源)
iframe.contentWindow.postMessage('hello from A', 'http://b.com');
```

2. **iframe 接收并回复消息**:

```javascript
// 页面B(http://b.com)
window.addEventListener('message', (event) => {
// 验证消息来源(防止恶意消息)
if (event.origin === 'http://a.com') {
console.log('收到A的消息:', event.data); // 输出 "hello from A"
// 回复消息
event.source.postMessage('hello from B', event.origin);
}
});
```

`postMessage()` 允许不同源页面通过“消息传递”间接通信,但仍无法直接操作对方DOM,既保证了安全性,又满足了跨域交互需求。

### 总结

- **同源策略**:要求协议、域名、端口完全相同才视为同源,是浏览器的核心安全机制。
- **对DOM操作的限制**:不同源页面(如父页面与跨域iframe)无法直接访问或操作彼此的DOM,防止敏感信息泄露。
- **跨域通信**:无法直接操作DOM,但可通过 `postMessage()` 安全交换数据。

这一机制平衡了安全性与功能性,是现代Web安全的基础。

## 19.如何检测浏览器的类型和版本?有哪些潜在问题?

### 一、什么是同源策略?

同源策略(Same-Origin Policy)是浏览器的核心安全机制,由Netscape在1995年提出,目的是**防止不同源的网页之间进行未经授权的资源访问或数据窃取**,保护用户信息安全。

“同源”指的是两个页面的 **协议(Protocol)、域名(Domain)、端口(Port)三者完全相同**。只要其中一项不同,就视为“不同源”。

**示例**:
以 `http://www.example.com:8080/page.html` 为基准,判断以下URL是否同源:

- `http://www.example.com:8080/other.html` → 同源(协议、域名、端口均相同);
- `https://www.example.com:8080/page.html` → 不同源(协议不同:http vs https);
- `http://blog.example.com:8080/page.html` → 不同源(域名不同:www.example.com vs blog.example.com);
- `http://www.example.com:80/page.html` → 不同源(端口不同:8080 vs 80)。

### 二、同源策略对 DOM 操作的限制(以 iframe 为例)

同源策略对DOM操作的核心限制是:**不同源的页面之间,不允许直接访问或操作彼此的DOM**。这一限制在包含`iframe`的页面中尤为明显——父页面与`iframe`页面若不同源,则无法互相访问对方的DOM元素、文档内容或JavaScript对象。

#### 具体限制表现

假设页面A(`http://a.com`)中嵌入了一个`iframe`,其 src 指向页面B(`http://b.com`,与A不同源):

1. **父页面(A)无法访问 iframe 页面(B)的 DOM**
若A尝试通过`iframe.contentDocument`或`iframe.contentWindow.document`获取B的DOM,浏览器会抛出**跨域访问错误**:

```html
<!-- 页面A:http://a.com -->
<iframe id="myIframe" src="http://b.com"></iframe>
<script>
const iframe = document.getElementById('myIframe');
// 尝试访问不同源的iframe DOM → 报错
console.log(iframe.contentDocument); // 抛出错误:Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
</script>
```

2. **iframe 页面(B)无法访问父页面(A)的 DOM**
同理,B若尝试通过`parent.document`或`top.document`访问A的DOM,也会被浏览器阻止:

```html
<!-- 页面B:http://b.com -->
<script>
// 尝试访问不同源的父页面DOM → 报错
console.log(parent.document); // 抛出错误:Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
</script>
```

3. **同源页面无限制**
若A和B同源(如A为`http://a.com`,B为`http://a.com/iframe.html`),则可以自由访问彼此的DOM:

```html
<!-- 页面A:http://a.com -->
<iframe id="myIframe" src="http://a.com/iframe.html"></iframe>
<script>
const iframe = document.getElementById('myIframe');
// 同源,可正常访问iframe的DOM
iframe.onload = () => {
console.log(iframe.contentDocument.body); // 输出iframe的body元素
};
</script>
```

### 三、为什么限制跨域 DOM 访问?

核心目的是**防止恶意网站窃取敏感信息**。例如:

- 假设用户同时打开了银行页面(`https://bank.com`)和一个恶意页面(`https://evil.com`)。若没有同源限制,恶意页面可以通过`iframe`嵌入银行页面,并读取用户输入的账号密码(通过访问银行页面的DOM),导致信息泄露。
- 限制跨域DOM访问后,恶意页面无法获取其他源页面的DOM内容,从根本上阻断了这类攻击。

### 四、跨域场景下的替代方案(非DOM直接访问)

虽然跨域无法直接操作DOM,但可以通过**安全的跨域通信机制**交换数据,最常用的是 `window.postMessage()`:

1. **父页面向 iframe 发送消息**:

```javascript
// 页面A(http://a.com)
const iframe = document.getElementById('myIframe');
// 向iframe发送消息(参数:消息内容,目标源)
iframe.contentWindow.postMessage('hello from A', 'http://b.com');
```

2. **iframe 接收并回复消息**:

```javascript
// 页面B(http://b.com)
window.addEventListener('message', (event) => {
// 验证消息来源(防止恶意消息)
if (event.origin === 'http://a.com') {
console.log('收到A的消息:', event.data); // 输出 "hello from A"
// 回复消息
event.source.postMessage('hello from B', event.origin);
}
});
```

`postMessage()` 允许不同源页面通过“消息传递”间接通信,但仍无法直接操作对方DOM,既保证了安全性,又满足了跨域交互需求。

### 总结

- **同源策略**:要求协议、域名、端口完全相同才视为同源,是浏览器的核心安全机制。
- **对DOM操作的限制**:不同源页面(如父页面与跨域iframe)无法直接访问或操作彼此的DOM,防止敏感信息泄露。
- **跨域通信**:无法直接操作DOM,但可通过 `postMessage()` 安全交换数据。

这一机制平衡了安全性与功能性,是现代Web安全的基础。

## 20.简述 DOMContentLoaded 和 load 事件的区别,它们分别在什么时机触发?

`DOMContentLoaded` 和 `load` 是浏览器在页面加载过程中触发的两个核心事件,二者的核心区别在于**触发时机(依赖的资源加载状态)不同**,直接影响代码执行的时机和场景。

### 一、触发时机与核心逻辑

#### 1. `DOMContentLoaded` 事件

- **触发时机**:当浏览器**完全解析完 HTML 并构建出 DOM 树后**,立即触发。
此时不依赖:外部资源(如图片、视频、字体、非同步加载的 CSS/JS)是否加载完成;
但会阻塞于:**同步加载的 JS 执行**(因为 JS 可能操作 DOM,浏览器需先执行完同步 JS,再完成 DOM 构建)和 **CSSOM 构建**(JS 会等待 CSSOM 完成后再执行,间接影响 DOM 构建进度)。

- **通俗理解**:“页面结构(DOM)准备好了,可以开始操作 DOM 了”,但页面中的图片、大文件可能还在加载(页面可能显示不全,但交互逻辑可初始化)。

#### 2. `load` 事件

- **触发时机**:当浏览器**加载完页面的所有资源后**(包括:DOM 树、CSS 样式表、所有图片/视频/字体、同步/异步 JS 等),才触发。
此时页面的所有内容已完全渲染,资源加载状态为“全部完成”。

- **通俗理解**:“页面的所有东西(结构、样式、图片、脚本)都准备好了,页面完全显示且可正常交互”。

### 二、核心区别对比

| 维度 | `DOMContentLoaded` | `load` |
|---------------------|---------------------------------------------|---------------------------------------------|
| **触发依赖** | 仅需 DOM 树构建完成,不依赖外部资源 | 需 DOM 树 + 所有外部资源(图片、CSS、JS 等)加载完成 |
| **触发时机** | 更早(页面加载早期) | 更晚(页面加载末期) |
| **执行场景** | 适合初始化 DOM 操作(如绑定事件、渲染列表) | 适合依赖外部资源的操作(如获取图片尺寸、初始化地图) |
| **阻塞因素** | 被同步 JS、CSSOM 构建阻塞 | 被所有资源加载阻塞(包括慢加载的图片/字体) |

### 三、示例:直观感受触发顺序

假设页面结构如下(包含同步 JS、外部 CSS 和图片):

```html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css"> <!-- 外部 CSS -->
<script>
// 同步 JS:会阻塞 DOM 构建,影响 DOMContentLoaded 触发
console.log("同步 JS 执行");
</script>
</head>
<body>
<img src="large-image.jpg" alt="大图片"> <!-- 外部图片 -->

<script>
// 监听 DOMContentLoaded
document.addEventListener('DOMContentLoaded', () => {
console.log("DOMContentLoaded 触发:DOM 已就绪,图片可能还在加载");
});

// 监听 load
window.addEventListener('load', () => {
console.log("load 触发:所有资源(DOM+图片+CSS)已加载完成");
});
</script>
</body>
</html>

执行顺序

  1. 浏览器解析 HTML,遇到同步 JS 先执行 → 输出“同步 JS 执行”;
  2. 继续解析 HTML 并构建 DOM 树,同时加载 CSS 并构建 CSSOM;
  3. DOM 树和 CSSOM 完成后 → 触发 DOMContentLoaded → 输出“DOMContentLoaded 触发…”(此时图片可能还在加载中);
  4. 图片加载完成后 → 触发 load → 输出“load 触发…”。

总结

  • **DOMContentLoaded**:早触发,DOM 就绪即可,适合优先初始化 DOM 相关逻辑(如交互事件绑定),提升页面响应速度;
  • **load**:晚触发,所有资源就绪,适合依赖外部资源的操作(如获取图片实际尺寸),避免因资源未加载导致的错误。