index.html 143 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="utf-8">
  5. <meta name="viewport" content="width=device-width,initial-scale=1">
  6. <title>Module 的加载实现 | 彪哥博客</title>
  7. <meta name="generator" content="VuePress 1.9.5">
  8. <link rel="icon" href="/blog/img/favicon.ico">
  9. <meta name="description" content="web前端技术博客,专注web前端学习与总结。JavaScript,js,ES6,TypeScript,vue,React,python,css3,html5,Node,git,github等技术文章。">
  10. <meta name="keywords" content="前端博客,个人技术博客,前端,前端开发,前端框架,web前端,前端面试题,技术文档,学习,面试,JavaScript,js,ES6,TypeScript,vue,python,css3,html5,Node,git,github,markdown">
  11. <meta name="theme-color" content="#11a8cd">
  12. <link rel="preload" href="/blog/assets/css/0.styles.dc03b589.css" as="style"><link rel="preload" href="/blog/assets/js/app.90754bd5.js" as="script"><link rel="preload" href="/blog/assets/js/2.106f41fb.js" as="script"><link rel="preload" href="/blog/assets/js/3.6748bd5c.js" as="script"><link rel="preload" href="/blog/assets/js/111.da99a105.js" as="script"><link rel="prefetch" href="/blog/assets/js/10.cad3aa70.js"><link rel="prefetch" href="/blog/assets/js/100.08a8b2d8.js"><link rel="prefetch" href="/blog/assets/js/101.2aabb12c.js"><link rel="prefetch" href="/blog/assets/js/102.13f3cc4d.js"><link rel="prefetch" href="/blog/assets/js/103.c19aee03.js"><link rel="prefetch" href="/blog/assets/js/104.91a6aec1.js"><link rel="prefetch" href="/blog/assets/js/105.71de1aa4.js"><link rel="prefetch" href="/blog/assets/js/106.207422de.js"><link rel="prefetch" href="/blog/assets/js/107.bf754f60.js"><link rel="prefetch" href="/blog/assets/js/108.87ddbf21.js"><link rel="prefetch" href="/blog/assets/js/109.de6075c6.js"><link rel="prefetch" href="/blog/assets/js/11.f2e9eca8.js"><link rel="prefetch" href="/blog/assets/js/110.f19c57ae.js"><link rel="prefetch" href="/blog/assets/js/112.9dd75c6f.js"><link rel="prefetch" href="/blog/assets/js/113.9322f157.js"><link rel="prefetch" href="/blog/assets/js/114.c5150cc6.js"><link rel="prefetch" href="/blog/assets/js/115.4fb547bf.js"><link rel="prefetch" href="/blog/assets/js/116.2ddf1aa8.js"><link rel="prefetch" href="/blog/assets/js/117.3c970f48.js"><link rel="prefetch" href="/blog/assets/js/118.921e1d54.js"><link rel="prefetch" href="/blog/assets/js/119.0141defb.js"><link rel="prefetch" href="/blog/assets/js/12.98512c60.js"><link rel="prefetch" href="/blog/assets/js/120.de47a761.js"><link rel="prefetch" href="/blog/assets/js/121.a0b3693a.js"><link rel="prefetch" href="/blog/assets/js/122.6c7dd225.js"><link rel="prefetch" href="/blog/assets/js/123.dbff103c.js"><link rel="prefetch" href="/blog/assets/js/124.493776ef.js"><link rel="prefetch" href="/blog/assets/js/125.554c9fbf.js"><link rel="prefetch" href="/blog/assets/js/126.2421fe84.js"><link rel="prefetch" href="/blog/assets/js/127.805f10a4.js"><link rel="prefetch" href="/blog/assets/js/128.6543adba.js"><link rel="prefetch" href="/blog/assets/js/129.d7c56b92.js"><link rel="prefetch" href="/blog/assets/js/13.a79fa0c7.js"><link rel="prefetch" href="/blog/assets/js/130.593d21f0.js"><link rel="prefetch" href="/blog/assets/js/131.4c90d8b8.js"><link rel="prefetch" href="/blog/assets/js/132.4ad12bdc.js"><link rel="prefetch" href="/blog/assets/js/133.485de1b9.js"><link rel="prefetch" href="/blog/assets/js/134.78bc8f57.js"><link rel="prefetch" href="/blog/assets/js/135.47498729.js"><link rel="prefetch" href="/blog/assets/js/136.d99350df.js"><link rel="prefetch" href="/blog/assets/js/137.57ba6d3f.js"><link rel="prefetch" href="/blog/assets/js/138.d976c801.js"><link rel="prefetch" href="/blog/assets/js/139.766f20b7.js"><link rel="prefetch" href="/blog/assets/js/14.0fdf0c78.js"><link rel="prefetch" href="/blog/assets/js/140.0d9b8fbc.js"><link rel="prefetch" href="/blog/assets/js/141.128b6e26.js"><link rel="prefetch" href="/blog/assets/js/142.e004a584.js"><link rel="prefetch" href="/blog/assets/js/143.5a19a0b2.js"><link rel="prefetch" href="/blog/assets/js/144.5f397211.js"><link rel="prefetch" href="/blog/assets/js/145.cde5f3a0.js"><link rel="prefetch" href="/blog/assets/js/146.4185092c.js"><link rel="prefetch" href="/blog/assets/js/147.ac0e55d3.js"><link rel="prefetch" href="/blog/assets/js/148.30f02604.js"><link rel="prefetch" href="/blog/assets/js/149.760a79ed.js"><link rel="prefetch" href="/blog/assets/js/15.fce722b2.js"><link rel="prefetch" href="/blog/assets/js/150.3a1675b7.js"><link rel="prefetch" href="/blog/assets/js/151.0d73a46e.js"><link rel="prefetch" href="/blog/assets/js/152.c9c054d4.js"><link rel="prefetch" href="/blog/assets/js/153.b007c8a2.js"><link rel="prefetch" href="/blog/assets/js/154.a3acc6bf.js"><link rel="prefetch" href="/blog/assets/js/155.b5c0abcd.js"><link rel="prefetch" href="/blog/assets/js/156.2ba750fc.js"><link rel="prefetch" href="/blog/assets/js/157.fe92af0c.js"><link rel="prefetch" href="/blog/assets/js/158.e16fda40.js"><link rel="prefetch" href="/blog/assets/js/159.7fc7fd44.js"><link rel="prefetch" href="/blog/assets/js/16.871928af.js"><link rel="prefetch" href="/blog/assets/js/160.b581a0f4.js"><link rel="prefetch" href="/blog/assets/js/161.97acae68.js"><link rel="prefetch" href="/blog/assets/js/162.cec1b9a3.js"><link rel="prefetch" href="/blog/assets/js/163.a00f98f7.js"><link rel="prefetch" href="/blog/assets/js/164.cbf4cf52.js"><link rel="prefetch" href="/blog/assets/js/165.bc7a523a.js"><link rel="prefetch" href="/blog/assets/js/166.ca626fb4.js"><link rel="prefetch" href="/blog/assets/js/167.0ca68106.js"><link rel="prefetch" href="/blog/assets/js/168.2e605db8.js"><link rel="prefetch" href="/blog/assets/js/169.401b96d4.js"><link rel="prefetch" href="/blog/assets/js/17.2399cb2b.js"><link rel="prefetch" href="/blog/assets/js/170.7c8b0366.js"><link rel="prefetch" href="/blog/assets/js/171.c3155533.js"><link rel="prefetch" href="/blog/assets/js/172.b659d767.js"><link rel="prefetch" href="/blog/assets/js/173.62c681db.js"><link rel="prefetch" href="/blog/assets/js/174.5c66f092.js"><link rel="prefetch" href="/blog/assets/js/175.d41dd28b.js"><link rel="prefetch" href="/blog/assets/js/176.e60d7f0a.js"><link rel="prefetch" href="/blog/assets/js/177.10de95b1.js"><link rel="prefetch" href="/blog/assets/js/178.f301674d.js"><link rel="prefetch" href="/blog/assets/js/179.77bb52e9.js"><link rel="prefetch" href="/blog/assets/js/18.c338fe95.js"><link rel="prefetch" href="/blog/assets/js/180.d2a4e612.js"><link rel="prefetch" href="/blog/assets/js/181.a53e32e0.js"><link rel="prefetch" href="/blog/assets/js/182.38687994.js"><link rel="prefetch" href="/blog/assets/js/183.544fef00.js"><link rel="prefetch" href="/blog/assets/js/184.711e54a6.js"><link rel="prefetch" href="/blog/assets/js/185.20075148.js"><link rel="prefetch" href="/blog/assets/js/186.08c67f20.js"><link rel="prefetch" href="/blog/assets/js/187.7ca2d0c6.js"><link rel="prefetch" href="/blog/assets/js/188.cd167879.js"><link rel="prefetch" href="/blog/assets/js/189.e8e2eb21.js"><link rel="prefetch" href="/blog/assets/js/19.6b963460.js"><link rel="prefetch" href="/blog/assets/js/190.8b557318.js"><link rel="prefetch" href="/blog/assets/js/191.10d0f80b.js"><link rel="prefetch" href="/blog/assets/js/192.127fef4c.js"><link rel="prefetch" href="/blog/assets/js/193.781690eb.js"><link rel="prefetch" href="/blog/assets/js/194.4b375e2e.js"><link rel="prefetch" href="/blog/assets/js/195.003e3d67.js"><link rel="prefetch" href="/blog/assets/js/196.7a3f55e5.js"><link rel="prefetch" href="/blog/assets/js/197.30d4c5b4.js"><link rel="prefetch" href="/blog/assets/js/198.628c2c1a.js"><link rel="prefetch" href="/blog/assets/js/199.d7c8cbb8.js"><link rel="prefetch" href="/blog/assets/js/20.839dae41.js"><link rel="prefetch" href="/blog/assets/js/200.1fed86d2.js"><link rel="prefetch" href="/blog/assets/js/201.5ba078d9.js"><link rel="prefetch" href="/blog/assets/js/202.b49b23f4.js"><link rel="prefetch" href="/blog/assets/js/203.d88a03d9.js"><link rel="prefetch" href="/blog/assets/js/204.fb928277.js"><link rel="prefetch" href="/blog/assets/js/205.432c3d8d.js"><link rel="prefetch" href="/blog/assets/js/206.ed726599.js"><link rel="prefetch" href="/blog/assets/js/207.c3794556.js"><link rel="prefetch" href="/blog/assets/js/208.416f7a9e.js"><link rel="prefetch" href="/blog/assets/js/209.d396aad5.js"><link rel="prefetch" href="/blog/assets/js/21.bd21bd29.js"><link rel="prefetch" href="/blog/assets/js/210.02e4ee2f.js"><link rel="prefetch" href="/blog/assets/js/211.0f8a9304.js"><link rel="prefetch" href="/blog/assets/js/212.9220dd3d.js"><link rel="prefetch" href="/blog/assets/js/213.89521ebd.js"><link rel="prefetch" href="/blog/assets/js/214.cd5ec468.js"><link rel="prefetch" href="/blog/assets/js/215.0084d772.js"><link rel="prefetch" href="/blog/assets/js/216.ebd468b9.js"><link rel="prefetch" href="/blog/assets/js/217.fac21407.js"><link rel="prefetch" href="/blog/assets/js/218.9793e19a.js"><link rel="prefetch" href="/blog/assets/js/219.b99343b6.js"><link rel="prefetch" href="/blog/assets/js/22.e6bcf65d.js"><link rel="prefetch" href="/blog/assets/js/220.8065033b.js"><link rel="prefetch" href="/blog/assets/js/221.97268a80.js"><link rel="prefetch" href="/blog/assets/js/222.22bf261f.js"><link rel="prefetch" href="/blog/assets/js/223.d5ff1bff.js"><link rel="prefetch" href="/blog/assets/js/224.364b4b11.js"><link rel="prefetch" href="/blog/assets/js/225.bd8b00c7.js"><link rel="prefetch" href="/blog/assets/js/226.78192713.js"><link rel="prefetch" href="/blog/assets/js/227.06ec006e.js"><link rel="prefetch" href="/blog/assets/js/228.166546a8.js"><link rel="prefetch" href="/blog/assets/js/229.fd44d2be.js"><link rel="prefetch" href="/blog/assets/js/23.ebbc3fd3.js"><link rel="prefetch" href="/blog/assets/js/230.109a1752.js"><link rel="prefetch" href="/blog/assets/js/231.c6faedb6.js"><link rel="prefetch" href="/blog/assets/js/232.f938a3a8.js"><link rel="prefetch" href="/blog/assets/js/233.0dc59324.js"><link rel="prefetch" href="/blog/assets/js/234.bb73adca.js"><link rel="prefetch" href="/blog/assets/js/235.97ed69db.js"><link rel="prefetch" href="/blog/assets/js/236.a8c51930.js"><link rel="prefetch" href="/blog/assets/js/237.6ae31c88.js"><link rel="prefetch" href="/blog/assets/js/238.2f5c56ae.js"><link rel="prefetch" href="/blog/assets/js/239.506a4e9f.js"><link rel="prefetch" href="/blog/assets/js/24.19bd04ec.js"><link rel="prefetch" href="/blog/assets/js/25.b4de33d1.js"><link rel="prefetch" href="/blog/assets/js/26.0bb98ba9.js"><link rel="prefetch" href="/blog/assets/js/27.df98327e.js"><link rel="prefetch" href="/blog/assets/js/28.31289bac.js"><link rel="prefetch" href="/blog/assets/js/29.45af5621.js"><link rel="prefetch" href="/blog/assets/js/30.d5c08e66.js"><link rel="prefetch" href="/blog/assets/js/31.78e43a68.js"><link rel="prefetch" href="/blog/assets/js/32.53ca76ee.js"><link rel="prefetch" href="/blog/assets/js/33.081e8ef6.js"><link rel="prefetch" href="/blog/assets/js/34.cb1866c1.js"><link rel="prefetch" href="/blog/assets/js/35.314ba98e.js"><link rel="prefetch" href="/blog/assets/js/36.1f6a5fae.js"><link rel="prefetch" href="/blog/assets/js/37.45e6d22f.js"><link rel="prefetch" href="/blog/assets/js/38.70b82353.js"><link rel="prefetch" href="/blog/assets/js/39.df6c26ac.js"><link rel="prefetch" href="/blog/assets/js/4.44654b1a.js"><link rel="prefetch" href="/blog/assets/js/40.80101c19.js"><link rel="prefetch" href="/blog/assets/js/41.2b5e8c27.js"><link rel="prefetch" href="/blog/assets/js/42.c6ded3fe.js"><link rel="prefetch" href="/blog/assets/js/43.6d9424d6.js"><link rel="prefetch" href="/blog/assets/js/44.835e4b5c.js"><link rel="prefetch" href="/blog/assets/js/45.d74d29a2.js"><link rel="prefetch" href="/blog/assets/js/46.d15a7dc0.js"><link rel="prefetch" href="/blog/assets/js/47.8d66ca97.js"><link rel="prefetch" href="/blog/assets/js/48.3c1102e1.js"><link rel="prefetch" href="/blog/assets/js/49.e17a3436.js"><link rel="prefetch" href="/blog/assets/js/5.88de390f.js"><link rel="prefetch" href="/blog/assets/js/50.6750f186.js"><link rel="prefetch" href="/blog/assets/js/51.9f93af9f.js"><link rel="prefetch" href="/blog/assets/js/52.f3ef3b5e.js"><link rel="prefetch" href="/blog/assets/js/53.a6bacd25.js"><link rel="prefetch" href="/blog/assets/js/54.dbb7c9ab.js"><link rel="prefetch" href="/blog/assets/js/55.2562d0c8.js"><link rel="prefetch" href="/blog/assets/js/56.14ea4931.js"><link rel="prefetch" href="/blog/assets/js/57.a2fad780.js"><link rel="prefetch" href="/blog/assets/js/58.8165b971.js"><link rel="prefetch" href="/blog/assets/js/59.556cab0d.js"><link rel="prefetch" href="/blog/assets/js/6.277038ca.js"><link rel="prefetch" href="/blog/assets/js/60.f048aa7c.js"><link rel="prefetch" href="/blog/assets/js/61.bdb307a8.js"><link rel="prefetch" href="/blog/assets/js/62.37a94f10.js"><link rel="prefetch" href="/blog/assets/js/63.74811780.js"><link rel="prefetch" href="/blog/assets/js/64.81f21b8a.js"><link rel="prefetch" href="/blog/assets/js/65.d970ff03.js"><link rel="prefetch" href="/blog/assets/js/66.cb805d9b.js"><link rel="prefetch" href="/blog/assets/js/67.39f85baa.js"><link rel="prefetch" href="/blog/assets/js/68.7f79766a.js"><link rel="prefetch" href="/blog/assets/js/69.fa8624bd.js"><link rel="prefetch" href="/blog/assets/js/7.e0a6d1b0.js"><link rel="prefetch" href="/blog/assets/js/70.1f3e978d.js"><link rel="prefetch" href="/blog/assets/js/71.13cd9358.js"><link rel="prefetch" href="/blog/assets/js/72.739b22a8.js"><link rel="prefetch" href="/blog/assets/js/73.95f69ae2.js"><link rel="prefetch" href="/blog/assets/js/74.b6624f6a.js"><link rel="prefetch" href="/blog/assets/js/75.b0d9aa06.js"><link rel="prefetch" href="/blog/assets/js/76.681b78df.js"><link rel="prefetch" href="/blog/assets/js/77.46f6e413.js"><link rel="prefetch" href="/blog/assets/js/78.aebd00ee.js"><link rel="prefetch" href="/blog/assets/js/79.1b784d15.js"><link rel="prefetch" href="/blog/assets/js/8.9428e7ee.js"><link rel="prefetch" href="/blog/assets/js/80.1f550d53.js"><link rel="prefetch" href="/blog/assets/js/81.101cc131.js"><link rel="prefetch" href="/blog/assets/js/82.077c8298.js"><link rel="prefetch" href="/blog/assets/js/83.2e375d11.js"><link rel="prefetch" href="/blog/assets/js/84.38102a34.js"><link rel="prefetch" href="/blog/assets/js/85.24532d6a.js"><link rel="prefetch" href="/blog/assets/js/86.1dabbf00.js"><link rel="prefetch" href="/blog/assets/js/87.763da0f2.js"><link rel="prefetch" href="/blog/assets/js/88.ff6e5f7c.js"><link rel="prefetch" href="/blog/assets/js/89.187e5e16.js"><link rel="prefetch" href="/blog/assets/js/9.da143545.js"><link rel="prefetch" href="/blog/assets/js/90.3c8cff94.js"><link rel="prefetch" href="/blog/assets/js/91.a50bd44d.js"><link rel="prefetch" href="/blog/assets/js/92.5484868f.js"><link rel="prefetch" href="/blog/assets/js/93.c8ee75e3.js"><link rel="prefetch" href="/blog/assets/js/94.b18a3e9b.js"><link rel="prefetch" href="/blog/assets/js/95.cddef6ae.js"><link rel="prefetch" href="/blog/assets/js/96.80e5a938.js"><link rel="prefetch" href="/blog/assets/js/97.1f5e5197.js"><link rel="prefetch" href="/blog/assets/js/98.e3a275c8.js"><link rel="prefetch" href="/blog/assets/js/99.d33bf89e.js">
  13. <link rel="stylesheet" href="/blog/assets/css/0.styles.dc03b589.css">
  14. </head>
  15. <body class="theme-mode-light">
  16. <div id="app" data-server-rendered="true"><div class="theme-container sidebar-open have-rightmenu"><header class="navbar blur"><div title="目录" class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/blog/" class="home-link router-link-active"><img src="/blog/img/logo.png" alt="彪哥博客" class="logo"> <span class="site-name can-hide">彪哥博客</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/blog/" class="nav-link">首页</a></div><div class="nav-item"><a href="http://fseller.com" target="_blank" rel="noopener noreferrer" class="nav-link external">
  17. 个人游戏网站
  18. <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="前端" class="dropdown-title"><a href="/blog/web/" class="link-title">前端</a> <span class="title" style="display:none;">前端</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><h4>前端文章</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/blog/pages/8143cc480faf9a11/" class="nav-link">JavaScript</a></li></ul></li><li class="dropdown-item"><h4>学习笔记</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/blog/note/javascript/" class="nav-link">《JavaScript教程》</a></li><li class="dropdown-subitem"><a href="/blog/note/js/" class="nav-link">《JavaScript高级程序设计》</a></li><li class="dropdown-subitem"><a href="/blog/note/es6/" class="nav-link">《ES6 教程》</a></li><li class="dropdown-subitem"><a href="/blog/note/vue/" class="nav-link">《Vue》</a></li><li class="dropdown-subitem"><a href="/blog/note/react/" class="nav-link">《React》</a></li><li class="dropdown-subitem"><a href="/blog/note/typescript-axios/" class="nav-link">《TypeScript 从零实现 axios》</a></li><li class="dropdown-subitem"><a href="/blog/note/git/" class="nav-link">《Git》</a></li><li class="dropdown-subitem"><a href="/blog/pages/51afd6/" class="nav-link">TypeScript</a></li><li class="dropdown-subitem"><a href="/blog/pages/4643cd/" class="nav-link">JS设计模式总结</a></li></ul></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="页面" class="dropdown-title"><a href="/blog/ui/" class="link-title">页面</a> <span class="title" style="display:none;">页面</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/pages/8309a5b876fc95e3/" class="nav-link">HTML</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/0a83b083bdf257cb/" class="nav-link">CSS</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="技术" class="dropdown-title"><a href="/blog/technology/" class="link-title">技术</a> <span class="title" style="display:none;">技术</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/pages/9a7ee40fc232253e/" class="nav-link">技术文档</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/4c778760be26d8b3/" class="nav-link">GitHub技巧</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/117708e0af7f0bd9/" class="nav-link">Nodejs</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/41f87d890d0a02af/" class="nav-link">博客搭建</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="更多" class="dropdown-title"><a href="/blog/more/" class="link-title">更多</a> <span class="title" style="display:none;">更多</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/pages/f2a556/" class="nav-link">学习</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/aea6571b7a8bae86/" class="nav-link">面试</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/2d615df9a36a98ed/" class="nav-link">心情杂货</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/baaa02/" class="nav-link">实用技巧</a></li><li class="dropdown-item"><!----> <a href="/blog/friends/" class="nav-link">友情链接</a></li></ul></div></div><div class="nav-item"><a href="/blog/about/" class="nav-link">关于</a></div><div class="nav-item"><a href="/blog/pages/beb6c0bd8a66cea6/" class="nav-link">收藏</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="索引" class="dropdown-title"><a href="/blog/archives/" class="link-title">索引</a> <span class="title" style="display:none;">索引</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/categories/" class="nav-link">分类</a></li><li class="dropdown-item"><!----> <a href="/blog/tags/" class="nav-link">标签</a></li><li class="dropdown-item"><!----> <a href="/blog/archives/" class="nav-link">归档</a></li></ul></div></div> <a href="https://github.com/heBody/blog" target="_blank" rel="noopener noreferrer" class="repo-link">
  19. GitHub
  20. <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav></div></header> <div class="sidebar-mask"></div> <div class="sidebar-hover-trigger"></div> <aside class="sidebar" style="display:none;"><div class="blogger"><img src="/blog/img/head.jpg"> <div class="blogger-info"><h3>彪哥</h3> <span>爱好前端</span></div></div> <nav class="nav-links"><div class="nav-item"><a href="/blog/" class="nav-link">首页</a></div><div class="nav-item"><a href="http://fseller.com" target="_blank" rel="noopener noreferrer" class="nav-link external">
  21. 个人游戏网站
  22. <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="前端" class="dropdown-title"><a href="/blog/web/" class="link-title">前端</a> <span class="title" style="display:none;">前端</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><h4>前端文章</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/blog/pages/8143cc480faf9a11/" class="nav-link">JavaScript</a></li></ul></li><li class="dropdown-item"><h4>学习笔记</h4> <ul class="dropdown-subitem-wrapper"><li class="dropdown-subitem"><a href="/blog/note/javascript/" class="nav-link">《JavaScript教程》</a></li><li class="dropdown-subitem"><a href="/blog/note/js/" class="nav-link">《JavaScript高级程序设计》</a></li><li class="dropdown-subitem"><a href="/blog/note/es6/" class="nav-link">《ES6 教程》</a></li><li class="dropdown-subitem"><a href="/blog/note/vue/" class="nav-link">《Vue》</a></li><li class="dropdown-subitem"><a href="/blog/note/react/" class="nav-link">《React》</a></li><li class="dropdown-subitem"><a href="/blog/note/typescript-axios/" class="nav-link">《TypeScript 从零实现 axios》</a></li><li class="dropdown-subitem"><a href="/blog/note/git/" class="nav-link">《Git》</a></li><li class="dropdown-subitem"><a href="/blog/pages/51afd6/" class="nav-link">TypeScript</a></li><li class="dropdown-subitem"><a href="/blog/pages/4643cd/" class="nav-link">JS设计模式总结</a></li></ul></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="页面" class="dropdown-title"><a href="/blog/ui/" class="link-title">页面</a> <span class="title" style="display:none;">页面</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/pages/8309a5b876fc95e3/" class="nav-link">HTML</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/0a83b083bdf257cb/" class="nav-link">CSS</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="技术" class="dropdown-title"><a href="/blog/technology/" class="link-title">技术</a> <span class="title" style="display:none;">技术</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/pages/9a7ee40fc232253e/" class="nav-link">技术文档</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/4c778760be26d8b3/" class="nav-link">GitHub技巧</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/117708e0af7f0bd9/" class="nav-link">Nodejs</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/41f87d890d0a02af/" class="nav-link">博客搭建</a></li></ul></div></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="更多" class="dropdown-title"><a href="/blog/more/" class="link-title">更多</a> <span class="title" style="display:none;">更多</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/pages/f2a556/" class="nav-link">学习</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/aea6571b7a8bae86/" class="nav-link">面试</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/2d615df9a36a98ed/" class="nav-link">心情杂货</a></li><li class="dropdown-item"><!----> <a href="/blog/pages/baaa02/" class="nav-link">实用技巧</a></li><li class="dropdown-item"><!----> <a href="/blog/friends/" class="nav-link">友情链接</a></li></ul></div></div><div class="nav-item"><a href="/blog/about/" class="nav-link">关于</a></div><div class="nav-item"><a href="/blog/pages/beb6c0bd8a66cea6/" class="nav-link">收藏</a></div><div class="nav-item"><div class="dropdown-wrapper"><button type="button" aria-label="索引" class="dropdown-title"><a href="/blog/archives/" class="link-title">索引</a> <span class="title" style="display:none;">索引</span> <span class="arrow right"></span></button> <ul class="nav-dropdown" style="display:none;"><li class="dropdown-item"><!----> <a href="/blog/categories/" class="nav-link">分类</a></li><li class="dropdown-item"><!----> <a href="/blog/tags/" class="nav-link">标签</a></li><li class="dropdown-item"><!----> <a href="/blog/archives/" class="nav-link">归档</a></li></ul></div></div> <a href="https://github.com/heBody/blog" target="_blank" rel="noopener noreferrer" class="repo-link">
  23. GitHub
  24. <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a></nav> <ul class="sidebar-links"><li><a href="/blog/pages/f344d070a1031ef7/" class="sidebar-link">ECMAScript 6 简介</a></li><li><a href="/blog/pages/c1edd70a6b7c7872/" class="sidebar-link">let 和 const 命令</a></li><li><a href="/blog/pages/b1ab10a62f7564da/" class="sidebar-link">变量的解构赋值</a></li><li><a href="/blog/pages/ca89eca8adeba5f4/" class="sidebar-link">字符串的扩展</a></li><li><a href="/blog/pages/a650b4a0ebfc9350/" class="sidebar-link">字符串的新增方法</a></li><li><a href="/blog/pages/0473261a6ab0ee8c/" class="sidebar-link">正则的扩展</a></li><li><a href="/blog/pages/5dfea9a0f2d1a392/" class="sidebar-link">数值的扩展</a></li><li><a href="/blog/pages/8ed309d668b20264/" class="sidebar-link">函数的扩展</a></li><li><a href="/blog/pages/e34009d60d8bc4b2/" class="sidebar-link">数组的扩展</a></li><li><a href="/blog/pages/b5e3e0a0ff6e9c25/" class="sidebar-link">对象的扩展</a></li><li><a href="/blog/pages/e85e68947502cf90/" class="sidebar-link">对象的新增方法</a></li><li><a href="/blog/pages/02c86eb2792f3262/" class="sidebar-link">Symbol</a></li><li><a href="/blog/pages/0c21dae358fca16b/" class="sidebar-link">Set 和 Map 数据结构</a></li><li><a href="/blog/pages/f56ec2ab97d60483/" class="sidebar-link">Proxy</a></li><li><a href="/blog/pages/74de3e45e4491e95/" class="sidebar-link">Reflect</a></li><li><a href="/blog/pages/2810ae8985e9bd52/" class="sidebar-link">Promise 对象</a></li><li><a href="/blog/pages/48df907ad3570f3d/" class="sidebar-link">Iterator 和 for-of 循环</a></li><li><a href="/blog/pages/718b48ed9ce0adce/" class="sidebar-link">Generator 函数的语法</a></li><li><a href="/blog/pages/75af7031eb66847b/" class="sidebar-link">Generator 函数的异步应用</a></li><li><a href="/blog/pages/3777253e65bac487/" class="sidebar-link">async 函数</a></li><li><a href="/blog/pages/e831e1593c82bbe0/" class="sidebar-link">Class 的基本语法</a></li><li><a href="/blog/pages/83f8c3a0cd87dd83/" class="sidebar-link">Class 的继承</a></li><li><a href="/blog/pages/efe2fb04eb8ac5fb/" class="sidebar-link">Module 的语法</a></li><li><a href="/blog/pages/a79ca2e64ceae213/" aria-current="page" class="active sidebar-link">Module 的加载实现</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level2"><a href="/blog/pages/a79ca2e64ceae213/#浏览器加载" class="sidebar-link">浏览器加载</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#传统方法" class="sidebar-link">传统方法</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#加载规则" class="sidebar-link">加载规则</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/blog/pages/a79ca2e64ceae213/#es6-模块与-commonjs-模块的差异" class="sidebar-link">ES6 模块与 CommonJS 模块的差异</a></li><li class="sidebar-sub-header level2"><a href="/blog/pages/a79ca2e64ceae213/#node-js-加载" class="sidebar-link">Node.js 加载</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#概述" class="sidebar-link">概述</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#main-字段" class="sidebar-link">main 字段</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#exports-字段" class="sidebar-link">exports 字段</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#es6-模块加载-commonjs-模块" class="sidebar-link">ES6 模块加载 CommonJS 模块</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#commonjs-模块加载-es6-模块" class="sidebar-link">CommonJS 模块加载 ES6 模块</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#node-js-的内置模块" class="sidebar-link">Node.js 的内置模块</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#加载路径" class="sidebar-link">加载路径</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#内部变量" class="sidebar-link">内部变量</a></li></ul></li><li class="sidebar-sub-header level2"><a href="/blog/pages/a79ca2e64ceae213/#循环加载" class="sidebar-link">循环加载</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#commonjs-模块的加载原理" class="sidebar-link">CommonJS 模块的加载原理</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#commonjs-模块的循环加载" class="sidebar-link">CommonJS 模块的循环加载</a></li><li class="sidebar-sub-header level3"><a href="/blog/pages/a79ca2e64ceae213/#es6-模块的循环加载" class="sidebar-link">ES6 模块的循环加载</a></li></ul></li></ul></li><li><a href="/blog/pages/984bf549204bb266/" class="sidebar-link">编程风格</a></li><li><a href="/blog/pages/32c35f7651d6e58e/" class="sidebar-link">读懂 ECMAScript 规格</a></li><li><a href="/blog/pages/16121351be68691b/" class="sidebar-link">异步遍历器</a></li><li><a href="/blog/pages/a2ba314746bfdbdd/" class="sidebar-link">ArrayBuffer</a></li><li><a href="/blog/pages/7188882b8d65af1b/" class="sidebar-link">最新提案</a></li><li><a href="/blog/pages/e97bc1e5626b082c/" class="sidebar-link">装饰器</a></li><li><a href="/blog/pages/1cf50330655efc69/" class="sidebar-link">函数式编程</a></li><li><a href="/blog/pages/6a8e2dc558da1b39/" class="sidebar-link">Mixin</a></li><li><a href="/blog/pages/8e8f80f69b775a56/" class="sidebar-link">SIMD</a></li><li><a href="/blog/pages/ea6f3b870f6dab69/" class="sidebar-link">参考链接</a></li></ul> </aside> <div><main class="page"><div class="theme-vdoing-wrapper "><div class="articleInfo-wrap" data-v-06970110><div class="articleInfo" data-v-06970110><ul class="breadcrumbs" data-v-06970110><li data-v-06970110><a href="/blog/" title="首页" class="iconfont icon-home router-link-active" data-v-06970110></a></li> <li data-v-06970110><a href="/blog/note/es6/#《ES6 教程》笔记" data-v-06970110>《ES6 教程》笔记</a></li></ul> <div class="info" data-v-06970110><div title="作者" class="author iconfont icon-touxiang" data-v-06970110><a href="javascript:;" data-v-06970110>阮一峰</a></div> <div title="创建时间" class="date iconfont icon-riqi" data-v-06970110><a href="javascript:;" data-v-06970110>2020-02-09</a></div> <!----></div></div></div> <!----> <div class="content-wrapper"><div class="right-menu-wrapper"><div class="right-menu-margin"><div class="right-menu-title">目录</div> <div class="right-menu-content"></div></div></div> <h1><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAAAXNSR0IArs4c6QAABGpJREFUSA3tVVtoXFUU3fvOI53UlmCaKIFmwEhsE7QK0ipFEdHEKpXaZGrp15SINsXUWvBDpBgQRKi0+KKoFeJHfZA+ED9KKoIU2gYD9UejTW4rVIzm0VSTziPzuNu1z507dibTTjL4U/DAzLn3nL3X2o91ziX6f9wMFdh6Jvbm9nNSV0msViVO6tN1Rm7NMu2OpeJ9lWBUTDxrJbYTS0hInuwciu9eLHlFxCLCZEk3MegsJmZ5K/JD6t7FkFdEvGUo1g7qJoG3MHImqRIn8/nzY1K9UPKKiJmtnUqHVE3Gbuay6vJE/N2FEmuxFjW2nUuE0yQXRRxLiTUAzs36zhZvOXJPdX850EVnnLZkB8prodQoM5JGj7Xk2mvC7JB8tG04Ef5PiXtG0UtxupRQSfTnBoCy554x18yJHI6I+G5Eru4LHmPJZEQsrvPUbMiA8G/WgMK7w7I+ez7++o2ANfbrjvaOl1tFMs+htG3IrZH9/hDX1Pr8Tc0UvH8tcX29KzAgIGcEkINyW5BF9x891hw6VYqgJHEk0huccS7vh3C6gTiODL+26huuBtbct8eZnqLML8PkxGYpuPZBqtqwkSjgc4mB5gbgig5i+y0UDK35LMxXisn9xQtK+nd26gTIHsHe/oblK/b29fUmN/8Y+9jAQrnBp56m1LcDlDp9irKTExSKduXJVWSqdBMA08pEJnEIOB3FPPMybu/oeV8zFeYN3xx576Q6RH+VmplE4ncQV5v+5rzSoyOU7PuEAg8g803PwBJ0CExno/jcMbN8tONYeOmHiuUNryvm3fRUy4tMPVLdAGkUhNWuggGrJcXPv+ouCjz0MKUHz1J2/E8IC9nqTabcxgaBYM0hPhD5Y65FsbxRQKxCQrDjDctW7PUM3HuZunFyifSAqEfuzCp48Il24luWUWZoyJCaPR82jE0+kFA643wRFVni4RYSq3ohJO2pZ7B5dO4xkDWbEpossJPLSrPjYID8rS2UHTlvyNxqIGsg674XJJ7vnh5L7PNwC4hh2sjCI96mzszOTpxLF0T7l88Yz7lAuK6OnL8gXLOnTvpzSb22YG8W7us3jSebFHeeqnXRG1vt+MoUM84LQIBmMsCTAcOauTh0T0l0neQK7m2bLMt2mGxU3HYssS0J2cdv5wljlPsrIuZLAG/2DOZIXgCYT8uMGZN+e2kSirfxZOPCsC0f24nTZzspnVn9VePS1Z5vubmAGGXG8ZFno9Hel0yfA5ZPhF7Dh972BQJ2qCpgH67lmWtBYbvk6sz02wjky2vXyz0XErP/kFB619js1BtwfOV4OPRqOQBjy3Qbk18vigUPPSD5ceHnwck7W9bhAqZdd7SuG7w4/P2F/GaJh8c7e9qgow+Q7cGBo+98WsLkuktFqiZabtXuQTu/Y5ETbR0v7tNSFnvrmu6pjdoan2KjMu8q/Hmj1EfCO2ZGfEIbIXKUlw8qaX9/b2oeSJmFksSeT/Fn0V3nSypChh4Gjh74ybO9aeZ/AN2dwciu2/MhAAAAAElFTkSuQmCC">Module 的加载实现<!----></h1> <div class="theme-vdoing-content content__default"><h1 id="module-的加载实现"><a href="#module-的加载实现" class="header-anchor">#</a> Module 的加载实现</h1> <p>上一章介绍了模块的语法,本章介绍如何在浏览器和 Node.js 之中加载 ES6 模块,以及实际开发中经常遇到的一些问题(比如循环加载)。
  25. </p> <h2 id="浏览器加载"><a href="#浏览器加载" class="header-anchor">#</a> 浏览器加载</h2> <h3 id="传统方法"><a href="#传统方法" class="header-anchor">#</a> 传统方法</h3> <p>HTML 网页中,浏览器通过<code>&lt;script&gt;</code>标签加载 JavaScript 脚本。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token comment">&lt;!-- 页面内嵌的脚本 --&gt;</span>
  26. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>application/javascript<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  27. <span class="token comment">// module code</span>
  28. </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  29. <span class="token comment">&lt;!-- 外部脚本 --&gt;</span>
  30. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>application/javascript<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>path/to/myModule.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  31. </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  32. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>上面代码中,由于浏览器脚本的默认语言是 JavaScript,因此<code>type=&quot;application/javascript&quot;</code>可以省略。</p> <p>默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到<code>&lt;script&gt;</code>标签就会停下来,等到执行完脚本,再继续向下渲染。如果是外部脚本,还必须加入脚本下载的时间。</p> <p>如果脚本体积很大,下载和执行的时间就会很长,因此造成浏览器堵塞,用户会感觉到浏览器“卡死”了,没有任何响应。这显然是很不好的体验,所以浏览器允许脚本异步加载,下面就是两种异步加载的语法。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>path/to/myModule.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">defer</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  33. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>path/to/myModule.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">async</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  34. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>上面代码中,<code>&lt;script&gt;</code>标签打开<code>defer</code>或<code>async</code>属性,脚本就会异步加载。渲染引擎遇到这一行命令,就会开始下载外部脚本,但不会等它下载和执行,而是直接执行后面的命令。</p> <p><code>defer</code>与<code>async</code>的区别是:<code>defer</code>要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;<code>async</code>一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。一句话,<strong><code>defer</code>是“渲染完再执行”,<code>async</code>是“下载完就执行”</strong>。另外,如果有多个<code>defer</code>脚本,会按照它们在页面出现的顺序加载,而多个<code>async</code>脚本是不能保证加载顺序的。</p> <h3 id="加载规则"><a href="#加载规则" class="header-anchor">#</a> 加载规则</h3> <p>浏览器加载 ES6 模块,也使用<code>&lt;script&gt;</code>标签,但是要加入<code>type=&quot;module&quot;</code>属性。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  35. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>上面代码在网页中插入一个模块<code>foo.js</code>,由于<code>type</code>属性设为<code>module</code>,所以浏览器知道这是一个 ES6 模块。</p> <p>浏览器对于<strong>带有<code>type=&quot;module&quot;</code>的<code>&lt;script&gt;</code>,都是异步加载</strong>,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,<strong>等同于打开了<code>&lt;script&gt;</code>标签的<code>defer</code>属性。</strong></p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  36. <span class="token comment">&lt;!-- 等同于 --&gt;</span>
  37. <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">defer</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  38. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>如果网页有多个<code>&lt;script type=&quot;module&quot;&gt;</code>,它们会按照在页面出现的顺序依次执行。</p> <p><code>&lt;script&gt;</code>标签的<code>async</code>属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>./foo.js<span class="token punctuation">&quot;</span></span> <span class="token attr-name">async</span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  39. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>一旦使用了<code>async</code>属性,<code>&lt;script type=&quot;module&quot;&gt;</code>就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。</p> <p>ES6 模块也允许内嵌在网页中,语法行为与加载外部脚本完全一致。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  40. <span class="token keyword">import</span> utils <span class="token keyword">from</span> <span class="token string">&quot;./utils.js&quot;</span><span class="token punctuation">;</span>
  41. <span class="token comment">// other code</span>
  42. </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  43. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>举例来说,jQuery 就支持模块加载。</p> <div class="language-html line-numbers-mode"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation attr-equals">=</span><span class="token punctuation">&quot;</span>module<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  44. <span class="token keyword">import</span> $ <span class="token keyword">from</span> <span class="token string">&quot;./jquery/src/jquery.js&quot;</span><span class="token punctuation">;</span>
  45. <span class="token function">$</span><span class="token punctuation">(</span><span class="token string">'#message'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">text</span><span class="token punctuation">(</span><span class="token string">'Hi from jQuery!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  46. </span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
  47. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>对于外部的模块脚本(上例是<code>foo.js</code>),有几点需要注意。</p> <ul><li>代码是<strong>在模块作用域之中运行</strong>,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。</li> <li>模块脚本<strong>自动采用严格模式</strong>,不管有没有声明<code>use strict</code>。</li> <li>模块之中,可以使用<code>import</code>命令加载其他模块(<code>.js</code>后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用<code>export</code>命令输出对外接口。</li> <li>模块之中,<strong>顶层的<code>this</code>关键字返回<code>undefined</code></strong>,而不是指向<code>window</code>。也就是说,在模块顶层使用<code>this</code>关键字,是无意义的。</li> <li>同一个模块如果加载多次,将只执行一次。</li></ul> <p>下面是一个示例模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">import</span> utils <span class="token keyword">from</span> <span class="token string">'https://example.com/js/utils.js'</span><span class="token punctuation">;</span>
  48. <span class="token keyword">const</span> x <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
  49. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>x <span class="token operator">===</span> window<span class="token punctuation">.</span>x<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">//false</span>
  50. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// true</span>
  51. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>利用顶层的<code>this</code>等于<code>undefined</code>这个语法点,可以侦测当前代码是否在 ES6 模块之中。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">const</span> isNotModuleScript <span class="token operator">=</span> <span class="token keyword">this</span> <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span>
  52. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><h2 id="es6-模块与-commonjs-模块的差异"><a href="#es6-模块与-commonjs-模块的差异" class="header-anchor">#</a> ES6 模块与 CommonJS 模块的差异</h2> <p>讨论 Node.js 加载 ES6 模块之前,必须了解 ES6 模块与 CommonJS 模块完全不同。</p> <p>它们有<strong>两个重大差异</strong>。</p> <ul><li><strong>CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。</strong></li> <li><strong>CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。</strong></li></ul> <p>第二个差异是因为 CommonJS 加载的是一个对象(即<code>module.exports</code>属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。</p> <p>下面重点解释第一个差异。</p> <p>CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。请看下面这个模块文件<code>lib.js</code>的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
  53. <span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
  54. <span class="token keyword">function</span> <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  55. counter<span class="token operator">++</span><span class="token punctuation">;</span>
  56. <span class="token punctuation">}</span>
  57. module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  58. <span class="token literal-property property">counter</span><span class="token operator">:</span> counter<span class="token punctuation">,</span>
  59. <span class="token literal-property property">incCounter</span><span class="token operator">:</span> incCounter<span class="token punctuation">,</span>
  60. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  61. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>上面代码输出内部变量<code>counter</code>和改写这个变量的内部方法<code>incCounter</code>。然后,在<code>main.js</code>里面加载这个模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// main.js</span>
  62. <span class="token keyword">var</span> mod <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./lib'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  63. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>mod<span class="token punctuation">.</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>
  64. mod<span class="token punctuation">.</span><span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  65. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>mod<span class="token punctuation">.</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>
  66. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码说明,<code>lib.js</code>模块加载以后,它的内部变化就影响不到输出的<code>mod.counter</code>了。这是因为<code>mod.counter</code>是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
  67. <span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
  68. <span class="token keyword">function</span> <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  69. counter<span class="token operator">++</span><span class="token punctuation">;</span>
  70. <span class="token punctuation">}</span>
  71. module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span>
  72. <span class="token keyword">get</span> <span class="token function">counter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  73. <span class="token keyword">return</span> counter
  74. <span class="token punctuation">}</span><span class="token punctuation">,</span>
  75. <span class="token literal-property property">incCounter</span><span class="token operator">:</span> incCounter<span class="token punctuation">,</span>
  76. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  77. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>上面代码中,输出的<code>counter</code>属性实际上是一个取值器函数。现在再执行<code>main.js</code>,就可以正确读取内部变量<code>counter</code>的变动了。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ <span class="token function">node</span> main.js
  78. <span class="token number">3</span>
  79. <span class="token number">4</span>
  80. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令<code>import</code>,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的<code>import</code>有点像 Unix 系统的“符号连接”,原始值变了,<code>import</code>加载的值也会跟着变。因此,<strong>ES6 模块是动态引用,并且不会缓存值</strong>,模块里面的变量绑定其所在的模块。</p> <p>还是举上面的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
  81. <span class="token keyword">export</span> <span class="token keyword">let</span> counter <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">;</span>
  82. <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  83. counter<span class="token operator">++</span><span class="token punctuation">;</span>
  84. <span class="token punctuation">}</span>
  85. <span class="token comment">// main.js</span>
  86. <span class="token keyword">import</span> <span class="token punctuation">{</span> counter<span class="token punctuation">,</span> incCounter <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./lib'</span><span class="token punctuation">;</span>
  87. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3</span>
  88. <span class="token function">incCounter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  89. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>counter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 4</span>
  90. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>上面代码说明,ES6 模块输入的变量<code>counter</code>是活的,完全反应其所在模块<code>lib.js</code>内部的变化。</p> <p>再举一个出现在<code>export</code>一节中的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// m1.js</span>
  91. <span class="token keyword">export</span> <span class="token keyword">var</span> foo <span class="token operator">=</span> <span class="token string">'bar'</span><span class="token punctuation">;</span>
  92. <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> foo <span class="token operator">=</span> <span class="token string">'baz'</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  93. <span class="token comment">// m2.js</span>
  94. <span class="token keyword">import</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./m1.js'</span><span class="token punctuation">;</span>
  95. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">;</span>
  96. <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  97. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>上面代码中,<code>m1.js</code>的变量<code>foo</code>,在刚加载时等于<code>bar</code>,过了 500 毫秒,又变为等于<code>baz</code>。</p> <p>让我们看看,<code>m2.js</code>能否正确读取这个变化。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ babel-node m2.js
  98. bar
  99. baz
  100. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。</p> <p>由于 ES6 输入的模块变量,只是一个“符号连接”,所以<strong>这个变量是只读的,对它进行重新赋值会报错</strong>。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// lib.js</span>
  101. <span class="token keyword">export</span> <span class="token keyword">let</span> obj <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span>
  102. <span class="token comment">// main.js</span>
  103. <span class="token keyword">import</span> <span class="token punctuation">{</span> obj <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./lib'</span><span class="token punctuation">;</span>
  104. obj<span class="token punctuation">.</span>prop <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span> <span class="token comment">// OK</span>
  105. obj <span class="token operator">=</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// TypeError</span>
  106. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><p>上面代码中,<code>main.js</code>从<code>lib.js</code>输入变量<code>obj</code>,可以对<code>obj</code>添加属性,但是重新赋值就会报错。因为变量<code>obj</code>指向的地址是只读的,不能重新赋值,这就好比<code>main.js</code>创造了一个名为<code>obj</code>的<code>const</code>变量。</p> <p>最后,<code>export</code>通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// mod.js</span>
  107. <span class="token keyword">function</span> <span class="token constant">C</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  108. <span class="token keyword">this</span><span class="token punctuation">.</span>sum <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  109. <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  110. <span class="token keyword">this</span><span class="token punctuation">.</span>sum <span class="token operator">+=</span> <span class="token number">1</span><span class="token punctuation">;</span>
  111. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  112. <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function-variable function">show</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  113. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>sum<span class="token punctuation">)</span><span class="token punctuation">;</span>
  114. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  115. <span class="token punctuation">}</span>
  116. <span class="token keyword">export</span> <span class="token keyword">let</span> c <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">C</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  117. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br></div></div><p>上面的脚本<code>mod.js</code>,输出的是一个<code>C</code>的实例。不同的脚本加载这个模块,得到的都是同一个实例。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// x.js</span>
  118. <span class="token keyword">import</span> <span class="token punctuation">{</span>c<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./mod'</span><span class="token punctuation">;</span>
  119. c<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  120. <span class="token comment">// y.js</span>
  121. <span class="token keyword">import</span> <span class="token punctuation">{</span>c<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./mod'</span><span class="token punctuation">;</span>
  122. c<span class="token punctuation">.</span><span class="token function">show</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  123. <span class="token comment">// main.js</span>
  124. <span class="token keyword">import</span> <span class="token string">'./x'</span><span class="token punctuation">;</span>
  125. <span class="token keyword">import</span> <span class="token string">'./y'</span><span class="token punctuation">;</span>
  126. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>现在执行<code>main.js</code>,输出的是<code>1</code>。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ babel-node main.js
  127. <span class="token number">1</span>
  128. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>这就证明了<code>x.js</code>和<code>y.js</code>加载的都是<code>C</code>的同一个实例。</p> <h2 id="node-js-加载"><a href="#node-js-加载" class="header-anchor">#</a> Node.js 加载</h2> <h3 id="概述"><a href="#概述" class="header-anchor">#</a> 概述</h3> <p>Node.js 对 ES6 模块的处理比较麻烦,因为它有自己的 CommonJS 模块格式,与 ES6 模块格式是不兼容的。目前的解决方案是,将两者分开,ES6 模块和 CommonJS 采用各自的加载方案。从 v13.2 版本开始,Node.js 已经默认打开了 ES6 模块支持。</p> <p>Node.js 要求 ES6 模块<strong>采用<code>.mjs</code>后缀文件名</strong>。也就是说,只要脚本文件里面使用<code>import</code>或者<code>export</code>命令,那么就必须采用<code>.mjs</code>后缀名。Node.js 遇到<code>.mjs</code>文件,就认为它是 ES6 模块,默认启用严格模式,不必在每个模块文件顶部指定<code>&quot;use strict&quot;</code>。</p> <p><strong>如果不希望将后缀名改成<code>.mjs</code>,可以在项目的<code>package.json</code>文件中,指定<code>type</code>字段为<code>module</code>。</strong></p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  129. <span class="token string-property property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span>
  130. <span class="token punctuation">}</span>
  131. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>一旦设置了以后,该目录里面的 JS 脚本,就被解释用 ES6 模块。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code><span class="token comment"># 解释成 ES6 模块</span>
  132. $ <span class="token function">node</span> my-app.js
  133. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>如果这时还要使用 CommonJS 模块,那么<strong>需要将 CommonJS 脚本的后缀名都改成<code>.cjs</code>。如果没有<code>type</code>字段,或者<code>type</code>字段为<code>commonjs</code>,则<code>.js</code>脚本会被解释成 CommonJS 模块</strong>。</p> <p>总结为一句话:<strong><code>.mjs</code>文件总是以 ES6 模块加载,<code>.cjs</code>文件总是以 CommonJS 模块加载,<code>.js</code>文件的加载取决于<code>package.json</code>里面<code>type</code>字段的设置</strong>。</p> <p>注意,ES6 模块与 CommonJS 模块尽量<strong>不要混用</strong>。<code>require</code>命令不能加载<code>.mjs</code>文件,会报错,只有<code>import</code>命令才可以加载<code>.mjs</code>文件。反过来,<code>.mjs</code>文件里面也不能使用<code>require</code>命令,必须使用<code>import</code>。</p> <h3 id="main-字段"><a href="#main-字段" class="header-anchor">#</a> main 字段</h3> <p><code>package.json</code>文件有两个字段可以指定模块的入口文件:<code>main</code>和<code>exports</code>。比较简单的模块,可以只使用<code>main</code>字段,指定模块加载的入口文件。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/es-module-package/package.json</span>
  134. <span class="token punctuation">{</span>
  135. <span class="token string-property property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span><span class="token punctuation">,</span>
  136. <span class="token string-property property">&quot;main&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./src/index.js&quot;</span>
  137. <span class="token punctuation">}</span>
  138. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面代码指定项目的入口脚本为<code>./src/index.js</code>,它的格式为 ES6 模块。如果没有<code>type</code>字段,<code>index.js</code>就会被解释为 CommonJS 模块。</p> <p>然后,<code>import</code>命令就可以加载这个模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./my-app.mjs</span>
  139. <span class="token keyword">import</span> <span class="token punctuation">{</span> something <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'es-module-package'</span><span class="token punctuation">;</span>
  140. <span class="token comment">// 实际加载的是 ./node_modules/es-module-package/src/index.js</span>
  141. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div><p>上面代码中,<strong>运行该脚本以后,Node.js 就会到<code>./node_modules</code>目录下面,寻找<code>es-module-package</code>模块,然后根据该模块<code>package.json</code>的<code>main</code>字段去执行入口文件。</strong></p> <p>这时,如果用 CommonJS 模块的<code>require()</code>命令去加载<code>es-module-package</code>模块会报错,因为 CommonJS 模块不能处理<code>export</code>命令。</p> <h3 id="exports-字段"><a href="#exports-字段" class="header-anchor">#</a> exports 字段</h3> <p><strong><code>exports</code>字段的优先级高于<code>main</code>字段</strong>。它有多种用法。</p> <p>(1)<strong>子目录别名</strong></p> <p><code>package.json</code>文件的<code>exports</code>字段可以指定脚本或子目录的别名。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/es-module-package/package.json</span>
  142. <span class="token punctuation">{</span>
  143. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  144. <span class="token string-property property">&quot;./submodule&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./src/submodule.js&quot;</span>
  145. <span class="token punctuation">}</span>
  146. <span class="token punctuation">}</span>
  147. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面的代码指定<code>src/submodule.js</code>别名为<code>submodule</code>,然后就可以从别名加载这个文件。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">import</span> submodule <span class="token keyword">from</span> <span class="token string">'es-module-package/submodule'</span><span class="token punctuation">;</span>
  148. <span class="token comment">// 加载 ./node_modules/es-module-package/src/submodule.js</span>
  149. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>下面是子目录别名的例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/es-module-package/package.json</span>
  150. <span class="token punctuation">{</span>
  151. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  152. <span class="token string-property property">&quot;./features/&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./src/features/&quot;</span>
  153. <span class="token punctuation">}</span>
  154. <span class="token punctuation">}</span>
  155. <span class="token keyword">import</span> feature <span class="token keyword">from</span> <span class="token string">'es-module-package/features/x.js'</span><span class="token punctuation">;</span>
  156. <span class="token comment">// 加载 ./node_modules/es-module-package/src/features/x.js</span>
  157. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>如果没有指定别名,就不能用“模块+脚本名”这种形式加载脚本。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// 报错</span>
  158. <span class="token keyword">import</span> submodule <span class="token keyword">from</span> <span class="token string">'es-module-package/private-module.js'</span><span class="token punctuation">;</span>
  159. <span class="token comment">// 不报错</span>
  160. <span class="token keyword">import</span> submodule <span class="token keyword">from</span> <span class="token string">'./node_modules/es-module-package/private-module.js'</span><span class="token punctuation">;</span>
  161. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>(2)main 的别名</p> <p><code>exports</code>字段的别名如果是<code>.</code>,就代表模块的主入口,优先级高于<code>main</code>字段,并且可以直接简写成<code>exports</code>字段的值。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  162. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  163. <span class="token string-property property">&quot;.&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  164. <span class="token punctuation">}</span>
  165. <span class="token punctuation">}</span>
  166. <span class="token comment">// 等同于</span>
  167. <span class="token punctuation">{</span>
  168. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  169. <span class="token punctuation">}</span>
  170. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p><strong>由于<code>exports</code>字段只有支持 ES6 的 Node.js 才认识,所以可以用来兼容旧版本的 Node.js</strong>。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  171. <span class="token string-property property">&quot;main&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main-legacy.cjs&quot;</span><span class="token punctuation">,</span>
  172. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  173. <span class="token string-property property">&quot;.&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main-modern.cjs&quot;</span>
  174. <span class="token punctuation">}</span>
  175. <span class="token punctuation">}</span>
  176. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码中,老版本的 Node.js (不支持 ES6 模块)的入口文件是<code>main-legacy.cjs</code>,新版本的 Node.js 的入口文件是<code>main-modern.cjs</code>。</p> <p><strong>(3)条件加载</strong></p> <p>利用<code>.</code>这个别名,可以为 ES6 模块和 CommonJS 指定不同的入口。目前,这个功能需要在 Node.js 运行的时候,打开<code>--experimental-conditional-exports</code>标志。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  177. <span class="token string-property property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span><span class="token punctuation">,</span>
  178. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  179. <span class="token string-property property">&quot;.&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  180. <span class="token string-property property">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.cjs&quot;</span><span class="token punctuation">,</span>
  181. <span class="token string-property property">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  182. <span class="token punctuation">}</span>
  183. <span class="token punctuation">}</span>
  184. <span class="token punctuation">}</span>
  185. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>上面代码中,别名<code>.</code>的<code>require</code>条件指定<code>require()</code>命令的入口文件(即 CommonJS 的入口),<code>default</code>条件指定其他情况的入口(即 ES6 的入口)。</p> <p>上面的写法可以简写如下。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  186. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  187. <span class="token string-property property">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.cjs&quot;</span><span class="token punctuation">,</span>
  188. <span class="token string-property property">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  189. <span class="token punctuation">}</span>
  190. <span class="token punctuation">}</span>
  191. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>注意,如果同时还有其他别名,就不能采用简写,否则或报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  192. <span class="token comment">// 报错</span>
  193. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  194. <span class="token string-property property">&quot;./feature&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./lib/feature.js&quot;</span><span class="token punctuation">,</span>
  195. <span class="token string-property property">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.cjs&quot;</span><span class="token punctuation">,</span>
  196. <span class="token string-property property">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./main.js&quot;</span>
  197. <span class="token punctuation">}</span>
  198. <span class="token punctuation">}</span>
  199. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br></div></div><h3 id="es6-模块加载-commonjs-模块"><a href="#es6-模块加载-commonjs-模块" class="header-anchor">#</a> ES6 模块加载 CommonJS 模块</h3> <p>目前,一个模块同时支持 ES6 和 CommonJS 两种格式的常见方法是,<code>package.json</code>文件的<code>main</code>字段指定 CommonJS 入口,给 Node.js 使用;<code>module</code>字段指定 ES6 模块入口,给打包工具使用,因为 Node.js 不认识<code>module</code>字段。</p> <p>有了上一节的条件加载以后,Node.js 本身就可以同时处理两种模块。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/pkg/package.json</span>
  200. <span class="token punctuation">{</span>
  201. <span class="token string-property property">&quot;type&quot;</span><span class="token operator">:</span> <span class="token string">&quot;module&quot;</span><span class="token punctuation">,</span>
  202. <span class="token string-property property">&quot;main&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.cjs&quot;</span><span class="token punctuation">,</span>
  203. <span class="token string-property property">&quot;exports&quot;</span><span class="token operator">:</span> <span class="token punctuation">{</span>
  204. <span class="token string-property property">&quot;require&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./index.cjs&quot;</span><span class="token punctuation">,</span>
  205. <span class="token string-property property">&quot;default&quot;</span><span class="token operator">:</span> <span class="token string">&quot;./wrapper.mjs&quot;</span>
  206. <span class="token punctuation">}</span>
  207. <span class="token punctuation">}</span>
  208. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br></div></div><p>上面代码指定了 CommonJS 入口文件<code>index.cjs</code>,下面是这个文件的代码。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/pkg/index.cjs</span>
  209. exports<span class="token punctuation">.</span>name <span class="token operator">=</span> <span class="token string">'value'</span><span class="token punctuation">;</span>
  210. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>然后,ES6 模块可以加载这个文件。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ./node_modules/pkg/wrapper.mjs</span>
  211. <span class="token keyword">import</span> cjsModule <span class="token keyword">from</span> <span class="token string">'./index.cjs'</span><span class="token punctuation">;</span>
  212. <span class="token keyword">export</span> <span class="token keyword">const</span> name <span class="token operator">=</span> cjsModule<span class="token punctuation">.</span>name<span class="token punctuation">;</span>
  213. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>注意,<code>import</code>命令加载 CommonJS 模块,只能整体加载,不能只加载单一的输出项。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// 正确</span>
  214. <span class="token keyword">import</span> packageMain <span class="token keyword">from</span> <span class="token string">'commonjs-package'</span><span class="token punctuation">;</span>
  215. <span class="token comment">// 报错</span>
  216. <span class="token keyword">import</span> <span class="token punctuation">{</span> method <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'commonjs-package'</span><span class="token punctuation">;</span>
  217. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>还有一种变通的加载方法,就是使用 Node.js 内置的<code>module.createRequire()</code>方法。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// cjs.cjs</span>
  218. module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token string">'cjs'</span><span class="token punctuation">;</span>
  219. <span class="token comment">// esm.mjs</span>
  220. <span class="token keyword">import</span> <span class="token punctuation">{</span> createRequire <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'module'</span><span class="token punctuation">;</span>
  221. <span class="token keyword">const</span> require <span class="token operator">=</span> <span class="token function">createRequire</span><span class="token punctuation">(</span><span class="token keyword">import</span><span class="token punctuation">.</span>meta<span class="token punctuation">.</span>url<span class="token punctuation">)</span><span class="token punctuation">;</span>
  222. <span class="token keyword">const</span> cjs <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./cjs.cjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  223. cjs <span class="token operator">===</span> <span class="token string">'cjs'</span><span class="token punctuation">;</span> <span class="token comment">// true</span>
  224. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>上面代码中,ES6 模块通过<code>module.createRequire()</code>方法可以加载 CommonJS 模块</p> <h3 id="commonjs-模块加载-es6-模块"><a href="#commonjs-模块加载-es6-模块" class="header-anchor">#</a> CommonJS 模块加载 ES6 模块</h3> <p>CommonJS 的<code>require</code>命令不能加载 ES6 模块,会报错,只能使用<code>import()</code>这个方法加载。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  225. <span class="token keyword">await</span> <span class="token keyword">import</span><span class="token punctuation">(</span><span class="token string">'./my-app.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  226. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  227. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>上面代码可以在 CommonJS 模块中运行。</p> <h3 id="node-js-的内置模块"><a href="#node-js-的内置模块" class="header-anchor">#</a> Node.js 的内置模块</h3> <p>Node.js 的内置模块可以整体加载,也可以加载指定的输出项。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// 整体加载</span>
  228. <span class="token keyword">import</span> EventEmitter <span class="token keyword">from</span> <span class="token string">'events'</span><span class="token punctuation">;</span>
  229. <span class="token keyword">const</span> e <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventEmitter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  230. <span class="token comment">// 加载指定的输出项</span>
  231. <span class="token keyword">import</span> <span class="token punctuation">{</span> readFile <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'fs'</span><span class="token punctuation">;</span>
  232. <span class="token function">readFile</span><span class="token punctuation">(</span><span class="token string">'./foo.txt'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token parameter">err<span class="token punctuation">,</span> source</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
  233. <span class="token keyword">if</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token punctuation">{</span>
  234. console<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">;</span>
  235. <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
  236. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>source<span class="token punctuation">)</span><span class="token punctuation">;</span>
  237. <span class="token punctuation">}</span>
  238. <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  239. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><h3 id="加载路径"><a href="#加载路径" class="header-anchor">#</a> 加载路径</h3> <p>ES6 模块的加载路径必须给出脚本的完整路径,不能省略脚本的后缀名。<code>import</code>命令和<code>package.json</code>文件的<code>main</code>字段如果省略脚本的后缀名,会报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// ES6 模块中将报错</span>
  240. <span class="token keyword">import</span> <span class="token punctuation">{</span> something <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./index'</span><span class="token punctuation">;</span>
  241. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br></div></div><p>为了与浏览器的<code>import</code>加载规则相同,Node.js 的<code>.mjs</code>文件支持 URL 路径。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">import</span> <span class="token string">'./foo.mjs?query=1'</span><span class="token punctuation">;</span> <span class="token comment">// 加载 ./foo 传入参数 ?query=1</span>
  242. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>上面代码中,脚本路径带有参数<code>?query=1</code>,Node 会按 URL 规则解读。同一个脚本只要参数不同,就会被加载多次,并且保存成不同的缓存。由于这个原因,只要文件名中含有<code>:</code>、<code>%</code>、<code>#</code>、<code>?</code>等特殊字符,最好对这些字符进行转义。</p> <p>目前,Node.js 的<code>import</code>命令只支持加载本地模块(<code>file:</code>协议)和<code>data:</code>协议,不支持加载远程模块。另外,脚本路径只支持相对路径,不支持绝对路径(即以<code>/</code>或<code>//</code>开头的路径)。</p> <p>最后,Node 的<code>import</code>命令是异步加载,这一点与浏览器的处理方法相同。</p> <h3 id="内部变量"><a href="#内部变量" class="header-anchor">#</a> 内部变量</h3> <p>ES6 模块应该是通用的,同一个模块不用修改,就可以用在浏览器环境和服务器环境。为了达到这个目标,Node 规定 ES6 模块之中不能使用 CommonJS 模块的特有的一些内部变量。</p> <p>首先,就是<code>this</code>关键字。ES6 模块之中,顶层的<code>this</code>指向<code>undefined</code>;CommonJS 模块的顶层<code>this</code>指向当前模块,这是两者的一个重大差异。</p> <p>其次,以下这些顶层变量在 ES6 模块之中都是不存在的。</p> <ul><li><code>arguments</code></li> <li><code>require</code></li> <li><code>module</code></li> <li><code>exports</code></li> <li><code>__filename</code></li> <li><code>__dirname</code></li></ul> <h2 id="循环加载"><a href="#循环加载" class="header-anchor">#</a> 循环加载</h2> <p>“循环加载”(circular dependency)指的是,<code>a</code>脚本的执行依赖<code>b</code>脚本,而<code>b</code>脚本的执行又依赖<code>a</code>脚本。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.js</span>
  243. <span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'b'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  244. <span class="token comment">// b.js</span>
  245. <span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  246. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>通常,“循环加载”表示存在强耦合,如果处理不好,还可能导致递归加载,使得程序无法执行,因此应该避免出现。</p> <p>但是实际上,这是很难避免的,尤其是依赖关系复杂的大项目,很容易出现<code>a</code>依赖<code>b</code>,<code>b</code>依赖<code>c</code>,<code>c</code>又依赖<code>a</code>这样的情况。这意味着,模块加载机制必须考虑“循环加载”的情况。</p> <p>对于 JavaScript 语言来说,目前最常见的两种模块格式 CommonJS 和 ES6,处理“循环加载”的方法是不一样的,返回的结果也不一样。</p> <h3 id="commonjs-模块的加载原理"><a href="#commonjs-模块的加载原理" class="header-anchor">#</a> CommonJS 模块的加载原理</h3> <p>介绍 ES6 如何处理“循环加载”之前,先介绍目前最流行的 CommonJS 模块格式的加载原理。</p> <p>CommonJS 的一个模块,就是一个脚本文件。<code>require</code>命令第一次加载该脚本,就会执行整个脚本,然后在内存生成一个对象。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token punctuation">{</span>
  247. <span class="token literal-property property">id</span><span class="token operator">:</span> <span class="token string">'...'</span><span class="token punctuation">,</span>
  248. <span class="token literal-property property">exports</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
  249. <span class="token literal-property property">loaded</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span>
  250. <span class="token operator">...</span>
  251. <span class="token punctuation">}</span>
  252. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码就是 Node 内部加载模块后生成的一个对象。该对象的<code>id</code>属性是模块名,<code>exports</code>属性是模块输出的各个接口,<code>loaded</code>属性是一个布尔值,表示该模块的脚本是否执行完毕。其他还有很多属性,这里都省略了。</p> <p>以后需要用到这个模块的时候,就会到<code>exports</code>属性上面取值。即使再次执行<code>require</code>命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。</p> <h3 id="commonjs-模块的循环加载"><a href="#commonjs-模块的循环加载" class="header-anchor">#</a> CommonJS 模块的循环加载</h3> <p>CommonJS 模块的重要特性是加载时执行,即脚本代码在<code>require</code>的时候,就会全部执行。一旦出现某个模块被&quot;循环加载&quot;,就只输出已经执行的部分,还未执行的部分不会输出。</p> <p>让我们来看,Node <a href="https://nodejs.org/api/modules.html#modules_cycles" target="_blank" rel="noopener noreferrer">官方文档<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>里面的例子。脚本文件<code>a.js</code>代码如下。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  253. <span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./b.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  254. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在 a.js 之中,b.done = %j'</span><span class="token punctuation">,</span> b<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">;</span>
  255. exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  256. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.js 执行完毕'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  257. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面代码之中,<code>a.js</code>脚本先输出一个<code>done</code>变量,然后加载另一个脚本文件<code>b.js</code>。注意,此时<code>a.js</code>代码就停在这里,等待<code>b.js</code>执行完毕,再往下执行。</p> <p>再看<code>b.js</code>的代码。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  258. <span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./a.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  259. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在 b.js 之中,a.done = %j'</span><span class="token punctuation">,</span> a<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">;</span>
  260. exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  261. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'b.js 执行完毕'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  262. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>上面代码之中,<code>b.js</code>执行到第二行,就会去加载<code>a.js</code>,这时,就发生了“循环加载”。系统会去<code>a.js</code>模块对应对象的<code>exports</code>属性取值,可是因为<code>a.js</code>还没有执行完,从<code>exports</code>属性只能取回已经执行的部分,而不是最后的值。</p> <p><code>a.js</code>已经执行的部分,只有一行。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
  263. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>因此,对于<code>b.js</code>来说,它从<code>a.js</code>只输入一个变量<code>done</code>,值为<code>false</code>。</p> <p>然后,<code>b.js</code>接着往下执行,等到全部执行完毕,再把执行权交还给<code>a.js</code>。于是,<code>a.js</code>接着往下执行,直到执行完毕。我们写一个脚本<code>main.js</code>,验证这个过程。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./a.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  264. <span class="token keyword">var</span> b <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./b.js'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  265. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'在 main.js 之中, a.done=%j, b.done=%j'</span><span class="token punctuation">,</span> a<span class="token punctuation">.</span>done<span class="token punctuation">,</span> b<span class="token punctuation">.</span>done<span class="token punctuation">)</span><span class="token punctuation">;</span>
  266. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>执行<code>main.js</code>,运行结果如下。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ <span class="token function">node</span> main.js
  267. 在 b.js 之中,a.done <span class="token operator">=</span> <span class="token boolean">false</span>
  268. b.js 执行完毕
  269. 在 a.js 之中,b.done <span class="token operator">=</span> <span class="token boolean">true</span>
  270. a.js 执行完毕
  271. 在 main.js 之中, <span class="token assign-left variable">a.done</span><span class="token operator">=</span>true, <span class="token assign-left variable">b.done</span><span class="token operator">=</span>true
  272. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br></div></div><p>上面的代码证明了两件事。一是,在<code>b.js</code>之中,<code>a.js</code>没有执行完毕,只执行了第一行。二是,<code>main.js</code>执行到第二行时,不会再次执行<code>b.js</code>,而是输出缓存的<code>b.js</code>的执行结果,即它的第四行。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>exports<span class="token punctuation">.</span>done <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
  273. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br></div></div><p>总之,CommonJS 输入的是被输出值的拷贝,不是引用。</p> <p>另外,由于 CommonJS 模块遇到循环加载时,返回的是当前已经执行的部分的值,而不是代码全部执行后的值,两者可能会有差异。所以,输入变量的时候,必须非常小心。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token keyword">var</span> a <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 安全的写法</span>
  274. <span class="token keyword">var</span> foo <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'a'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>foo<span class="token punctuation">;</span> <span class="token comment">// 危险的写法</span>
  275. exports<span class="token punctuation">.</span><span class="token function-variable function">good</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  276. <span class="token keyword">return</span> a<span class="token punctuation">.</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token string">'good'</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用的是 a.foo 的最新值</span>
  277. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  278. exports<span class="token punctuation">.</span><span class="token function-variable function">bad</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">arg</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  279. <span class="token keyword">return</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token string">'bad'</span><span class="token punctuation">,</span> arg<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 使用的是一个部分加载时的值</span>
  280. <span class="token punctuation">}</span><span class="token punctuation">;</span>
  281. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>上面代码中,如果发生循环加载,<code>require('a').foo</code>的值很可能后面会被改写,改用<code>require('a')</code>会更保险一点。</p> <h3 id="es6-模块的循环加载"><a href="#es6-模块的循环加载" class="header-anchor">#</a> ES6 模块的循环加载</h3> <p>ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用<code>import</code>从一个模块加载变量(即<code>import foo from 'foo'</code>),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。</p> <p>请看下面这个例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.mjs</span>
  282. <span class="token keyword">import</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./b'</span><span class="token punctuation">;</span>
  283. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  284. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>bar<span class="token punctuation">)</span><span class="token punctuation">;</span>
  285. <span class="token keyword">export</span> <span class="token keyword">let</span> foo <span class="token operator">=</span> <span class="token string">'foo'</span><span class="token punctuation">;</span>
  286. <span class="token comment">// b.mjs</span>
  287. <span class="token keyword">import</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./a'</span><span class="token punctuation">;</span>
  288. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'b.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  289. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>foo<span class="token punctuation">)</span><span class="token punctuation">;</span>
  290. <span class="token keyword">export</span> <span class="token keyword">let</span> bar <span class="token operator">=</span> <span class="token string">'bar'</span><span class="token punctuation">;</span>
  291. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br></div></div><p>上面代码中,<code>a.mjs</code>加载<code>b.mjs</code>,<code>b.mjs</code>又加载<code>a.mjs</code>,构成循环加载。执行<code>a.mjs</code>,结果如下。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ <span class="token function">node</span> --experimental-modules a.mjs
  292. b.mjs
  293. ReferenceError: foo is not defined
  294. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br></div></div><p>上面代码中,执行<code>a.mjs</code>以后会报错,<code>foo</code>变量未定义,这是为什么?</p> <p>让我们一行行来看,ES6 循环加载是怎么处理的。首先,执行<code>a.mjs</code>以后,引擎发现它加载了<code>b.mjs</code>,因此会优先执行<code>b.mjs</code>,然后再执行<code>a.mjs</code>。接着,执行<code>b.mjs</code>的时候,已知它从<code>a.mjs</code>输入了<code>foo</code>接口,这时不会去执行<code>a.mjs</code>,而是认为这个接口已经存在了,继续往下执行。执行到第三行<code>console.log(foo)</code>的时候,才发现这个接口根本没定义,因此报错。</p> <p>解决这个问题的方法,就是让<code>b.mjs</code>运行的时候,<code>foo</code>已经有定义了。这可以通过将<code>foo</code>写成函数来解决。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.mjs</span>
  295. <span class="token keyword">import</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./b'</span><span class="token punctuation">;</span>
  296. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  297. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  298. <span class="token keyword">function</span> <span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">'foo'</span> <span class="token punctuation">}</span>
  299. <span class="token keyword">export</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span><span class="token punctuation">;</span>
  300. <span class="token comment">// b.mjs</span>
  301. <span class="token keyword">import</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./a'</span><span class="token punctuation">;</span>
  302. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'b.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  303. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">foo</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  304. <span class="token keyword">function</span> <span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token string">'bar'</span> <span class="token punctuation">}</span>
  305. <span class="token keyword">export</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span><span class="token punctuation">;</span>
  306. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>这时再执行<code>a.mjs</code>就可以得到预期结果。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ <span class="token function">node</span> --experimental-modules a.mjs
  307. b.mjs
  308. foo
  309. a.mjs
  310. bar
  311. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br></div></div><p>这是因为函数具有提升作用,在执行<code>import {bar} from './b'</code>时,函数<code>foo</code>就已经有定义了,所以<code>b.mjs</code>加载的时候不会报错。这也意味着,如果把函数<code>foo</code>改写成函数表达式,也会报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// a.mjs</span>
  312. <span class="token keyword">import</span> <span class="token punctuation">{</span>bar<span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./b'</span><span class="token punctuation">;</span>
  313. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'a.mjs'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  314. console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">bar</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  315. <span class="token keyword">const</span> <span class="token function-variable function">foo</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token string">'foo'</span><span class="token punctuation">;</span>
  316. <span class="token keyword">export</span> <span class="token punctuation">{</span>foo<span class="token punctuation">}</span><span class="token punctuation">;</span>
  317. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br></div></div><p>上面代码的第四行,改成了函数表达式,就不具有提升作用,执行就会报错。</p> <p>我们再来看 ES6 模块加载器<a href="https://github.com/ModuleLoader/es6-module-loader/blob/master/docs/circular-references-bindings.md" target="_blank" rel="noopener noreferrer">SystemJS<span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></a>给出的一个例子。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// even.js</span>
  318. <span class="token keyword">import</span> <span class="token punctuation">{</span> odd <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./odd'</span>
  319. <span class="token keyword">export</span> <span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  320. <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">even</span><span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  321. counter<span class="token operator">++</span><span class="token punctuation">;</span>
  322. <span class="token keyword">return</span> n <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token function">odd</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  323. <span class="token punctuation">}</span>
  324. <span class="token comment">// odd.js</span>
  325. <span class="token keyword">import</span> <span class="token punctuation">{</span> even <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./even'</span><span class="token punctuation">;</span>
  326. <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">odd</span><span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  327. <span class="token keyword">return</span> n <span class="token operator">!==</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">even</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  328. <span class="token punctuation">}</span>
  329. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br></div></div><p>上面代码中,<code>even.js</code>里面的函数<code>even</code>有一个参数<code>n</code>,只要不等于 0,就会减去 1,传入加载的<code>odd()</code>。<code>odd.js</code>也会做类似操作。</p> <p>运行上面这段代码,结果如下。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code>$ babel<span class="token operator">-</span>node
  330. <span class="token operator">&gt;</span> <span class="token keyword">import</span> <span class="token operator">*</span> <span class="token keyword">as</span> m <span class="token keyword">from</span> <span class="token string">'./even.js'</span><span class="token punctuation">;</span>
  331. <span class="token operator">&gt;</span> m<span class="token punctuation">.</span><span class="token function">even</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  332. <span class="token boolean">true</span>
  333. <span class="token operator">&gt;</span> m<span class="token punctuation">.</span>counter
  334. <span class="token number">6</span>
  335. <span class="token operator">&gt;</span> m<span class="token punctuation">.</span><span class="token function">even</span><span class="token punctuation">(</span><span class="token number">20</span><span class="token punctuation">)</span>
  336. <span class="token boolean">true</span>
  337. <span class="token operator">&gt;</span> m<span class="token punctuation">.</span>counter
  338. <span class="token number">17</span>
  339. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br></div></div><p>上面代码中,参数<code>n</code>从 10 变为 0 的过程中,<code>even()</code>一共会执行 6 次,所以变量<code>counter</code>等于 6。第二次调用<code>even()</code>时,参数<code>n</code>从 20 变为 0,<code>even()</code>一共会执行 11 次,加上前面的 6 次,所以变量<code>counter</code>等于 17。</p> <p>这个例子要是改写成 CommonJS,就根本无法执行,会报错。</p> <div class="language-javascript line-numbers-mode"><pre class="language-javascript"><code><span class="token comment">// even.js</span>
  340. <span class="token keyword">var</span> odd <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./odd'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  341. <span class="token keyword">var</span> counter <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  342. exports<span class="token punctuation">.</span>counter <span class="token operator">=</span> counter<span class="token punctuation">;</span>
  343. exports<span class="token punctuation">.</span><span class="token function-variable function">even</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  344. counter<span class="token operator">++</span><span class="token punctuation">;</span>
  345. <span class="token keyword">return</span> n <span class="token operator">==</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token function">odd</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  346. <span class="token punctuation">}</span>
  347. <span class="token comment">// odd.js</span>
  348. <span class="token keyword">var</span> even <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./even'</span><span class="token punctuation">)</span><span class="token punctuation">.</span>even<span class="token punctuation">;</span>
  349. module<span class="token punctuation">.</span><span class="token function-variable function">exports</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token parameter">n</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  350. <span class="token keyword">return</span> n <span class="token operator">!=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">even</span><span class="token punctuation">(</span>n <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  351. <span class="token punctuation">}</span>
  352. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br><span class="line-number">5</span><br><span class="line-number">6</span><br><span class="line-number">7</span><br><span class="line-number">8</span><br><span class="line-number">9</span><br><span class="line-number">10</span><br><span class="line-number">11</span><br><span class="line-number">12</span><br><span class="line-number">13</span><br><span class="line-number">14</span><br></div></div><p>上面代码中,<code>even.js</code>加载<code>odd.js</code>,而<code>odd.js</code>又去加载<code>even.js</code>,形成“循环加载”。这时,执行引擎就会输出<code>even.js</code>已经执行的部分(不存在任何结果),所以在<code>odd.js</code>之中,变量<code>even</code>等于<code>undefined</code>,等到后面调用<code>even(n - 1)</code>就会报错。</p> <div class="language-bash line-numbers-mode"><pre class="language-bash"><code>$ <span class="token function">node</span>
  353. <span class="token operator">&gt;</span> var m <span class="token operator">=</span> require<span class="token punctuation">(</span><span class="token string">'./even'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  354. <span class="token operator">&gt;</span> m.even<span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span>
  355. TypeError: even is not a <span class="token keyword">function</span>
  356. </code></pre> <div class="line-numbers-wrapper"><span class="line-number">1</span><br><span class="line-number">2</span><br><span class="line-number">3</span><br><span class="line-number">4</span><br></div></div></div></div> <div class="page-edit"><div class="edit-link"><a href="https://github.com/heBody/blog/edit/master/docs/《ES6 教程》笔记/24.Module 的加载实现.md" target="_blank" rel="noopener noreferrer">编辑</a> <span><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg> <span class="sr-only">(opens new window)</span></span></div> <div class="tags"><a href="/blog/tags/?tag=ES6" title="标签">#ES6</a></div> <div class="last-updated"><span class="prefix">上次更新:</span> <span class="time">2022/12/14, 19:36:42</span></div></div> <div class="page-nav-wapper"><div class="page-nav-centre-wrap"><a href="/blog/pages/efe2fb04eb8ac5fb/" class="page-nav-centre page-nav-centre-prev"><div class="tooltip">Module 的语法</div></a> <a href="/blog/pages/984bf549204bb266/" class="page-nav-centre page-nav-centre-next"><div class="tooltip">编程风格</div></a></div> <div class="page-nav"><p class="inner"><span class="prev">
  357. <a href="/blog/pages/efe2fb04eb8ac5fb/" class="prev">Module 的语法</a></span> <span class="next"><a href="/blog/pages/984bf549204bb266/">编程风格</a>→
  358. </span></p></div></div></div> <div class="article-list"><div class="article-title"><a href="/blog/archives/" class="iconfont icon-bi">最近更新</a></div> <div class="article-wrapper"><dl><dd>01</dd> <dt><a href="/blog/pages/922650/"><div>
  359. Git修改分支名
  360. <!----></div></a> <span class="date">08-11</span></dt></dl><dl><dd>02</dd> <dt><a href="/blog/pages/55f894/"><div>
  361. CSS给table的tbody添加滚动条
  362. <!----></div></a> <span class="date">06-29</span></dt></dl><dl><dd>03</dd> <dt><a href="/blog/pages/829589/"><div>
  363. 我做了一个手写春联小网页,祝大家虎年暴富
  364. <span class="title-tag">
  365. 原创
  366. </span></div></a> <span class="date">01-28</span></dt></dl> <dl><dd></dd> <dt><a href="/blog/archives/" class="more">更多文章&gt;</a></dt></dl></div></div></main></div> <div class="footer"><div class="icons"><a href="mailto:30363811@qq.com" title="发邮件" target="_blank" class="iconfont icon-youjian"></a><a href="https://github.com/heBody" title="GitHub" target="_blank" class="iconfont icon-github"></a></div>
  367. Copyright © 2016-2022
  368. <span>Hesb | <a href="https://github.com/heBody/blob" target="_blank">MIT License</a></span></div> <div class="buttons"><div title="返回顶部" class="button blur go-to-top iconfont icon-fanhuidingbu" style="display:none;"></div> <div title="去评论" class="button blur go-to-comment iconfont icon-pinglun" style="display:none;"></div> <div title="主题模式" class="button blur theme-mode-but iconfont icon-zhuti"><ul class="select-box" style="display:none;"><li class="iconfont icon-zidong">
  369. 跟随系统
  370. </li><li class="iconfont icon-rijianmoshi">
  371. 浅色模式
  372. </li><li class="iconfont icon-yejianmoshi">
  373. 深色模式
  374. </li><li class="iconfont icon-yuedu">
  375. 阅读模式
  376. </li></ul></div></div> <!----> <!----> <!----></div><div class="global-ui"><div></div></div></div>
  377. <script src="/blog/assets/js/app.90754bd5.js" defer></script><script src="/blog/assets/js/2.106f41fb.js" defer></script><script src="/blog/assets/js/3.6748bd5c.js" defer></script><script src="/blog/assets/js/111.da99a105.js" defer></script>
  378. </body>
  379. </html>