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); box.innerHTML = '<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>'; </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); </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); box.outerHTML = '<p>替换整个元素</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.classList.remove("active"); box.classList.add("new-class"); console.log(box.className); </script> ```
##### (7)`id`
- **作用**:获取或设置元素的 `id` 属性(唯一标识,常用于选择元素)。
```html <div id="old-id"></div> <script> const div = document.getElementById("old-id"); div.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); box.style.fontSize = "20px"; </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
const newDiv = document.createElement('div');
newDiv.id = 'new-div'; newDiv.className = 'box'; newDiv.setAttribute('data-id', '123');
newDiv.textContent = '这是新创建的 div';
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.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');
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> const item2 = document.getElementById('item2'); const parent = item2.parentNode; const deletedItem = parent.removeChild(item2); console.log(deletedItem); </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>
```
### 二、替换 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> const newP = document.createElement('p'); newP.textContent = '新段落'; newP.id = 'new-p'; const container = document.getElementById('container'); const oldP = document.getElementById('old-p'); const replacedElement = container.replaceChild(newP, oldP); console.log(replacedElement); </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); console.log(e.currentTarget); console.log(this === e.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> const items = document.querySelectorAll('#list li'); items.forEach(item => { item.addEventListener('click', function() { console.log('点击了:', this.textContent); }); });
const newLi = document.createElement('li'); newLi.textContent = '项目4'; document.getElementById('list').appendChild(newLi); </script> ```
##### 2. 事件委托方式(委托给父元素)
```html <ul id="list"> <li>项目1</li> <li>项目2</li> <li>项目3</li> </ul>
<script> const list = document.getElementById('list'); list.addEventListener('click', function(e) { if (e.target.tagName === 'LI') { console.log('点击了:', e.target.textContent); } });
const newLi = document.createElement('li'); newLi.textContent = '项目4'; list.appendChild(newLi); </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) { const targetLi = e.target.closest('li'); if (targetLi) { 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 <iframe id="myIframe" src="http://b.com"></iframe> <script> const iframe = document.getElementById('myIframe'); console.log(iframe.contentDocument); </script> ```
2. **iframe 页面(B)无法访问父页面(A)的 DOM** 同理,B若尝试通过`parent.document`或`top.document`访问A的DOM,也会被浏览器阻止:
```html <script> console.log(parent.document); </script> ```
3. **同源页面无限制** 若A和B同源(如A为`http://a.com`,B为`http://a.com/iframe.html`),则可以自由访问彼此的DOM:
```html <iframe id="myIframe" src="http://a.com/iframe.html"></iframe> <script> const iframe = document.getElementById('myIframe'); iframe.onload = () => { console.log(iframe.contentDocument.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 <iframe id="myIframe" src="http://b.com"></iframe> <script> const iframe = document.getElementById('myIframe'); console.log(iframe.contentDocument); </script> ```
2. **iframe 页面(B)无法访问父页面(A)的 DOM** 同理,B若尝试通过`parent.document`或`top.document`访问A的DOM,也会被浏览器阻止:
```html <script> console.log(parent.document); </script> ```
3. **同源页面无限制** 若A和B同源(如A为`http://a.com`,B为`http://a.com/iframe.html`),则可以自由访问彼此的DOM:
```html <iframe id="myIframe" src="http://a.com/iframe.html"></iframe> <script> const iframe = document.getElementById('myIframe'); iframe.onload = () => { console.log(iframe.contentDocument.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"> <script> console.log("同步 JS 执行"); </script> </head> <body> <img src="large-image.jpg" alt="大图片">
<script> document.addEventListener('DOMContentLoaded', () => { console.log("DOMContentLoaded 触发:DOM 已就绪,图片可能还在加载"); });
window.addEventListener('load', () => { console.log("load 触发:所有资源(DOM+图片+CSS)已加载完成"); }); </script> </body> </html>
|