Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 欧美极品另类,欧美一级做一a做片性视频,青青青国产免费线在

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript HTML DOM-改變 HTM

          JavaScript HTML DOM-改變 HTML

          HTML DOM 允許 JavaScript 改變 HTML 元素的內容。

          改變 HTML 輸出流

          JavaScript 能夠創建動態的 HTML 內容:

          今天的日期是: Thu Aug 25 2016 09:23:24 GMT+0800 (中國標準時間)

          在 JavaScript 中,document.write() 可用于直接向 HTML 輸出流寫內容。

          實例

          <!DOCTYPE html>

          <html>

          <body>

          <script>

          document.write(Date());

          </script>

          </body>

          </html>

          絕對不要在文檔加載完成之后使用 document.write()。這會覆蓋該文檔。

          改變 HTML 內容

          修改 HTML 內容的最簡單的方法時使用 innerHTML 屬性。

          如需改變 HTML 元素的內容,請使用這個語法:

          document.getElementById(id).innerHTML=新的 HTML

          本例改變了 <p>元素的內容:

          實例

          <html>

          <body>

          <p id="p1">Hello World!</p>

          <script>

          document.getElementById("p1").innerHTML="新文本!";

          </script>

          </body>

          </html>

          本例改變了 <h1> 元素的內容:

          實例

          <!DOCTYPE html>

          <html>

          <body>

          <h1 id="header">Old Header</h1>

          <script>

          var element=document.getElementById("header");

          element.innerHTML="新標題";

          </script>

          </body>

          </html>

          實例講解:

          • 上面的 HTML 文檔含有 id="header" 的 <h1> 元素

          • 我們使用 HTML DOM 來獲得 id="header" 的元素

          • JavaScript 更改此元素的內容 (innerHTML)

          改變 HTML 屬性

          如需改變 HTML 元素的屬性,請使用這個語法:

          document.getElementById(id).attribute=新屬性值

          本例改變了 <img> 元素的 src 屬性:

          實例

          <!DOCTYPE html>

          <html>

          <body>

          <img id="image" src="smiley.gif">

          <script>

          document.getElementById("image").src="landscape.jpg";

          </script>

          </body>

          </html>

          實例講解:

          • 上面的 HTML 文檔含有 id="image" 的 <img> 元素

          • 我們使用 HTML DOM 來獲得 id="image" 的元素

          • JavaScript 更改此元素的屬性(把 "smiley.gif" 改為 "landscape.jpg")

          如您還有不明白的可以在下面與我留言或是與我探討QQ群308855039,我們一起飛!

          檔對象模型(DOM)

          JS 有很多地方讓咱們吐槽,但沒那么糟糕。作為一種在瀏覽器中運行的腳本語言,它對于處理web頁面非常有用。在本中,我們將看到我們有哪些方法來交互和修改HTML文檔及其元素。但首先讓我們來揭開文檔對象模型的神秘面紗。

          文檔對象模型是一個基本概念,它是咱們在瀏覽器中所做的一切工作的基礎。但那到底是什么? 當咱們訪問一個 web 頁面時,瀏覽器會指出如何解釋每個 HTML 元素。這樣它就創建了 HTML 文檔的虛擬表示,并保存在內存中。HTML 頁面被轉換成樹狀結構,每個 HTML 元素都變成一個葉子,連接到父分支。考慮這個簡單的HTML 頁面:

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <title>A super simple title!</title>
          </head>
          <body>
          <h1>A super simple web page!</h1>
          </body>
          </html
          

          當瀏覽器掃描上面的 HTML 時,它創建了一個文檔對象模型,它是HTML結構的鏡像。在這個結構的頂部有一個 document 也稱為根元素,它包含另一個元素:html。html 元素包含一個 head,head 又有一個 title。然后是含有 h1的 body。每個 HTML 元素由特定類型(也稱為接口)表示,并且可能包含文本或其他嵌套元素

          document (HTMLDocument)
           |
           | --> html (HTMLHtmlElement)
           | 
           | --> head (HtmlHeadElement)
           | |
           | | --> title (HtmlTitleElement)
           | | --> text: "A super simple title!"
           |
           | --> body (HtmlBodyElement)
           | |
           | | --> h1 (HTMLHeadingElement)
           | | --> text: "A super simple web page!"
          

          每個 HTML 元素都是從 Element 派生而來的,但是它們中的很大一部分是進一步專門化的。咱們可以檢查原型,以查明元素屬于什么“種類”。例如,h1 元素是 HTMLHeadingElement

          document.quertSelector('h1').__proto__
          // 輸出:HTMLHeadingElement
          

          HTMLHeadingElement 又是 HTMLElement 的“后代”

          document.querySelector('h1').__proto__.__proto__
          // Output: HTMLElement
          
          Element 是一個通用性非常強的基類,所有 Document 對象下的對象都繼承自它。這個接口描述了所有相同種類的元素所普遍具有的方法和屬性。一些接口繼承自 Element 并且增加了一些額外功能的接口描述了具體的行為。例如, HTMLElement 接口是所有 HTML 元素的基本接口,而 SVGElement 接口是所有 SVG 元素的基礎。大多數功能是在這個類的更深層級(hierarchy)的接口中被進一步制定的。

          在這一點上(特別是對于初學者),document 和 window 之間可能有些混淆。window 指的是瀏覽器,而 document 指的是當前的 HTML 頁面。window 是一個全局對象,可以從瀏覽器中運行的任何 JS 代碼直接訪問它。它不是 JS 的“原生”對象,而是由瀏覽器本身公開的。window 有很多屬性和方法,如下所示:

          window.alert('Hello world'); // Shows an alert
          window.setTimeout(callback, 3000); // Delays execution
          window.fetch(someUrl); // makes XHR requests
          window.open(); // Opens a new tab
          window.location; // Browser location
          window.history; // Browser history
          window.navigator; // The actual device
          window.document; // The current page
          

          由于這些屬性是全局屬性,因此也可以省略 window:

          alert('Hello world'); // Shows an alert
          setTimeout(callback, 3000); // Delays execution
          fetch(someUrl); // makes XHR requests
          open(); // Opens a new tab
          location; // Browser location
          history; // Browser history
          navigator; // The actual device
          document; // The current page
          

          你應該已經熟悉其中的一些方法,例如 setTimeout()或 window.navigator,它可以獲取當前瀏覽器使用的語言:

          if (window.navigator) {
           var lang=window.navigator.language;
           if (lang==="en-US") {
           // show something
           }
           if (lang==="it-IT") {
           // show something else
           }
          }
          

          要了解更多 window 上的方法,請查看MDN文檔。在下一節中,咱們深入地研究一下 DOM

          節點、元素 和DOM 操作

          document 接口有許多實用的方法,比如 querySelector(),它是用于選擇當前 HTML 頁面內的任何 HTML 元素:

          document.querySelector('h1');
          


          window 表示當前窗口的瀏覽器,下面的指令與上面的相同:

          window.document.querySelector('h1');
          


          不管怎樣,下面的語法更常見,在下一節中咱們將大量使用這種形式:

          document.methodName();
          


          除了 querySelector() 用于選擇 HTML 元素之外,還有很多更有用的方法

          // 返回單個元素
          document.getElementById('testimonials'); 
          // 返回一個 HTMLCollection
          document.getElementsByTagName('p'); 
          // 返回一個節點列表
          document.querySelectorAll('p');
          

          咱們不僅可以選 擇HTML 元素,還可以交互和修改它們的內部狀態。例如,希望讀取或更改給定元素的內部內容:

          // Read or write
          document.querySelector('h1').innerHtml; // Read
          document.querySelector('h1').innerHtml=''; // Write! Ouch!
          

          DOM 中的每個 HTML 元素也是一個“節點”,實際上咱們可以像這樣檢查節點類型:

          document.querySelector('h1').nodeType;
          


          上述結果返回 1,表示是 Element 類型的節點的標識符。咱們還可以檢查節點名:

          document.querySelector('h1').nodeName;
          "H1"
          

          這里,節點名以大寫形式返回。通常我們處理 DOM 中的四種類型的節點

          • document: 根節點(nodeType 9)
          • 類型為Element的節點:實際的HTML標簽(nodeType 1),例如 <p> 和 <div>
          • 類型屬性的節點:每個HTML元素的屬性(屬性)
          • Text 類型的節點:元素的實際文本內容(nodeType 3)

          由于元素是節點,節點可以有屬性(properties )(也稱為attributes),咱們可以檢查和操作這些屬性:

          // 返回 true 或者 false
          document.querySelector('a').hasAttribute('href');
          // 返回屬性文本內容,或 null
          document.querySelector('a').getAttribute('href');
          // 設置給定的屬性
          document.querySelector('a').setAttribute('href', 'someLink');
          

          前面我們說過 DOM 是一個類似于樹的結構。這種特性也反映在 HTML 元素上。每個元素都可能有父元素和子元素,我們可以通過檢查元素的某些屬性來查看它們:

          // 返回一個 HTMLCollection
          document.children;
          // 返回一個節點列表
          document.childNodes;
          // 返回一個節點
          document.querySelector('a').parentNode;
          // 返回HTML元素
          document.querySelector('a').parentElement;
          

          了解了如何選擇和查詢 HTML 元素。那創建元素又是怎么樣?為了創建 Element 類型的新節點,原生 DOM API 提供了 createElement 方法:

          var heading=document.createElement('h1');
          


          使用 createTextNode 創建文本節點:

          var text=document.createTextNode('Hello world');
          


          通過將 text 附加到新的 HTML 元素中,可以將這兩個節點組合在一起。最后,還可以將heading元素附加到根文檔中:

          var heading=document.createElement('h1');
          var text=document.createTextNode('Hello world');
          heading.appendChild(text);
          document.body.appendChild(heading);
          

          還可以使用 remove() 方法從 DOM 中刪除節點。在元素上調用方法,該節點將從頁面中消失:

          document.querySelector('h1').remove();
          


          這些是咱們開始在瀏覽器中使用 JS 操作 DOM 所需要知道的全部內容。在下一節中,咱們將靈活地使用 DOM,但首先要繞個彎,因為咱們還需要討論“DOM事件”

          DOM 和事件

          DOM 元素是很智能的。它們不僅可以包含文本和其他 HTML 元素,還可以“發出”和響應“事件”。瀏覽任何網站并打開瀏覽器控制臺。使用以下命令選擇一個元素:

          document.querySelector('p')
          


          看看這個屬性

          document.querySelector('p').onclick
          


          它是什么類型:

          typeof document.querySelector('p').onclick // "object"
          


          "object"! 為什么它被稱為“onclick”? 憑一點直覺我們可以想象它是元素上的某種神奇屬性,能夠對點擊做出反應? 完全正確。

          如果你感興趣,可以查看任何 HTML 元素的原型鏈。會發現每個元素也是一個 Element,而元素又是一個節點,而節點又是一個EventTarget。可以使用 instanceof 來驗證這一點。

          document.querySelector('p') instanceof EventTarget // true
          


          我很樂意稱 EventTarget 為所有 HTML 元素之父,但在JS中沒有真正的繼承,它更像是任何 HTML 元素都可以看到另一個連接對象的屬性。因此,任何 HTML 元素都具有與 EventTarget相同的特性:發布事件的能力

          但事件到底是什么呢?以 HTML 按鈕為例。如果你點擊它,那就是一個事件。有了這個.onclick對象,咱們可以注冊事件,只要元素被點擊,它就會運行。傳遞給事件的函數稱為“事件監聽器”“事件句柄”

          事件和監聽

          在 DOM 中注冊事件監聽器有三種方法。第一種形式比較陳舊,應該避免,因為它耦合了邏輯操作和標簽

          <!-- 不好的方式 -->
          <button onclick="console.log('clicked')">喜歡,就點點我</button>
          

          第二個選項依賴于以事件命名的對象。例如,咱們可以通過在對象.onclick上注冊一個函數來監聽click事件:

          document.querySelector("button").onclick=handleClick;
          function handleClick() {
           console.log("Clicked!");
          }
          

          此語法更加簡潔,是內聯處理程序的不錯替代方案。還有另一種基于addEventListener的現代形式:

          document.querySelector("button").addEventListener("click", handleClick);
          function handleClick() {
           console.log("Clicked!");
          }
          

          就我個人而言,我更喜歡這種形式,但如果想爭取最大限度的瀏覽器兼容性,請使用 .on 方式。現在咱們已經有了一 個 HTML 元素和一個事件監聽器,接著進一步研究一下 DOM 事件。

          事件對象、事件默認值和事件冒泡

          作為事件處理程序傳遞的每個函數默認接收一個名為“event”的對象

          var button=document.querySelector("button");
          button.addEventListener("click", handleClick);
          function handleClick() {
           console.log(event);
          }
          

          它可以直接在函數體中使用,但是在我的代碼中,我更喜歡將它顯式地聲明為參數:

          function handleClick(event) {
           console.log(event);
          }
          

          事件對象是“必須要有的”,因為咱們可以通過調用事件上的一些方法來控制事件的行為。事件實際上有特定的特征,尤其是“默認”“冒泡”。考慮一 個HTML 鏈接。使用以下標簽創建一個名為click-event.html的新HTML文件:

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <title>Click event</title>
          </head>
          <body>
          <div>
           <a href="/404.html">click me!</a>
          </div>
          </body>
          <script src="click-event.js"></script>
          </html>
          

          在瀏覽器中運行該文件并嘗試單擊鏈接。它將跳轉到一個404的界面。鏈接上單擊事件的默認行為是轉到href屬性中指定的實際頁面。但如果我告訴你有辦法阻止默認值呢?輸入preventDefault(),該方法可用于事件對象。使用以下代碼創建一個名為click-event.js的新文件:

          var button=document.querySelector("a");
          button.addEventListener("click", handleClick);
          function handleClick(event) {
           event.preventDefault();
          }
          

          在瀏覽器中刷新頁面并嘗試現在單擊鏈接:它不會跳轉了。因為咱們阻止了瀏覽器的“事件默認” 鏈接不是默認操作的惟一HTML 元素,表單具有相同的特性。

          當 HTML 元素嵌套在另一個元素中時,還會出現另一個有趣的特性。考慮以下 HTML

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <title>Nested events</title>
          </head>
          <body>
          <div id="outer">
           I am the outer div
           <div id="inner">
           I am the inner div
           </div>
          </div>
          </body>
          <script src="nested-events.js"></script>
          </html>
          

          和下面的 JS 代碼:

          // nested-events.js
          var outer=document.getElementById('inner');
          var inner=document.getElementById('outer');
          function handleClick(event){
           console.log(event);
          }
          inner.addEventListener('click', handleClick);
          outer.addEventListener('click', handleClick);
          

          有兩個事件監聽器,一個用于外部 div,一個用于內部 div。準確地點擊內部div,你會看到:

          兩個事件對象被打印。這就是事件冒泡在起作用。它看起來像是瀏覽器行為中的一個 bug,使用 stopPropagation() 方法可以禁用,這也是在事件對象上調用的

          //
          function handleClick(event) {
           event.stopPropagation();
           console.log(event);
          }
          ///
          

          盡管看起來實現效果很差,但在注冊過多事件監聽器確實對性能不利的情況下,冒泡還是會讓人眼前一亮。考慮以下示例:

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <title>Event bubbling</title>
          </head>
          <body>
          <ul>
           <li>one</li>
           <li>two</li>
           <li>three</li>
           <li>four</li>
           <li>five</li>
          </ul>
          </body>
          <script src="event-bubbling.js"></script>
          </html>
          

          如果要兼聽列表的點擊事件,需要在列表中注冊多少事件監聽器?答案是:一個。只需要一個在ul上注冊的偵聽器就可以截獲任何li上的所有單擊:

          // event-bubbling.js
          var ul=document.getElementsByTagName("ul")[0];
          function handleClick(event) {
           console.log(event);
          }
          ul.addEventListener("click", handleClick);
          

          可以看到,事件冒泡是提高性能的一種實用方法。實際上,對瀏覽器來說,注冊事件監聽器是一項昂貴的操作,而且在出現大量元素列表的情況下,可能會導致性能損失。

          用 JS 生成表格

          現在咱們開始編碼。給定一個對象數組,希望動態生成一個HTML 表格。HTML 表格由 <table> 元素表示。每個表也可以有一個頭部,由 <thead> 元素表示。頭部可以有一個或多個行 <tr>,每個行都有一個單元格,由一個 <th>元 素表示。如下所示:

          <table>
           <thead>
           <tr>
           <th>name</th>
           <th>height</th>
           <th>place</th>
           </tr>
           </thead>
           <!-- more stuff here! -->
          </table>
          

          不止這樣,大多數情況下,每個表都有一個主體,由 <tbody> 定義,而 <tbody> 又包含一組行<tr>。每一行都可以有包含實際數據的單元格。表單元格由<td>定義。完整如下所示:

          <table>
           <thead>
           <tr>
           <th>name</th>
           <th>height</th>
           <th>place</th>
           </tr>
           </thead>
           <tbody>
           <tr>
           <td>Monte Falco</td>
           <td>1658</td>
           <td>Parco Foreste Casentinesi</td>
           </tr>
           <tr>
           <td>Monte Falterona</td>
           <td>1654</td>
           <td>Parco Foreste Casentinesi</td>
           </tr>
           </tbody>
          </table>
          

          現在的任務是從 JS 對象數組開始生成表格。首先,創建一個名為build-table.html的新文件,內容如下:

          <!DOCTYPE html>
          <html lang="en">
          <head>
           <meta charset="UTF-8">
           <title>Build a table</title>
          </head>
          <body>
          <table>
          <!-- here goes our data! -->
          </table>
          </body>
          <script src="build-table.js"></script>
          </html>
          

          在相同的文件夾中創建另一個名為build-table.js的文件,并使用以下數組開始:

          "use strict";
          var mountains=[
           { name: "Monte Falco", height: 1658, place: "Parco Foreste Casentinesi" },
           { name: "Monte Falterona", height: 1654, place: "Parco Foreste Casentinesi" },
           { name: "Poggio Scali", height: 1520, place: "Parco Foreste Casentinesi" },
           { name: "Pratomagno", height: 1592, place: "Parco Foreste Casentinesi" },
           { name: "Monte Amiata", height: 1738, place: "Siena" }
          ];
          

          考慮這個表格。首先,咱們需要一個 <thead>:

          document.createElement('thead')
          


          這沒有錯,但是仔細查看MDN的表格文檔會發現一個有趣的細節。<table> 是一個 HTMLTableElement,它還包含有趣方法。其中最有用的是HTMLTableElement.createTHead(),它可以幫助創建咱們需要的 <thead>。

          首先,編寫一個生成 thead 標簽的函數 generateTableHead

          function generateTableHead(table) {
           var thead=table.createTHead();
          }
          

          該函數接受一個選擇器并在給定的表上創建一個 <thead>:

          function generateTableHead(table) {
           var thead=table.createTHead();
          }
          var table=document.querySelector("table");
          generateTableHead(table);
          

          在瀏覽器中打開 build-table.html:什么都沒有.但是,如果打開瀏覽器控制臺,可以看到一個新的 <thead> 附加到表。

          接著填充 header 內容。首先要在里面創建一行。還有另一個方法可以提供幫助:HTMLTableElement.insertRow()。有了這個,咱們就可以擴展方法了:

          function generateTableHead (table) {
           var thead=table,createThead();
           var row=thead.insertRow();
          }
          

          此時,我們可以生成我們的行。通過查看源數組,可以看到其中的任何對象都有咱們需要信息:

          var mountains=[
           { name: "Monte Falco", height: 1658, place: "Parco Foreste Casentinesi" },
           { name: "Monte Falterona", height: 1654, place: "Parco Foreste Casentinesi" },
           { name: "Poggio Scali", height: 1520, place: "Parco Foreste Casentinesi" },
           { name: "Pratomagno", height: 1592, place: "Parco Foreste Casentinesi" },
           { name: "Monte Amiata", height: 1738, place: "Siena" }
          ];
          

          這意味著咱們可以將另一個參數傳遞給我們的函數:一個遍歷以生成標題單元格的數組:

          function generateTableHead(table, data) {
           var thead=table.createTHead();
           var row=thead.insertRow();
           for (var i=0; i < data.length; i++) {
           var th=document.createElement("th");
           var text=document.createTextNode(data[i]);
           th.appendChild(text);
           row.appendChild(th);
           }
          }
          

          不幸的是,沒有創建單元格的原生方法,因此求助于document.createElement("th")。同樣值得注意的是,document.createTextNode(data[i])用于創建文本節點,appendChild()用于向每個標記添加新元素。

          當以這種方式創建和操作元素時,我們稱之為“命令式” DOM 操作。現代前端庫通過支持“聲明式”方法來解決這個問題。我們可以聲明需要哪些 HTML 元素,而不是一步一步地命令瀏覽器,其余的由庫處理。

          回到我們的代碼,可以像下面這樣使用第一個函數

          var table=document.querySelector("table");
          var data=Object.keys(mountains[0]);
          generateTableHead(table, data);
          

          現在我們可以進一步生成實際表的數據。下一個函數將實現一個類似于generateTableHead的邏輯,但這一次咱們需要兩個嵌套的for循環。在最內層的循環中,使用另一種原生方法來創建一系列td。方法是HTMLTableRowElement.insertCell()。在前面創建的文件中添加另一個名為generateTable的函數

          function generateTable(table, data) {
           for (var i=0; i < data.length; i++) {
           var row=table.insertRow();
           for (var key in data[i]) {
           var cell=row.insertCell();
           var text=document.createTextNode(data[i][key]);
           cell.appendChild(text);
           }
           }
          }
          

          調用上面的函數,將 HTML表 和對象數組作為參數傳遞:

          generateTable(table, mountains);
          


          咱們深入研究一下 generateTable 的邏輯。參數 data 是一個與 mountains 相對應的數組。最外層的 for 循環遍歷數組并為每個元素創建一行:

          function generateTable(table, data) {
           for (var i=0; i < data.length; i++) {
           var row=table.insertRow();
           // omitted for brevity
           }
          }
          

          最內層的循環遍歷任何給定對象的每個鍵,并為每個對象創建一個包含鍵值的文本節點

          function generateTable(table, data) {
           for (var i=0; i < data.length; i++) {
           var row=table.insertRow();
           for (var key in data[i]) {
           // inner loop
           var cell=row.insertCell();
           var text=document.createTextNode(data[i][key]);
           cell.appendChild(text);
           }
           }
          }
          

          最終代碼:

          var mountains=[
           { name: "Monte Falco", height: 1658, place: "Parco Foreste Casentinesi" },
           { name: "Monte Falterona", height: 1654, place: "Parco Foreste Casentinesi" },
           { name: "Poggio Scali", height: 1520, place: "Parco Foreste Casentinesi" },
           { name: "Pratomagno", height: 1592, place: "Parco Foreste Casentinesi" },
           { name: "Monte Amiata", height: 1738, place: "Siena" }
          ];
          function generateTableHead(table, data) {
           var thead=table.createTHead();
           var row=thead.insertRow();
           for (var i=0; i < data.length; i++) {
           var th=document.createElement("th");
           var text=document.createTextNode(data[i]);
           th.appendChild(text);
           row.appendChild(th);
           }
          }
          function generateTable(table, data) {
           for (var i=0; i < data.length; i++) {
           var row=table.insertRow();
           for (var key in data[i]) {
           var cell=row.insertCell();
           var text=document.createTextNode(data[i][key]);
           cell.appendChild(text);
           }
           }
          }
          

          其中調用:

          var table=document.querySelector("table");
          var data=Object.keys(mountains[0]);
          generateTable(table, mountains);
          generateTableHead(table, data);
          

          執行結果:


          當然,咱們的方法還可以該進,下個章節將介紹。

          總結

          DOM 是 web 瀏覽器保存在內存中的 web 頁面的虛擬副本。DOM 操作是指從 DOM 中創建、修改和刪除 HTML 元素的操作。在過去,咱們常常依賴 jQuery 來完成更簡單的任務,但現在原生 API 已經足夠成熟,可以讓 jQuery 過時了。另一方面,jQuery 不會很快消失,但是每個 JS 開發人員都必須知道如何使用原生 API 操作 DOM。

          這樣做的理由有很多,額外的庫增加了加載時間和 JS 應用程序的大小。更不用說 DOM 操作在面試中經常出現。

          DOM 中每個可用的 HTML 元素都有一個接口,該接口公開一定數量的屬性和方法。當你對使用何種方法有疑問時,參考MDN文檔。操作 DOM 最常用的方法是 document.createElement()用于創建新的 HTML 元素,document.createTextNode() 用于在 DOM 中創建文本節點。最后但同樣重要的是 .appendchild(),用于將新的 HTML 元素或文本節點附加到現有元素。

          HTML 元素還能夠發出事件,也稱為DOM事件。值得注意的事件為“click”、“submit”、“drag”、“drop”等等。DOM 事件有一些特殊的行為,比如“默認”和冒泡。

          JS 開發人員可以利用這些屬性,特別是對于事件冒泡,這些屬性對于加速 DOM 中的事件處理非常有用。雖然對原生 API 有很好的了解是件好事,但是現代前端庫提供了不容置疑的好處。用 Angular、React 和 Vue 來構建一個大型的JS應用程序確實是可行的。

          代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。

          原文:https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter8.md

          引言

          如今,Javascript 模塊化規范非常方便、自然,但這個新規范僅執行了2年,就在 4 年前,js 的模塊化還停留在運行時支持,10 年前,通過后端模版定義、注釋定義模塊依賴。對經歷過來的人來說,歷史的模塊化方式還停留在腦海中,反而新上手的同學會更快接受現代的模塊化規范。

          但為什么要了解 Javascript 模塊化發展的歷史呢?因為凡事都有兩面性,了解 Javascript 模塊化規范,有利于我們思考出更好的模塊化方案,縱觀歷史,從 1999 年開始,模塊化方案最多維持兩年,就出現了新的替代方案,比原有的模塊化更清晰、強壯,我們不能被現代模塊化方式限制住思維,因為現在的 ES2015 模塊化方案距離發布也僅僅過了兩年。

          2 內容概要

          直接定義依賴 (1999): 由于當時 js 文件非常簡單,模塊化方式非常簡單粗暴 —— 通過全局方法定義、引用模塊。這種定義方式與現在的 commonjs 非常神似,區別是 commonjs 以文件作為模塊,而這種方法可以在任何文件中定義模塊,模塊不與文件關聯。

          閉包模塊化模式 (2003): 用閉包方式解決了變量污染問題,閉包內返回模塊對象,只需對外暴露一個全局變量。

          模版依賴定義 (2006): 這時候開始流行后端模版語法,通過后端語法聚合 js 文件,從而實現依賴加載,說實話,現在 go 語言等模版語法也很流行這種方式,寫后端代碼的時候不覺得,回頭看看,還是掛在可維護性上。

          注釋依賴定義 (2006): 幾乎和模版依賴定義同時出現,與 1999 年方案不同的,不僅僅是模塊定義方式,而是終于以文件為單位定義模塊了,通過 lazyjs 加載文件,同時讀取文件注釋,繼續遞歸加載剩下的文件。

          外部依賴定義 (2007): 這種定義方式在 cocos2d-js 開發中普遍使用,其核心思想是將依賴抽出單獨文件定義,這種方式不利于項目管理,畢竟依賴抽到代碼之外,我是不是得兩頭找呢?所以才有通過 webpack 打包為一個文件的方式暴力替換為 commonjs 的方式出現。

          Sandbox模式 (2009): 這種模塊化方式很簡單,暴力,將所有模塊塞到一個 sanbox 變量中,硬傷是無法解決明明沖突問題,畢竟都塞到一個 sandbox 對象里,而 Sandbox 對象也需要定義在全局,存在被覆蓋的風險。模塊化需要保證全局變量盡量干凈,目前為止的模塊化方案都沒有很好的做到這一點。

          依賴注入 (2009): 就是大家熟知的 angular1.0,依賴注入的思想現在已廣泛運用在 react、vue 等流行框架中。但依賴注入和解決模塊化問題還差得遠。

          CommonJS (2009): 真正解決模塊化問題,從 node 端逐漸發力到前端,前端需要使用構建工具模擬。

          Amd (2009): 都是同一時期的產物,這個方案主要解決前端動態加載依賴,相比 commonJs,體積更小,按需加載。

          Umd (2011): 兼容了 CommonJS 與 Amd,其核心思想是,如果在 commonjs 環境(存在 module.exports,不存在 define),將函數執行結果交給 module.exports 實現 Commonjs,否則用 Amd 環境的 define,實現 Amd。

          Labeled Modules (2012): 和 Commonjs 很像了,沒什么硬傷,但生不逢時,碰上 Commonjs 與 Amd,那只有被人遺忘的份了。

          YModules (2013): 既然都出了 Commonjs Amd,文章還列出了此方案,一定有其獨到之處。其核心思想在于使用 provide 取代 return,可以控制模塊結束時機,處理異步結果;拿到第二個參數 module,修改其他模塊的定義(雖然很有拓展性,但用在項目里是個攪屎棍)。

          ES2015 Modules (2015): 就是我們現在的模塊化方案,還沒有被瀏覽器實現,大部分項目已通過 babel 或 typescript 提前體驗。

          3 精讀

          從語言層面到文件層面的模塊化

          從 1999 年開始,模塊化探索都是基于語言層面的優化,真正的革命從 2009 年 CommonJS 的引入開始,前端開始大量使用預編譯。

          這篇文章所提供的模塊化歷史的方案都是邏輯模塊化,從 CommonJS 方案開始前端把服務端的解決方案搬過來之后,算是看到標準物理與邏輯統一的模塊化。但之后前端工程不得不引入模塊化構建這一步。正是這一步給前端開發無疑帶來了諸多的不便,尤其是現在我們開發過程中經常為了優化這個工具帶了很多額外的成本。

          從 CommonJS 之前其實都只是封裝,并沒有一套模塊化規范,這個就有些像類與包的概念。我在10年左右用的最多的還是 YUI2,YUI2 是用 namespace 來做模塊化的,但有很多問題沒有解決,比如多版本共存,因此后來 YUI3 出來了。

          YUI().use('node', 'event', function (Y) {
           // The Node and Event modules are loaded and ready to use.
           // Your code goes here!
          });
          

          YUI3 的 sandbox 像極了差不多同時出現的 AMD 規范,但早期 yahoo 在前端圈的影響力還是很大的,而 requirejs 到 2011 年才誕生,因此圈子不是用著 YUI 要不就自己封裝一套 sandbox,內部使用 jQuery。

          為什么模塊化方案這么晚才成型,可能早期應用的復雜度都在后端,前端都是非常簡單邏輯。后來 Ajax 火了之后,web app 概念的開始流行,前端的復雜度也呈指數級上漲,到今天幾乎和后端接近一個量級。工程發展到一定階段,要出現的必然會出現。

          前端三劍客的模塊化展望

          從 js 模塊化發展史,我們還看到了 css html 模塊化方面的嚴重落后,如今依賴編譯工具的模塊化增強在未來會被標準所替代。

          原生支持的模塊化,解決 html 與 css 模塊化問題正是以后的方向。

          再回到 JS 模塊化這個主題,開頭也說到是為了構建 scope,實則提供了業務規范標準的輸入輸出的方式。但文章中的 JS 的模塊化還不等于前端工程的模塊化,Web 界面是由 HTML、CSS 和 JS 三種語言實現,不論是 CommonJS 還是 AMD 包括之后的方案都無法解決 CSS 與 HTML 模塊化的問題。

          對于 CSS 本身它就是 global scope,因此開發樣式可以說是喜憂參半。近幾年也涌現把 HTML、CSS 和 JS 合并作模塊化的方案,其中 react/css-modules 和 vue 都為人熟知。當然,這一點還是非常依賴于 webpack/rollup 等構建工具,讓我們意識到在 browser 端還有很多本質的問題需要推進。

          對于 css 模塊化,目前不依賴預編譯的方式是 styled-component,通過 js 動態創建 class。而目前 css 也引入了與 js 通信的機制 與 原生變量支持。未來 css 模塊化也很可能是運行時的,所以目前比較看好 styled-component 的方向。

          對于 html 模塊化,小尤最近爆出與 chrome 小組調研 html Modules,如果 html 得到了瀏覽器,編輯器的模塊化支持,未來可能會取代 jsx 成為最強大的模塊化、模板語言。

          對于 js 模塊化,最近出現的 <script type="module"> 方式,雖然還沒有得到瀏覽器原生支持,但也是我比較看好的未來趨勢,這樣就連 webpack 的拆包都不需要了,直接把源代碼傳到服務器,配合 http2.0 完美拋開預編譯的枷鎖。

          上述三中方案都不依賴預編譯,分別實現了 html、css、js 模塊化,相信這就是未來。

          模塊化標準推進速度仍然緩慢

          2015 年提出的標準,在 17 年依然沒有得到實現,即便在 nodejs 端。

          這幾年 TC39 對語言終于重視起來了,慢慢有動作了,但針對模塊標準制定的速度,與落實都非常緩慢,與 javascript 越來越流行的趨勢逐漸脫節。nodejs 至今也沒有實現 ES2015 模塊化規范,所有 jser 都處在構建工具的陰影下。

          Http 2.0 對 js 模塊化的推動

          js 模塊化定義的再美好,瀏覽器端的支持粒度永遠是瓶頸,http 2.0 正是考慮到了這個因素,大力支持了 ES 2015 模塊化規范。

          幸運的是,模塊化構建將來可能不再需要。隨著 HTTP/2 流行起來,請求和響應可以并行,一次連接允許多個請求,對于前端來說宣告不再需要在開發和上線時再做編譯這個動作。

          幾年前,模塊化幾乎是每個流行庫必造的輪子(YUI、Dojo、Angular),大牛們自己爽的同時其實造成了社區的分裂,很難積累。有了 ES2015 Modules 之后,JS 開發者終于可以像 Java 開始者十年前一樣使用一致的方式愉快的互相引用模塊。

          不過 ES2015 Modules 也只是解決了開發的問題,由于瀏覽器的特殊性,還是要經過繁瑣打包的過程,等 Import,Export 和 HTTP 2.0 被主流瀏覽器支持,那時候才是徹底的模塊化。

          Http 2.0 后就不需要構建工具了嗎?

          看到大家基本都提到了 HTTP/2,對這項技術解決前端模塊化及資源打包等工程問題抱有非常大的期待。很多人也認為 HTTP/2 普及后,基本就沒有 Webpack 什么事情了。

          不過 Webpack 作者 @sokra 在他的文章 webpack & HTTP/2 里提到了一個新的 Webpack 插件 AggressiveSplittingPlugin。簡單的說,這款插件就是為了充分利用 HTTP/2 的文件緩存能力,將你的業務代碼自動拆分成若干個數十 KB 的小文件。后續若其中任意一個文件發生變化,可以保證其他的小 chunck 不需要重新下載。

          可見,即使不斷的有新技術出現,也依然需要配套的工具來將前端工程問題解決方案推向極致。

          模塊化是大型項目的銀彈嗎?

          只要遵循了最新模塊化規范,就可以使項目具有最好的可維護性嗎? Js 模塊化的目的是支持前端日益上升的復雜度,但絕不是唯一的解決方案。

          分析下 JavaScript 為什么沒有模塊化,為什么又需要模塊化:這個 95 年被設計出來的時候,語言的開發者根本沒有想到它會如此的大放異彩,也沒有將它設計成一種模塊化語言。按照文中的說法,99 年也就是 4 年后開始出現了模塊化的需求。如果只有幾行代碼用模塊化是扯,初始的 web 開發業務邏輯都寫在 server 端,js 的作用小之又小。而現在 spa 都出現了,幾乎所有的渲染邏輯都在前端,如果還是沒有模塊化的組織,開發過程會越來越難,維護也是更痛苦。

          本文中已經詳細說明了模塊化的發展和優劣,這里不準備做過多的贅述。但還有一個問題需要我們去關注,那就是在模塊化之后還有一個模塊間耦合的問題,如果模塊間耦合度大也會降低代碼的可重用性或者說復用性。所以也出現了降低耦合的觀察者模式或者發布/訂閱模式。這對于提升代碼重用,復用性和避免單點故障等都很重要。說到這里,還想順便提一下最近流行起來的響應式編程(RxJS),響應式編程中有一個很核心的概念就是 observable,也就是 Rx 中的流(stream)。它可以被 subscribe,其實也就是觀察者設計模式。

          總結

          未來前端復雜度不斷增加已成定論,隨著后端成熟,自然會將焦點轉移到前端領域,而且服務化、用戶體驗越來越重要,前端體驗早不是當初能看就行,任何網頁的異常、視覺的差異,或文案的模糊,都會導致用戶流失,支付中斷。前端對公司營收的影響,漸漸與后端服務宕機同等嚴重,所以前端會越來越重,異常監控,性能檢測,工具鏈,可視化等等都是這幾年大家逐漸重視起來的。

          我們早已不能將 javascript 早期玩具性質的模塊化方案用于現代越來越重要的系統中,前端界必然出現同等重量級的模塊化管理方案,感謝 TC39 制定的 ES2015 模塊化規范,我們已經離不開它,哪怕所有人必須使用 babel。

          話說回來,標準推進的太慢,我們還是把編譯工具當作常態,抱著哪怕支持了 ES2015 所有特性,babel 依然還有用的心態,將預編譯進行到底。一句話,模塊化仍在路上。js 模塊化的矛頭已經對準了 css 與 html,這兩位元老也該向前衛的 js 學習學習了。

          未來 css、html 的模塊化會自立門戶,還是賦予 js 更強的能力,讓兩者的模塊化依附于 js 的能力呢?目前 html 有自立門戶的苗頭(htmlModules),而 css 遲遲沒有改變,社區出現的 styled-component 已經用 js 將 css 模塊化得很好了,最新 css 規范也支持了與 js 的變量通信,難道希望依附于 js 嗎?這里希望得到大家更廣泛的討論。

          我也認同,畢竟壓縮、混淆、md5、或者利用 nonce 屬性對 script 標簽加密,都離不開本地構建工具。

          據說 http2 的優化中,有個最佳文件大小與數量的比例,那么還是脫離不了構建工具,前端未來會越來越復雜,同時也越來越美好。

          至此,對于 javascript 模塊化討論已接近尾聲,對其優缺點也基本達成了一致。前端復雜度不斷提高,促使著模塊化的改進,代理(瀏覽器、node) 的支持程度,與前端特殊性(流量、緩存)可能前端永遠也離不開構建工具,新的標準會讓這些工作做的更好,同時取代、增強部分特征,前端的未來是更加美好的,復雜度也更高。


          主站蜘蛛池模板: 亚洲人AV永久一区二区三区久久| 红桃AV一区二区三区在线无码AV| 亚洲一区二区视频在线观看| 国产suv精品一区二区33| 精产国品一区二区三产区| 国模少妇一区二区三区| 人妻无码一区二区三区| 在线不卡一区二区三区日韩| 伊人久久精品一区二区三区| 亚洲综合无码一区二区| 亚洲爆乳无码一区二区三区| 亚洲色无码一区二区三区| 一区二区三区杨幂在线观看| 国产伦一区二区三区免费| 亚洲AV无码国产一区二区三区| 亚洲欧洲一区二区| 亚洲A∨无码一区二区三区| 国产成人久久精品一区二区三区 | 国产精品小黄鸭一区二区三区 | 男人的天堂精品国产一区| 亚洲AV成人精品一区二区三区| 亚洲一区二区三区免费在线观看| 久久久av波多野一区二区| 日本不卡一区二区视频a| 久久精品国产一区二区电影| 相泽南亚洲一区二区在线播放| 精品国产一区二区三区在线| 日韩精品一区二区三区中文3d | 亚洲AV午夜福利精品一区二区| 伦理一区二区三区| 日韩免费观看一区| 3d动漫精品啪啪一区二区中| 亚欧在线精品免费观看一区| 伊人久久精品无码麻豆一区| 在线不卡一区二区三区日韩| 国产日韩AV免费无码一区二区三区 | 精品人体无码一区二区三区| 中日av乱码一区二区三区乱码| 精品福利一区二区三| 91精品一区二区三区在线观看| 亚洲丶国产丶欧美一区二区三区|