整合營銷服務商

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

          免費咨詢熱線:

          JavaScript進階班之DOM技術總結

          目錄總覽

          1、DOM簡介

          1.1、什么是DOM

          文檔對象模型(Document Object Model,簡稱 DOM),是 W3C 組織推薦的處理可擴展標記語言(HTML或者XML)的標準編程接口,W3C 已經定義了一系列的 DOM 接口,通過這些 DOM 接口可以改變網頁的內容、結構和樣式。

          • 文檔:一個頁面就是一個文檔,DOM中使用doucument來表示
          • 元素:頁面中的所有標簽都是元素,DOM中使用 element 表示
          • 節點:網頁中的所有內容都是節點(標簽,屬性,文本,注釋等),DOM中使用node表示

          DOM 把以上內容都看做是對象

          2、獲取元素

          2.1、如何獲取頁面元素

          DOM在我們實際開發中主要用來操作元素。

          我們如何來獲取頁面中的元素呢?

          獲取頁面中的元素可以使用以下幾種方式:

          • 根據 ID 獲取
          • 根據標簽名獲取
          • 通過 HTML5 新增的方法獲取
          • 特殊元素獲取

          2.2、根據ID獲取

          使用 getElementByld() 方法可以獲取帶ID的元素對象

          doucument.getElementByld('id名')

          使用 console.dir() 可以打印我們獲取的元素對象,更好的查看對象里面的屬性和方法。

          示例

          <div id="time">2019-9-9</div>
          <script>
              // 1.因為我們文檔頁面從上往下加載,所以得先有標簽,所以script寫在標簽下面
              // 2.get 獲得 element 元素 by 通過 駝峰命名法
              // 3.參數 id是大小寫敏感的字符串
              // 4.返回的是一個元素對象
              var timer = document.getElementById('time');
              console.log(timer);
              // 5. console.dir 打印我們的元素對象,更好的查看里面的屬性和方法
              console.dir(timer);
          </script>

          2.3、根據標簽名獲取

          根據標簽名獲取,使用 getElementByTagName() 方法可以返回帶有指定標簽名的對象的集合

          doucument.getElementsByTagName('標簽名');
          • 因為得到的是一個對象的集合,所以我們想要操作里面的元素就需要遍歷
          • 得到元素對象是動態的
          • 返回的是獲取過來元素對象的集合,以偽數組的形式存儲
          • 如果獲取不到元素,則返回為空的偽數組(因為獲取不到對象)
          <ul>
            <li>知否知否,應是等你好久</li>
              <li>知否知否,應是等你好久</li>
              <li>知否知否,應是等你好久</li>
              <li>知否知否,應是等你好久</li>
              <li>知否知否,應是等你好久</li>
          </ul>
          <script>
              // 1.返回的是獲取過來元素對象的集合 以偽數組的形式存儲
              var lis = document.getElementsByTagName('li');
              console.log(lis);
              console.log(lis[0]);
              // 2.依次打印,遍歷
              for (var i = 0; i < lis.length; i++) {
                  console.log(lis[i]);
              }
              // 3.如果頁面中只有 1 個 li,返回的還是偽數組的形式
              // 4.如果頁面中沒有這個元素,返回的是空偽數組
          </script>

          2.4、根據標簽名獲取

          還可以根據標簽名獲取某個元素(父元素)內部所有指定標簽名的子元素,獲取的時候不包括父元素自己

          element.getElementsByTagName('標簽名')
          
          ol.getElementsByTagName('li');

          注意:父元素必須是單個對象(必須指明是哪一個元素對象),獲取的時候不包括父元素自己

          <script>
            //element.getElementsByTagName('標簽名'); 父元素必須是指定的單個元素
              var ol = document.getElementById('ol');
              console.log(ol.getElementsByTagName('li'));
          </script>

          2.5、通過H5新增方法獲取

          ①getElementsByClassName

          根據類名返回元素對象合集

          • document.getElementsByClassName('類名')
          document.getElementsByClassName('類名'); 

          ②document.querySelector

          根據指定選擇器返回第一個元素對象

          document.querySelector('選擇器');
          // 切記里面的選擇器需要加符號 
          // 類選擇器.box 
          // id選擇器 #nav
          var firstBox = document.querySelector('.box');

          ③document.querySelectorAll

          根據指定選擇器返回所有元素對象

          document.querySelectorAll('選擇器');

          注意:

          querySelectorquerySelectorAll 里面的選擇器需要加符號,比如: document.querySelector('#nav');

          ④例子

          <script>
            // 1. getElementsByClassName 根據類名獲得某些元素集合
              var boxs = document.getElementsByClassName('box');
              console.log(boxs);
              // 2. querySelector 返回指定選擇器的第一個元素對象  切記 里面的選擇器需要加符號 .box  #nav
              var firstBox = document.querySelector('.box');
              console.log(firstBox);
              var nav = document.querySelector('#nav');
              console.log(nav);
              var li = document.querySelector('li');
              console.log(li);
              // 3. querySelectorAll()返回指定選擇器的所有元素對象集合
              var allBox = document.querySelectorAll('.box');
              console.log(allBox);
              var lis = document.querySelectorAll('li');
              console.log(lis);
          </script>

          2.6、獲取特殊元素

          ①獲取body元素

          返回body元素對象

          document.body;

          ②獲取html元素

          返回html元素對象

          document.documentElement;

          3、事件基礎

          3.1、事件概述

          JavaScript 使我們有能力創建動態頁面,而事件是可以被 JavaScript 偵測到的行為。

          簡單理解: 觸發— 響應機制。

          網頁中的每個元素都可以產生某些可以觸發 JavaScript 的事件,例如,我們可以在用戶點擊某按鈕時產生一個事件,然后去執行某些操作。

          3.2、事件三要素

          1. 事件源(誰)
          2. 事件類型(什么事件)
          3. 事件處理程序(做啥)
          <script>
              // 點擊一個按鈕,彈出對話框
             // 1. 事件是有三部分組成  事件源  事件類型  事件處理程序   我們也稱為事件三要素
              //(1) 事件源 事件被觸發的對象   誰  按鈕
              var btn = document.getElementById('btn');
              //(2) 事件類型  如何觸發 什么事件 比如鼠標點擊(onclick) 還是鼠標經過 還是鍵盤按下
              //(3) 事件處理程序  通過一個函數賦值的方式 完成
              btn.onclick = function() {
                  alert('點秋香');
              }
          </script>

          3.3、執行事件的步驟

          1. 獲取事件源
          2. 注冊事件(綁定事件)
          3. 添加事件處理程序(采取函數賦值形式)
          <script>
              // 執行事件步驟
              // 點擊div 控制臺輸出 我被選中了
             // 1. 獲取事件源
              var div = document.querySelector('div');
              // 2.綁定事件 注冊事件
              // div.onclick 
              // 3.添加事件處理程序 
              div.onclick = function() {
                  console.log('我被選中了');
              }
          </script>

          3.4、鼠標事件

          鼠標事件

          觸發條件

          onclick

          鼠標點擊左鍵觸發

          onmouseover

          鼠標經過觸發

          onmouseout

          鼠標離開觸發

          onfocus

          獲得鼠標焦點觸發

          onblur

          失去鼠標焦點觸發

          onmousemove

          鼠標移動觸發

          onmouseup

          鼠標彈起觸發

          onmousedown

          鼠標按下觸發

          4、操作元素

          JavaScript 的 DOM 操作可以改變網頁內容、結構和樣式,我們可以利用 DOM 操作元素來改變元素里面的內容 、屬性等。注意以下都是屬性

          4.1、改變元素內容

          從起始位置到終止位置的內容,但它去除html標簽,同時空格和換行也會去掉。

          element.innerText

          起始位置到終止位置的全部內容,包括HTML標簽,同時保留空格和換行

          element.innerHTML
          <body>
              <div></div>
              <p>
                  我是文字
                  <span>123</span>
              </p>
          
              <script>
                  // innerText 和 innerHTML的區別 
                  // 1. innerText 不識別html標簽,去除空格和換行
                  var div = document.querySelector('div');
                  div.innerText = '<strong>今天是:</strong> 2019';
                  // 2. innerHTML 識別html標簽 保留空格和換行的
                  div.innerHTML = '<strong>今天是:</strong> 2019';
                  // 這兩個屬性是可讀寫的  可以獲取元素里面的內容
                  var p = document.querySelector('p');
                  console.log(p.innerText);
                  console.log(p.innerHTML);
              </script>
          </body>

          4.2、改變元素屬性

          // img.屬性
          img.src = "xxx";
          
          input.value = "xxx";
          input.type = "xxx";
          input.checked = "xxx";
          input.selected = true / false;
          input.disabled = true / false;

          4.3、改變樣式屬性

          我們可以通過 JS 修改元素的大小、顏色、位置等樣式。

          • 行內樣式操作
          // element.style
          div.style.backgroundColor = 'pink';
          div.style.width = '250px';
          • 類名樣式操作
          // element.className

          注意:

          1. JS里面的樣式采取駝峰命名法,比如 fontSize ,backgroundColor
          2. JS 修改 style 樣式操作 ,產生的是行內樣式,CSS權重比較高
          3. 如果樣式修改較多,可以采取操作類名方式更改元素樣式
          4. class 因為是個保留字,因此使用className來操作元素類名屬性
          5. className 會直接更改元素的類名,會覆蓋原先的類名
          <body>
              <div class="first">文本</div>
          		<script>
                  // 1. 使用 element.style 獲得修改元素樣式  如果樣式比較少 或者 功能簡單的情況下使用
                  var test = document.querySelector('div');
                  test.onclick = function() {
                      // this.style.backgroundColor = 'purple';
                      // this.style.color = '#fff';
                      // this.style.fontSize = '25px';
                      // this.style.marginTop = '100px';
                      // 讓我們當前元素的類名改為了 change
          
                      // 2. 我們可以通過 修改元素的className更改元素的樣式 適合于樣式較多或者功能復雜的情況
                      // 3. 如果想要保留原先的類名,我們可以這么做 多類名選擇器
                      // this.className = 'change';
                      this.className = 'first change';
                  }
              </script>
          </body>

          4.4、總結

          4.5、排他思想

          如果有同一組元素,我們相要某一個元素實現某種樣式,需要用到循環的排他思想算法:

          1. 所有元素全部清除樣式(干掉其他人)
          2. 給當前元素設置樣式 (留下我自己)
          3. 注意順序不能顛倒,首先干掉其他人,再設置自己
          <body>
            <button>按鈕1</button>
              <button>按鈕2</button>
              <button>按鈕3</button>
              <button>按鈕4</button>
              <button>按鈕5</button>
              <script>
                  // 1. 獲取所有按鈕元素
                  var btns = document.getElementsByTagName('button');
                  // btns得到的是偽數組  里面的每一個元素 btns[i]
                  for (var i = 0; i < btns.length; i++) {
                      btns[i].onclick = function() {
                          // (1) 我們先把所有的按鈕背景顏色去掉  干掉所有人
                          for (var i = 0; i < btns.length; i++) {
                              btns[i].style.backgroundColor = '';
                          }
                          // (2) 然后才讓當前的元素背景顏色為pink 留下我自己
                          this.style.backgroundColor = 'pink';
          
                      }
                  }
                  //2. 首先先排除其他人,然后才設置自己的樣式 這種排除其他人的思想我們成為排他思想
              </script>
          </body>

          4.6、自定義屬性

          4.6.1、獲取屬性值

          • 獲取內置屬性值(元素本身自帶的屬性)
          element.屬性;
          • 獲取自定義的屬性
          element.getAttribute('屬性');

          4.6.2、設置屬性值

          • 設置內置屬性值
          element.屬性 = '值';
          • 主要設置自定義的屬性
          element.setAttribute('屬性','值');

          4.6.3、移除屬性

          element.removeAttribute('屬性');
          <body>
              <div id="demo" index="1" class="nav"></div>
              <script>
                  var div = document.querySelector('div');
                  // 1. 獲取元素的屬性值
                  // (1) element.屬性
                  console.log(div.id);
                  //(2) element.getAttribute('屬性')  get得到獲取 attribute 屬性的意思 我們程序員自己添加的屬性我們稱為自定義屬性 index
                  console.log(div.getAttribute('id'));
                  console.log(div.getAttribute('index'));
                  // 2. 設置元素屬性值
                  // (1) element.屬性= '值'
                  div.id = 'test';
                  div.className = 'navs';
                  // (2) element.setAttribute('屬性', '值');  主要針對于自定義屬性
                  div.setAttribute('index', 2);
                  div.setAttribute('class', 'footer'); // class 特殊  這里面寫的就是class 不是className
                  // 3 移除屬性 removeAttribute(屬性)    
                  div.removeAttribute('index'); 		
          		</script>
          </body>

          4.7、H5自定義屬性

          自定義屬性目的:

          • 保存并保存數據,有些數據可以保存到頁面中而不用保存到數據庫中
          • 有些自定義屬性很容易引起歧義,不容易判斷到底是內置屬性還是自定義的,所以H5有了規定

          4.7.1 設置H5自定義屬性

          H5規定自定義屬性 data-開頭作為屬性名并賦值

          <div data-index = "1"></>
          // 或者使用JavaScript設置
          div.setAttribute('data-index',1);

          4.7.2 獲取H5自定義屬性

          • 兼容性獲取 element.getAttribute('data-index')
          • H5新增的:element.dataset.indexelement.dataset['index'] IE11才開始支持
          <body>
            <div getTime="20" data-index="2" data-list-name="andy"></div>
              <script>
                  var div = document.querySelector('div');
                  console.log(div.getAttribute('getTime'));
                  div.setAttribute('data-time', 20);
                  console.log(div.getAttribute('data-index'));
                  console.log(div.getAttribute('data-list-name'));
                  // h5新增的獲取自定義屬性的方法 它只能獲取data-開頭的
                  // dataset 是一個集合里面存放了所有以data開頭的自定義屬性
                  console.log(div.dataset);
                  console.log(div.dataset.index);
                  console.log(div.dataset['index']);
                  // 如果自定義屬性里面有多個-鏈接的單詞,我們獲取的時候采取 駝峰命名法
                  console.log(div.dataset.listName);
                  console.log(div.dataset['listName']);
              </script>
          </body>

          5、節點操作

          獲取元素通常使用兩種方式:

          1.利用DOM提供的方法獲取元素

          2.利用節點層級關系獲取元素

          document.getElementById()

          利用父子兄節點關系獲取元素

          document.getElementsByTagName()

          邏輯性強,但是兼容性較差

          document.querySelector 等


          邏輯性不強,繁瑣


          這兩種方式都可以獲取元素節點,我們后面都會使用,但是節點操作更簡單

          一般的,節點至少擁有三個基本屬性

          5.1、節點概述

          網頁中的所有內容都是節點(標簽、屬性、文本、注釋等),在DOM 中,節點使用 node 來表示。

          HTML DOM 樹中的所有節點均可通過 JavaScript 進行訪問,所有 HTML 元素(節點)均可被修改,也可以創建或刪除。

          一般的,節點至少擁有nodeType(節點類型)、nodeName(節點名稱)和nodeValue(節點值)這三個基本屬性。

          • 元素節點:nodeType 為1
          • 屬性節點:nodeType 為2
          • 文本節點:nodeType 為3(文本節點包括文字、空格、換行等)

          我們在實際開發中,節點操作主要操作的是元素節點

          利用 DOM 樹可以把節點劃分為不同的層級關系,常見的是父子兄層級關系

          5.2、父級節點

          node.parentNode
          • parentNode屬性可以返回某節點的父結點,注意是最近的一個父結點
          • 如果指定的節點沒有父結點則返回null
          <body>
            <!-- 節點的優點 -->
              <div>我是div</div>
              <span>我是span</span>
              <ul>
                  <li>我是li</li>
                  <li>我是li</li>
                  <li>我是li</li>
                  <li>我是li</li>
              </ul>
              <div class="demo">
                  <div class="box">
                      <span class="erweima">×</span>
                  </div>
              </div>
          
              <script>
                  // 1. 父節點 parentNode
                  var erweima = document.querySelector('.erweima');
                  // var box = document.querySelector('.box');
                  // 得到的是離元素最近的父級節點(親爸爸) 如果找不到父節點就返回為 null
                  console.log(erweima.parentNode);
              </script>
          </body>

          5.3、子結點

          parentNode.childNodes(標準)
          • parentNode.childNodes 返回包含指定節點的子節點的集合,該集合為即時更新的集合
          • 返回值包含了所有的子結點,包括元素節點,文本節點等
          • 如果只想要獲得里面的元素節點,則需要專門處理。所以我們一般不提倡使用childNodes
          parentNode.children(非標準)
          • parentNode.children 是一個只讀屬性,返回所有的子元素節點
          • 它只返回子元素節點,其余節點不返回 (這個是我們重點掌握的
          • 雖然 children 是一個非標準,但是得到了各個瀏覽器的支持,因此我們可以放心使用
          <body>
            <ul>
                  <li>我是li</li>
                  <li>我是li</li>
                  <li>我是li</li>
                  <li>我是li</li>
              </ul>
              <ol>
                  <li>我是li</li>
                  <li>我是li</li>
                  <li>我是li</li>
                  <li>我是li</li>
              </ol>
              <script>
                  // DOM 提供的方法(API)獲取
                  var ul = document.querySelector('ul');
                  var lis = ul.querySelectorAll('li');
                  // 1. 子節點  childNodes 所有的子節點 包含 元素節點 文本節點等等
                  console.log(ul.childNodes);
                  console.log(ul.childNodes[0].nodeType);
                  console.log(ul.childNodes[1].nodeType);
                  // 2. children 獲取所有的子元素節點 也是我們實際開發常用的
                  console.log(ul.children);
              </script>
          </body>

          5.3.1、第一個子結點

          parentNode.firstChild
          • firstChild 返回第一個子節點,找不到則返回null
          • 同樣,也是包含所有的節點

          5.3.2、最后一個子結點

          parentNode.lastChild
          • lastChild 返回最后一個子節點,找不到則返回null
          • 同樣,也是包含所有的節點
          <body>
            <ol>
                  <li>我是li1</li>
                  <li>我是li2</li>
                  <li>我是li3</li>
                  <li>我是li4</li>
                  <li>我是li5</li>
              </ol>
              <script>
                  var ol = document.querySelector('ol');
                  // 1. firstChild 第一個子節點 不管是文本節點還是元素節點
                  console.log(ol.firstChild);
                  console.log(ol.lastChild);
                  // 2. firstElementChild 返回第一個子元素節點 ie9才支持
                  console.log(ol.firstElementChild);
                  console.log(ol.lastElementChild);
                  // 3. 實際開發的寫法  既沒有兼容性問題又返回第一個子元素
                  console.log(ol.children[0]);			//第一個子元素節點
                  console.log(ol.children[ol.children.length - 1]);//最后一個子元素節點
              </script>
          </body>

          5.3.3、第一個子結點(兼容性)

          parentNode.firstElementChild
          • firstElementChild 返回第一個子節點,找不到則返回null
          • 有兼容性問題,IE9以上才支持

          5.3.4、最后一個子結點(兼容性)

          parentNode.lastElementChild
          • lastElementChild 返回最后一個子節點,找不到則返回null
          • 有兼容性問題,IE9以上才支持

          5.3.5、解決方案

          實際開發中,firstChild 和 lastChild 包含其他節點,操作不方便,而 firstElementChild 和 lastElementChild 又有兼容性問題,那么我們如何獲取第一個子元素節點或最后一個子元素節點呢?

          解決方案

          • 如果想要第一個子元素節點,可以使用 parentNode.chilren[0]
          • 如果想要最后一個子元素節點,可以使用
          // 數組元素個數減1 就是最后一個元素的索引號
          parentNode.chilren[parentNode.chilren.length - 1]
          • 示例:
          <body>
              <ol>
                  <li>我是li1</li>
          				<li>我是li2</li>
                  <li>我是li3</li>
                  <li>我是li4</li>
              </ol>
              <script>
                  var ol = document.querySelector('ol');
                  // 1.firstChild 獲取第一個子結點的,包含文本結點和元素結點
                  console.log(ol.firstChild);
                  // 返回的是文本結點 #text(第一個換行結點)
                  
                  console.log(ol.lastChild);
                  // 返回的是文本結點 #text(最后一個換行結點)
                  // 2. firstElementChild 返回第一個子元素結點
                  console.log(ol.firstElementChild);
                  // <li>我是li1</li>
                  
                  // 第2個方法有兼容性問題,需要IE9以上才支持
                  // 3.實際開發中,既沒有兼容性問題,又返回第一個子元素
                  console.log(ol.children[0]);
                  // <li>我是li1</li>
                  console.log(ol.children[3]);
                  // <li>我是li4</li>
                  // 當里面li個數不唯一時候,需要取到最后一個結點時這么寫
                  console.log(ol.children[ol.children.length - 1]);
              </script>
          </body>

          5.4、兄弟節點

          5.4.1、下一個兄弟節點

          node.nextSibling
          • nextSibling 返回當前元素的下一個兄弟元素節點,找不到則返回null
          • 同樣,也是包含所有的節點

          5.4.2、上一個兄弟節點

          node.previousSibling
          • previousSibling 返回當前元素上一個兄弟元素節點,找不到則返回null
          • 同樣,也是包含所有的節點

          5.4.3、下一個兄弟節點(兼容性)

          node.nextElementSibling
          • nextElementSibling 返回當前元素下一個兄弟元素節點,找不到則返回null
          • 有兼容性問題,IE9才支持

          5.4.4、上一個兄弟節點(兼容性)

          node.previousElementSibling
          • previousElementSibling 返回當前元素上一個兄弟元素節點,找不到則返回null
          • 有兼容性問題,IE9才支持

          示例

          <body>
              <div>我是div</div>
              <span>我是span</span>
          		<script>
                  var div = document.querySelector('div');
                  // 1.nextSibling 下一個兄弟節點 包含元素節點或者 文本節點等等
                  console.log(div.nextSibling);		// #text
                  console.log(div.previousSibling);	// #text
                  // 2. nextElementSibling 得到下一個兄弟元素節點
                  console.log(div.nextElementSibling);	//<span>我是span</span>
                  console.log(div.previousElementSibling);//null
              </script>
          </body>

          如何解決兼容性問題 ?

          答:自己封裝一個兼容性的函數

          function getNextElementSibling(element) {
            	var el = element;
              while(el = el.nextSibling) {
                  if(el.nodeType === 1){
                      return el;
                  }
              }
              return null;
          }

          5.5、創建節點

          document.createElement('tagName');
          • document.createElement() 方法創建由 tagName 指定的HTML 元素
          • 因為這些元素原先不存在,是根據我們的需求動態生成的,所以我們也稱為動態創建元素節點

          5.5.1、添加節點

          node.appendChild(child)
          • node.appendChild() 方法將一個節點添加到指定父節點的子節點列表末尾。類似于 CSS 里面的 after 偽元素。
          node.insertBefore(child,指定元素)
          • node.insertBefore() 方法將一個節點添加到父節點的指定子節點前面。類似于 CSS 里面的 before 偽元素。

          示例

          <body>
              <ul>
                  <li>123</li>
              </ul>
          		<script>
                  // 1. 創建節點元素節點
                  var li = document.createElement('li');
                  // 2. 添加節點 node.appendChild(child)  node 父級  child 是子級 后面追加元素  類似于數組中的push
                  // 先獲取父親ul
                  var ul = document.querySelector('ul');
                  ul.appendChild(li);
                  // 3. 添加節點 node.insertBefore(child, 指定元素);
                  var lili = document.createElement('li');
                  ul.insertBefore(lili, ul.children[0]);
                  // 4. 我們想要頁面添加一個新的元素分兩步: 1. 創建元素 2. 添加元素
              </script>
          </body>

          5.5.2、刪除節點

          node.removeChild(child)
          • node.removeChild()方法從 DOM 中刪除一個子節點,返回刪除的節點

          5.5.3、復制節點(克隆節點)

          node.cloneNode()
          • node.cloneNode()方法返回調用該方法的節點的一個副本。 也稱為克隆節點/拷貝節點
          • 如果括號參數為空或者為 false ,則是淺拷貝,即只克隆復制節點本身,不克隆里面的子節點
          • 如果括號參數為 true ,則是深度拷貝,會復制節點本身以及里面所有的子節點

          示例

          <body>
              <ul>
                  <li>1111</li>
                  <li>2</li>
                  <li>3</li>
              </ul>
          		<script>
                  var ul = document.querySelector('ul');
                  // 1. node.cloneNode(); 括號為空或者里面是false 淺拷貝 只復制標簽不復制里面的內容
                  // 2. node.cloneNode(true); 括號為true 深拷貝 復制標簽復制里面的內容
                  var lili = ul.children[0].cloneNode(true);
                  ul.appendChild(lili);
              </script>
          </body>

          5.5.4、面試題

          三種動態創建元素的區別

          • doucument.write()
          • element.innerHTML
          • document.createElement()

          區別

          • document.write() 是直接將內容寫入頁面的內容流,但是文檔流執行完畢,則它會導致頁面全部重繪
          • innerHTML 是將內容寫入某個 DOM 節點,不會導致頁面全部重繪
          • innerHTML 創建多個元素效率更高(不要拼接字符串,采取數組形式拼接),結構稍微復雜
          <body>
              <div class="innner"></div>
              <div class="create"></div>
          		<script>
                  // 2. innerHTML 創建元素
                  var inner = document.querySelector('.inner');
                  // 2.1 innerHTML 用拼接字符串方法
                  for (var i = 0; i <= 100; i++) {
                      inner.innerHTML += '<a href="#">百度</a>';
                  }
                  // 2.2 innerHTML 用數組形式拼接
                  var arr = [];
                  for (var i = 0; i <= 100; i++) {
                      arr.push('<a href="#">百度</a>');
                  }
                  inner.innerHTML = arr.join('');
          
                  // 3.document.createElement() 創建元素
                  var create = document.querySelector('.create');
                  var a = document.createElement('a');
                  create.appendChild(a);
              </script>
          </body>
          • createElement()創建多個元素效率稍低一點點,但是結構更清晰

          總結:不同瀏覽器下, innerHTML 效率要比 createElement 高

          6、DOM核心

          對于DOM操作,我們主要針對子元素的操作,主要有

          • 創建
          • 屬性操作
          • 時間操作

          6.1、創建

          1. document.write
          2. innerHTML
          3. createElement

          6.2、增

          1. appendChild
          2. insertBefore

          6.3、刪

          1. removeChild

          6.4、改

          • 主要修改dom的元素屬性,dom元素的內容、屬性、表單的值等
          1. 修改元素屬性:src、href、title 等
          2. 修改普通元素內容:innerHTML、innerText
          3. 修改表單元素:value、type、disabled
          4. 修改元素樣式:style、className

          6.5、查

          • 主要獲取查詢dom的元素
          1. DOM提供的API方法:getElementById、getElementsByTagName (古老用法,不推薦)
          2. H5提供的新方法:querySelector、querySelectorAll (提倡)
          3. 利用節點操作獲取元素:父(parentNode)、子(children)、兄(previousElementSibling、nextElementSibling) 提倡

          6.6、屬性操作

          • 主要針對于自定義屬性
          1. setAttribute:設置dom的屬性值
          2. getAttribute:得到dom的屬性值
          3. removeAttribute:移除屬性

          7、事件高級

          7.1、注冊事件(綁定事件)

          給元素添加事件,稱為注冊事件或者綁定事件

          注冊事件有兩種方式:傳統方式和方法監聽注冊方式

          傳統注冊方式

          方法監聽注冊方式

          利用 on 開頭的事件 onclick

          w3c 標準推薦方式

          <button onclick = "alert("hi")"></button>

          addEventListener() 它是一個方法

          btn.onclick = function() {}

          IE9 之前的 IE 不支持此方法,可使用 attachEvent() 代替

          特點:注冊事件的唯一性

          特點:同一個元素同一個事件可以注冊多個監聽器

          同一個元素同一個事件只能設置一個處理函數,最后注冊的處理函數將會覆蓋前面注冊的處理函數

          按注冊順序依次執行

          ①addEventListener事件監聽方式

          • eventTarget.addEventListener()方法將指定的監聽器注冊到 eventTarget(目標對象)上
          • 當該對象觸發指定的事件時,就會執行事件處理函數
          eventTarget.addEventListener(type,listener[,useCapture])

          該方法接收三個參數:

          • type:事件類型字符串,比如click,mouseover,注意這里不要帶on
          • listener:事件處理函數,事件發生時,會調用該監聽函數
          • useCapture:可選參數,是一個布爾值,默認是 false。學完 DOM 事件流后,我們再進一步學習
          <body>
              <button>傳統注冊事件</button>
          		<button>方法監聽注冊事件</button>
              <button>ie9 attachEvent</button>
              <script>
                  var btns = document.querySelectorAll('button');
                  // 1. 傳統方式注冊事件
                  btns[0].onclick = function() {
                      alert('hi');
                  }
                  btns[0].onclick = function() {
                          alert('hao a u');
                      }
                      // 2. 事件監聽注冊事件 addEventListener 
                      // (1) 里面的事件類型是字符串 所以加引號 而且不帶on
                      // (2) 同一個元素 同一個事件可以添加多個偵聽器(事件處理程序)
                  btns[1].addEventListener('click', function() {
                      alert(22);
                  })
                  btns[1].addEventListener('click', function() {
                          alert(33);
                      })
                      // 3. attachEvent ie9以前的版本支持
                  btns[2].attachEvent('onclick', function() {
                      alert(11);
                  })
              </script>
          </body>

          ②attachEvent事件監聽方式(兼容)

          • eventTarget.attachEvent()方法將指定的監聽器注冊到 eventTarget(目標對象) 上
          • 當該對象觸發指定的事件時,指定的回調函數就會被執行
          eventTarget.attachEvent(eventNameWithOn,callback)

          該方法接收兩個參數:

          • eventNameWithOn:事件類型字符串,比如 onclick 、onmouseover ,這里要帶 on
          • callback: 事件處理函數,當目標觸發事件時回調函數被調用
          • ie9以前的版本支持

          ③注冊事件兼容性解決方案

          兼容性處理的原則:首先照顧大多數瀏覽器,再處理特殊瀏覽器

           function addEventListener(element, eventName, fn) {
             // 判斷當前瀏覽器是否支持 addEventListener 方法
                if (element.addEventListener) {
                  element.addEventListener(eventName, fn);  // 第三個參數 默認是false
                } else if (element.attachEvent) {
                  element.attachEvent('on' + eventName, fn);
                } else {
                  // 相當于 element.onclick = fn;
                  element['on' + eventName] = fn;
           } 

          7.2、刪除事件(解綁事件)

          7.2.1、removeEventListener刪除事件方式

          eventTarget.removeEventListener(type,listener[,useCapture]);

          該方法接收三個參數:

          • type:事件類型字符串,比如click,mouseover,注意這里不要帶on
          • listener:事件處理函數,事件發生時,會調用該監聽函數
          • useCapture:可選參數,是一個布爾值,默認是 false。學完 DOM 事件流后,我們再進一步學習

          7.2.2、detachEvent刪除事件方式(兼容)

          eventTarget.detachEvent(eventNameWithOn,callback);

          該方法接收兩個參數:

          • eventNameWithOn:事件類型字符串,比如 onclick 、onmouseover ,這里要帶 on
          • callback: 事件處理函數,當目標觸發事件時回調函數被調用
          • ie9以前的版本支持

          7.2.3、傳統事件刪除方式

          eventTarget.onclick = null;

          事件刪除示例:

          <body>
              <div>1</div>
              <div>2</div>
              <div>3</div>
          		<script>
                  var divs = document.querySelectorAll('div');
                  divs[0].onclick = function() {
                      alert(11);
                      // 1. 傳統方式刪除事件
                      divs[0].onclick = null;
                  }
                  // 2.removeEventListener 刪除事件
                  divs[1].addEventListener('click',fn);   //里面的fn不需要調用加小括號
          
                  function fn(){
                      alert(22);
                      divs[1].removeEventListener('click',fn);
                  }
                  // 3.IE9 中的刪除事件方式
                  divs[2].attachEvent('onclick',fn1);
                  function fn1() {
                      alert(33);
                      divs[2].detachEvent('onclick',fn1);
                  }
              </script>
          </body>

          7.2.4、刪除事件兼容性解決方案

           function removeEventListener(element, eventName, fn) {
             // 判斷當前瀏覽器是否支持 removeEventListener 方法
                if (element.removeEventListener) {
                  element.removeEventListener(eventName, fn);  // 第三個參數 默認是false
                } else if (element.detachEvent) {
                  element.detachEvent('on' + eventName, fn);
                } else {
                  element['on' + eventName] = null;
           } 

          7.3、DOM事件流

          • 事件流描述的是從頁面中接收事件的順序
          • 事件發生時會在元素節點之間按照特定的順序傳播,這個傳播過程DOM事件流

          • 事件冒泡: IE 最早提出,事件開始時由最具體的元素接收,然后逐級向上傳播到到 DOM 最頂層節點的過程。
          • 事件捕獲: 網景最早提出,由 DOM 最頂層節點開始,然后逐級向下傳播到到最具體的元素接收的過程。

          加深理解

          我們向水里面扔一塊石頭,首先它會有一個下降的過程,這個過程就可以理解為從最頂層向事件發生的最具體元素(目標點)的捕獲過程;之后會產生泡泡,會在最低點( 最具體元素)之后漂浮到水面上,這個過程相當于事件冒泡。

          7.3.1、捕獲階段

          • document -> html -> body -> father -> son

          兩個盒子嵌套,一個父盒子一個子盒子,我們的需求是當點擊父盒子時彈出 father ,當點擊子盒子時彈出 son

          <body>
            	<div class="father">
                  <div class="son">son盒子</div>
              </div>
              <script>
                  // dom 事件流 三個階段
                  // 1. JS 代碼中只能執行捕獲或者冒泡其中的一個階段。
                  // 2. onclick 和 attachEvent(ie) 只能得到冒泡階段。
                  // 3. 捕獲階段 如果addEventListener 第三個參數是 true 那么則處于捕獲階段  document -> html -> body -> father -> son
                  var son = document.querySelector('.son');
                  son.addEventListener('click', function() {
                       alert('son');
                  }, true);
                  var father = document.querySelector('.father');
                  father.addEventListener('click', function() {
                      alert('father');
                  }, true);
              </script>
          </body>

          但是因為DOM流的影響,我們點擊子盒子,會先彈出 father,之后再彈出 son

          這是因為捕獲階段由 DOM 最頂層節點開始,然后逐級向下傳播到到最具體的元素接收

          • document -> html -> body -> father -> son
          • 先看 document 的事件,沒有;再看 html 的事件,沒有;再看 body 的事件,沒有;再看 father 的事件,有就先執行;再看 son 的事件,再執行。

          7.3.2、冒泡階段

          • son -> father ->body -> html -> document
          <body>
            	<div class="father">
                  <div class="son">son盒子</div>
              </div>
              <script>
          		// 4. 冒泡階段 如果addEventListener 第三個參數是 false 或者 省略 那么則處于冒泡階段  son -> father ->body -> html -> document
                  var son = document.querySelector('.son');
                  son.addEventListener('click', function() {
                      alert('son');
                  }, false);
                  var father = document.querySelector('.father');
                  father.addEventListener('click', function() {
                      alert('father');
                  }, false);
                  document.addEventListener('click', function() {
                      alert('document');
                  })
              </script>
          </body>

          我們點擊子盒子,會彈出 son、father、document

          這是因為冒泡階段開始時由最具體的元素接收,然后逐級向上傳播到到 DOM 最頂層節點

          • son -> father ->body -> html -> document

          7.3.3、小結

          • JS 代碼中只能執行捕獲或者冒泡其中的一個階段
          • onclickattachEvent只能得到冒泡階段
          • addEventListener(type,listener[,useCapture])第三個參數如果是 true,表示在事件捕獲階段調用事件處理程序;如果是 false (不寫默認就是false),表示在事件冒泡階段調用事件處理程序
          • 實際開發中我們很少使用事件捕獲,我們更關注事件冒泡
          • 有些事件是沒有冒泡的,比如 onblur、onfocus、onmouseenter、onmouseleave

          7.4、事件對象

          eventTarget.onclick = function(event) {
             // 這個 event 就是事件對象,我們還喜歡的寫成 e 或者 evt 
          } 
          eventTarget.addEventListener('click', function(event) {
             // 這個 event 就是事件對象,我們還喜歡的寫成 e 或者 evt  
          })
          • 官方解釋:event 對象代表事件的狀態,比如鍵盤按鍵的狀態、鼠標的位置、鼠標按鈕的狀態

          簡單理解:

          事件發生后,跟事件相關的一系列信息數據的集合都放到這個對象里面

          這個對象就是事件對象 event,它有很多屬性和方法,比如“

          誰綁定了這個事件

          鼠標觸發事件的話,會得到鼠標的相關信息,如鼠標位置

          鍵盤觸發事件的話,會得到鍵盤的相關信息,如按了哪個鍵

          • 這個 event 是個形參,系統幫我們設定為事件對象,不需要傳遞實參過去
          • 當我們注冊事件時, event 對象就會被系統自動創建,并依次傳遞給事件監聽器(事件處理函數)
          <body>
              <div>123</div>
          		<script>
                  // 事件對象
                  var div = document.querySelector('div');
                  div.onclick = function(e) {
                          // console.log(e);
                          // console.log(window.event);
                          // e = e || window.event;
                          console.log(e);
          
          
                      }
                  // 1. event 就是一個事件對象 寫到我們偵聽函數的 小括號里面 當形參來看
                  // 2. 事件對象只有有了事件才會存在,它是系統給我們自動創建的,不需要我們傳遞參數
                  // 3. 事件對象 是 我們事件的一系列相關數據的集合 跟事件相關的 比如鼠標點擊里面就包含了鼠標的相關信息,鼠標坐標啊,如果是鍵盤事件里面就包含的鍵盤事件的信息 比如 判斷用戶按下了那個鍵
                  // 4. 這個事件對象我們可以自己命名 比如 event 、 evt、 e
                  // 5. 事件對象也有兼容性問題 ie678 通過 window.event 兼容性的寫法  e = e || window.event;
              </script>
          </body>

          7.4.1、事件對象的兼容性方案

          事件對象本身的獲取存在兼容問題:

          1. 標準瀏覽器中是瀏覽器給方法傳遞的參數,只需要定義形參 e 就可以獲取到。
          2. 在 IE6~8 中,瀏覽器不會給方法傳遞參數,如果需要的話,需要到 window.event 中獲取查找

          解決:

          e = e || window.event;

          7.4.2、事件對象的常見屬性和方法

          事件對象屬性方法

          說明

          e.target

          返回觸發事件的對象 標準

          e.srcElement

          返回觸發事件的對象 非標準 ie6-8使用

          e.type

          返回事件的類型 比如click mouseover 不帶on

          e.cancelBubble

          該屬性阻止冒泡,非標準,ie6-8使用

          e.returnValue

          該屬性阻止默認行為 非標準,ie6-8使用

          e.preventDefault()

          該方法阻止默認行為 標準 比如不讓鏈接跳轉

          e.stopPropagation()

          阻止冒泡 標準

          e.targetthis 的區別:

          • this 是事件綁定的元素, 這個函數的調用者(綁定這個事件的元素)
          • e.target 是事件觸發的元素。

          7.5、事件對象阻止默認行為

          <body>
              <div>123</div>
          		<a href="http://www.baidu.com">百度</a>
              <form action="http://www.baidu.com">
                  <input type="submit" value="提交" name="sub">
              </form>
              <script>
                  // 常見事件對象的屬性和方法
                  // 1. 返回事件類型
                  var div = document.querySelector('div');
                  div.addEventListener('click', fn);
                  div.addEventListener('mouseover', fn);
                  div.addEventListener('mouseout', fn);
          
                  function fn(e) {
                      console.log(e.type);
          
                  }
                  // 2. 阻止默認行為(事件) 讓鏈接不跳轉 或者讓提交按鈕不提交
                  var a = document.querySelector('a');
                  a.addEventListener('click', function(e) {
                          e.preventDefault(); //  dom 標準寫法
                      })
                      // 3. 傳統的注冊方式
                  a.onclick = function(e) {
                      // 普通瀏覽器 e.preventDefault();  方法
                      // e.preventDefault();
                      // 低版本瀏覽器 ie678  returnValue  屬性
                      // e.returnValue;
                      // 我們可以利用return false 也能阻止默認行為 沒有兼容性問題 特點: return 后面的代碼不執行了, 而且只限于傳統的注冊方式
                      return false;
                      alert(11);
                  }
              </script>
          </body>

          7.6、阻止事件冒泡

          事件冒泡:開始時由最具體的元素接收,然后逐級向上傳播到到 DOM 最頂層節點

          事件冒泡本身的特性,會帶來的壞處,也會帶來的好處,需要我們靈活掌握。

          • 標準寫法
          e.stopPropagation();
          • 非標準寫法: IE6-8 利用對象事件 cancelBubble屬性
          e.cancelBubble = true;
          <body>
              <div class="father">
                  <div class="son">son兒子</div>
              </div>
              <script>
                  // 常見事件對象的屬性和方法
                  // 阻止冒泡  dom 推薦的標準 stopPropagation() 
                  var son = document.querySelector('.son');
                  son.addEventListener('click', function(e) {
                      alert('son');
                      e.stopPropagation(); // stop 停止  Propagation 傳播
                      e.cancelBubble = true; // 非標準 cancel 取消 bubble 泡泡
                  }, false);
          
                  var father = document.querySelector('.father');
                  father.addEventListener('click', function() {
                      alert('father');
                  }, false);
                  document.addEventListener('click', function() {
                      alert('document');
                  })
          		</script>
          </body>

          7.6.1、阻止事件冒泡的兼容性解決方案

          if(e && e.stopPropagation){
            e.stopPropagation();
            }else{
                window.event.cancelBubble = true;
            }

          4.4.4 e.target 與 this

          e.target 與 this 的區別

          • this是事件綁定的元素,這個函數的調用者(綁定這個事件的元素)
          • e.target是事件觸發的元素
          <body>
              <div>123</div>
          		<ul>
                  <li>abc</li>
                  <li>abc</li>
                  <li>abc</li>
              </ul>
              <script>
                  // 常見事件對象的屬性和方法
                  // 1. e.target 返回的是觸發事件的對象(元素)  this 返回的是綁定事件的對象(元素)
                  // 區別 : e.target 點擊了那個元素,就返回那個元素 this 那個元素綁定了這個點擊事件,那么就返回誰
                  var div = document.querySelector('div');
                  div.addEventListener('click', function(e) {
                      console.log(e.target);
                      console.log(this);
          
                  })
                  var ul = document.querySelector('ul');
                  ul.addEventListener('click', function(e) {
                          // 我們給ul 綁定了事件  那么this 就指向ul  
                          console.log(this);
                          console.log(e.currentTarget);
          
                          // e.target 指向我們點擊的那個對象 誰觸發了這個事件 我們點擊的是li e.target 指向的就是li
                          console.log(e.target);
          
                      })
                      // 了解兼容性
                      // div.onclick = function(e) {
                      //     e = e || window.event;
                      //     var target = e.target || e.srcElement;
                      //     console.log(target);
          
                  // }
                  // 2. 了解 跟 this 有個非常相似的屬性 currentTarget  ie678不認識
              </script>
          </body>

          4.4.5 事件對象的兼容性

          事件對象本身的獲取存在兼容問題:

          • 標準瀏覽器中瀏覽器是給方法傳遞的參數,只需定義形參e就可以獲取到
          • 在 IE6 -> 8 中,瀏覽器不會給方法傳遞參數,如果需要的話,需要到window.event中獲取查找

          解決方案

          • e = e || window.event
          <body>
              <div>123</div>
          		<script>
                  // 事件對象
                  var div = document.querySelector('div');
                  div.onclick = function(e) {
                          // e = e || window.event;
                          console.log(e);
          				// 事件對象也有兼容性問題 ie678 通過 window.event 兼容性的寫法  e = e || window.event;
          
                      }
          </body>

          7.7、事件委托

          • 事件委托也稱為事件代理,在 jQuery 里面稱為事件委派
          • 事件委托的原理
          • 不是每個子節點單獨設置事件監聽器,而是事件監聽器設置在其父節點上,然后利用冒泡原理影響設置每個子節點
          <body>
              <ul>
            	<li>知否知否,點我應有彈框在手!</li>
                  <li>知否知否,點我應有彈框在手!</li>
                  <li>知否知否,點我應有彈框在手!</li>
                  <li>知否知否,點我應有彈框在手!</li>
                  <li>知否知否,點我應有彈框在手!</li>
              </ul>
              <script>
                  // 事件委托的核心原理:給父節點添加偵聽器, 利用事件冒泡影響每一個子節點
                  var ul = document.querySelector('ul');
                  ul.addEventListener('click', function(e) {
                      // alert('知否知否,點我應有彈框在手!');
                      // e.target 這個可以得到我們點擊的對象
                      e.target.style.backgroundColor = 'pink';
                      // 點了誰,就讓誰的style里面的backgroundColor顏色變為pink
                  })
              </script>
          </body>

          以上案例:給 ul 注冊點擊事件,然后利用事件對象的 target 來找到當前點擊的 li,因為點擊 li,事件會冒泡到 ul 上, ul 有注冊事件,就會觸發事件監聽器。

          7.8、常見的鼠標事件

          鼠標事件

          觸發條件

          onclick

          鼠標點擊左鍵觸發

          onmouseover

          鼠標經過觸發

          onmouseout

          鼠標離開觸發

          onfocus

          獲得鼠標焦點觸發

          onblur

          失去鼠標焦點觸發

          onmousemove

          鼠標移動觸發

          onmouseup

          鼠標彈起觸發

          onmousedown

          鼠標按下觸發

          7.8.1、禁止鼠標右鍵與鼠標選中

          • contextmenu主要控制應該何時顯示上下文菜單,主要用于程序員取消默認的上下文菜單
          • selectstart 禁止鼠標選中
          <body>
              <h1>我是一段不愿意分享的文字</h1>
          	  <script>
                  // 1. contextmenu 我們可以禁用右鍵菜單
                  document.addEventListener('contextmenu', function(e) {
                          e.preventDefault(); // 阻止默認行為
                      })
                      // 2. 禁止選中文字 selectstart
                  document.addEventListener('selectstart', function(e) {
                      e.preventDefault();
          
                  })
              </script>
          </body>

          7.8.2、鼠標事件對象

          • event對象代表事件的狀態,跟事件相關的一系列信息的集合
          • 現階段我們主要是用鼠標事件對象 MouseEvent 和鍵盤事件對象 KeyboardEvent。

          鼠標事件對象

          說明

          e.clientX

          返回鼠標相對于瀏覽器窗口可視區的X坐標

          e.clientY

          返回鼠標相對于瀏覽器窗口可視區的Y坐標

          e.pageX(重點)

          返回鼠標相對于文檔頁面的X坐標 IE9+ 支持

          e.pageY(重點)

          返回鼠標相對于文檔頁面的Y坐標 IE9+ 支持

          e.screenX

          返回鼠標相對于電腦屏幕的X坐標

          e.screenY

          返回鼠標相對于電腦屏幕的Y坐標

          示例:

          <body>
              <script>
                  // 鼠標事件對象 MouseEvent
                  document.addEventListener('click', function(e) {
                      // 1. client 鼠標在可視區的x和y坐標
                      console.log(e.clientX);
                      console.log(e.clientY);
                      console.log('---------------------');
          
                      // 2. page 鼠標在頁面文檔的x和y坐標
                      console.log(e.pageX);
                      console.log(e.pageY);
                      console.log('---------------------');
          
                      // 3. screen 鼠標在電腦屏幕的x和y坐標
                      console.log(e.screenX);
                      console.log(e.screenY);
          
                  })
              </script>
          </body>

          7.9、常用的鍵盤事件

          鍵盤事件

          觸發條件

          onkeyup

          某個鍵盤按鍵被松開時觸發

          onkeydown

          某個鍵盤按鍵被按下時觸發

          onkeypress

          某個鍵盤按鍵被按下時觸發,但是它不識別功能鍵,比如 ctrl shift 箭頭等

          • 如果使用addEventListener 不需要加 on
          • onkeypress 和前面2個的區別是,它不識別功能鍵,比如左右箭頭,shift 等
          • 三個事件的執行順序是: keydown – keypress — keyup
          <body>
              <script>
                  // 常用的鍵盤事件
                  //1. keyup 按鍵彈起的時候觸發 
                  // document.onkeyup = function() {
                  //         console.log('我彈起了');
          
                  //     }
                  document.addEventListener('keyup', function() {
                      console.log('我彈起了');
                  })
          
                  //3. keypress 按鍵按下的時候觸發  不能識別功能鍵 比如 ctrl shift 左右箭頭啊
                  document.addEventListener('keypress', function() {
                          console.log('我按下了press');
                      })
                      //2. keydown 按鍵按下的時候觸發  能識別功能鍵 比如 ctrl shift 左右箭頭啊
                  document.addEventListener('keydown', function() {
                          console.log('我按下了down');
                      })
                      // 4. 三個事件的執行順序  keydown -- keypress -- keyup
              </script>
          </body>

          7.9.1、鍵盤對象屬性

          鍵盤事件對象 屬性

          說明

          keyCode

          返回該值的ASCII值

          • onkeydownonkeyup 不區分字母大小寫,onkeypress 區分字母大小寫。
          • 在我們實際開發中,我們更多的使用keydown和keyup, 它能識別所有的鍵(包括功能鍵)
          • Keypress 不識別功能鍵,但是keyCode屬性能區分大小寫,返回不同的ASCII值

          Hi,大家好,我是希留。

          上篇文章帶大家認識了Vue,以及初始化了一個Vue工程的項目。今天繼續介紹一下Vue的一些基礎用法。

          目錄

          • 一、Vue項目的目錄結構
          • 二、基本語法
            • 1.基本數據渲染和指令
            • 2.雙向數據綁定
            • 3.修飾符
            • 4.縮寫
            • 5.計算屬性
            • 6.偵聽屬性
            • 7.條件渲染
            • 8.列表渲染
            • 9.過濾器


          一、Vue項目的目錄結構

          使用開發工具(idea、webstorm、vscode…)打開Vue項目。由于后端用idea的比較多,這里以idea為例(需要安裝Vue.js插件),打開后目錄結構如下:

          如上圖所示,Vue的目錄結構主要由以下部分組成:

          my-vue-project
              ├--- build
              ├--- config
              ├--- node_modules
              └--- src
                   ├--- assets
                   └--- components
                   ├--- router
                   └--- App.vue
                   ├--- main.js
              ├--- static
              └--- package.json
              
          

          build :構建相關,用來存放項目構建腳本,對項目進行編譯打包
          config :配置相關,用來存放基本的配置文件
          node_modules :用來存放的是項目的所有依賴
          src :存放項目的源碼
          assets :主題 字體等靜態資源
          components :全局公用組件
          router :路由
          App.vue :入口頁面
          main.js :入口 加載組件 初始化等
          static :第三方不打包資源
          package.json :定義了項目的所有依賴,包括開發時依賴和發布時依賴


          二、基本語法

          1、基本數據渲染和指令

          你看到的 v-bind 特性被稱為指令。指令帶有前綴 v-

          除了使用插值表達式{{}}進行數據渲染,也可以使用 v-bind指令,它的簡寫的形式就是一個冒號(:)

          代碼如下:

          <template>
            <div class="hello">
              <!-- 如果要將模型數據綁定在html屬性中,則使用 v-bind 指令
               此時title中顯示的是模型數據-->
              <h1 v-bind:title="message">
                {{content}}
              </h1>
          
              <!-- v-bind 指令的簡寫形式:冒號(:) -->
              <h1 :title="message">
                {{content}}
              </h1>
          
            </div>
          </template>
          
          <script>
          export default {
            name: 'HelloWorld',
            data () {
              return {
                content: '我是標題',
                message: '頁面加載于 ' + new Date().toLocaleString()
              }
            }
          }
          </script>
          

          運行項目,瀏覽器打開地址,可看到頁面正常渲染出來數據。

          2、雙向數據綁定

          雙向數據綁定和單向數據綁定;

          使用 v-model 進行雙向數據綁定:

           <template>
            <div class="hello">
          	...
          		
              !-- v-bind:value只能進行單向的數據渲染 -->
              <input type="text" v-bind:value="searchMap.keyWord">
          
              <!-- v-model 可以進行雙向的數據綁定  -->
              <input type="text" v-model="searchMap.keyWord">
          
              <p>您要查詢的是:{{searchMap.keyWord}}</p>
            </div>
          </template>
            
          
          <script>
          export default {
            name: 'HelloWorld',
            data () {
              return {
                ...
                searchMap: {
                  keyWord: '希留'
                }
              }
            }
          }
          </script>
          

          使用v-bind綁定的數據是單向的,并不會隨著輸入值的改變而改變


          使用 v-model 指令 才能把數據雙向綁定

          3、修飾符

          修飾符 (Modifiers) 是以半角句號(.)指明的特殊后綴,用于指出一個指令應該以特殊方式綁定。

          例如,.prevent 修飾符告訴 v-on 指令對于觸發的事件調用 event.preventDefault()方法
          (即阻止事件原本的默認行為)

          示例代碼如下:

          <template>
            <div class="hello">
          	...
          		
              <!-- 修飾符用于指出一個指令應該以特殊方式綁定。
               這里的 .prevent 修飾符告訴 v-on 指令對于觸發的事件調用js的 event.preventDefault()方法
               (該方法表示阻止表單提交的默認行為) -->
              <form action="save" v-on:submit.prevent="onSubmit">
                <label for="username">
                  <input type="text" id="username" v-model="user.username">
                  <button type="submit">保存</button>
                </label>
              </form>
            </div>
          </template>
            
          
          <script>
          export default {
            name: 'HelloWorld',
            data () {
              return {
                ...
                user: {}
              }
            },
            methods: {
              onSubmit() {
                if (this.user.username) {
                  console.log('提交表單')
                } else {
                  alert('請輸入用戶名')
                }
              }
            }
          }
          </script>
          

          當在方法上加了.preven修飾符后,點擊保存只會執行onSubmit方法。若沒加,則會先執行onSubmit方法在執行action里的方法。

          4、縮寫

          v- 前綴作為一種視覺提示,用來識別模板中 Vue 特定的 attribute。Vue 為 v-bind 和 v-on 這兩個最常用的指令,提供了特定簡寫,它們看起來可能與普通的 HTML 略有不同,但 : 與 @ 對于 attribute 名來說都是合法字符,在所有支持 Vue 的瀏覽器都能被正確地解析。而且,它們不會出現在最終渲染的標記中。

          v-bind 縮寫
          <!-- 完整語法 -->
          <a v-bind:href="url">...</a>
          
          <!-- 縮寫 -->
          <a :href="url">...</a>
          
          <!-- 動態參數的縮寫 (2.6.0+) -->
          <a :[key]="url"> ... </a>
          
          v-on 縮寫
          <!-- 完整語法 -->
          <a v-on:click="doSomething">...</a>
          
          <!-- 縮寫 -->
          <a @click="doSomething">...</a>
          
          <!-- 動態參數的縮寫 (2.6.0+) -->
          <a @[event]="doSomething"> ... </a>
          

          5、計算屬性

          模板內的表達式非常便利,但是設計它們的初衷是用于簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。

          所以Vue提供了計算屬性。對于任何復雜邏輯,你都應當使用計算屬性。且計算屬性是基于它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們才會重新求值。

          示例代碼如下:

          <template>
            <div class="hello">
          	...
          	<p>原始值: "{{ message }}"</p>
              <!-- 1、插值數據綁定中使用表達式 -->
              <p>反轉消息: {{ message.split('').reverse().join('') }}</p>
              <!-- 2、使用計算屬性 -->
              <p>反轉消息: "{{ reversedMessage }}"</p>
            </div>
          </template>
            
          
          <script>
          export default {
            name: 'HelloWorld',
            data () {
              return {
                ...
                message: '上海自來水來自海上 haha'
              }
            },
            computed: {
                reversedMessage () {
                    return this.message.split('').reverse().join('')
                }
            }
          }
          </script>
          

          6、偵聽屬性

          雖然計算屬性在大多數情況下更合適,但有時也需要一個自定義的偵聽器。這就是為什么 Vue 通過 watch 選項提供了一個更通用的方法,來響應數據的變化。當需要在數據變化時執行異步或開銷較大的操作時,這個方式是最有用的。

          示例代碼如下:

          <template>
            <div class="hello">
          	...
          	<p>
                Ask a yes/no question:
                <input v-model="question">
                <button @click="getAnswer()">提問</button>
              </p>
              <p>{{ answer }}</p>
            </div>
          </template>
            
          
          <script>
          import axios from 'axios'
          export default {
            name: 'HelloWorld',
            data () {
              return {
                ...
                message: '上海自來水來自海上 haha'
              }
            },
            methods: {
          
              getAnswer() {
                var vm = this
                axios.get('https://yesno.wtf/api')
                  .then(function (response) {
                    vm.answer = _.capitalize(response.data.answer)
                  })
                  .catch(function (error) {
                    vm.answer = '網找不到API接口. ' + error
                  })
              }
            },
            watch: {
              // 如果 `question` 發生改變,這個函數就會運行
              question: function (newQuestion, oldQuestion) {
                this.answer = '等待您停止輸入...'
                this.getAnswer()
              }
            }
          }
          </script>
          

          在這個示例中,使用 watch 選項允許我們執行異步操作 (訪問一個 API),限制我們執行該操作的頻率,并在我們得到最終結果前,設置中間狀態。這些都是計算屬性無法做到的。

          7、條件渲染

          v-if 指令用于條件性地渲染一塊內容。這塊內容只會在指令的表達式返回 truthy 值的時候被渲染。
          v-if 是一個指令,所以必須將它添加到一個元素上。v-else-if,充當 v-if 的“else-if 塊”,可以連續使用;
          v-else 指令來表示 v-if 的“else 塊”,必須緊跟在帶 v-if 或者 v-else-if 的元素的后面,否則它將不會被識別。

          另一個用于根據條件展示元素的選項是 v-show 指令。用法大致一樣,不同的是帶有 v-show 的元素始終會被渲染并保留在 DOM 中。v-show 只是簡單地切換元素的 CSS property display。v-show 不支持 元素,也不支持 v-else。

          兩者對比:

          • v-if 是“真正”的條件渲染,因為它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷毀和重建。
          • v-if 也是惰性的:如果在初始渲染時條件為假,則什么也不做——直到條件第一次變為真時,才會開始渲染條件塊。
          • 相比之下,v-show 就簡單得多——不管初始條件是什么,元素總是會被渲染,并且只是簡單地基于 CSS 進行切換。
          • 一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運行時條件很少改變,則使用 v-if 較好。

          8、列表渲染

          我們可以用 v-for 指令基于一個數組來渲染一個列表。v-for 指令需要使用 item in items 形式的特殊語法,其中 items 是源數據數組,而 item 則是被迭代的數組元素的別名。

          為了給 Vue 一個提示,以便它能跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每項提供一個唯一 key attribute,建議盡可能在使用 v-for 時提供 key attribute,因為它是 Vue 識別節點的一個通用機制,key 并不僅與 v-for 特別關聯。

          示例代碼如下:

          <!-- 1、遍歷數據列表 -->
              <table border="1">   
                <tr v-for="(item, index) in userList" :key="item.id">
                  <td>{{index}}</td>
                  <td>{{item.id}}</td>
                  <td>{{item.username}}</td>
                  <td>{{item.age}}</td>
                </tr>
              </table>
          <script>
          
          export default {
            name: 'HelloWorld',
            data () {
              return {
                ...
                userList: [
                  { id: 1, username: 'helen', age: 18 },
                  { id: 2, username: 'peter', age: 28 },
                  { id: 3, username: 'andy', age: 38 }
                ]
              }
            },
          
          }
          </script>
          

          9、過濾器

          過濾器常用來處理文本格式化的操作。過濾器可以用在兩個地方:雙花括號插值和 v-bind 表達式

          過濾器應該被添加在 JavaScript 表達式的尾部,由“管道”符號指示

          ```javascript
          <!-- 1、遍歷數據列表 -->
              <table border="1">   
                <tr v-for="(item, index) in userList" :key="item.id">
                  <td>{{index}}</td>
                  <td>{{item.id}}</td>
                  <td>{{item.username}}</td>
                  <td>{{item.age}}</td>
                  <!-- | 管道符號:表示使用后面的過濾器處理前面的數據 -->
                  <td>{{item.gender | genderFilter}}</td>
                </tr>
              </table>
          <script>
          
          export default {
            name: 'HelloWorld',
            data () {
              return {
                ...
                userList: [
                  { id: 1, username: 'helen', age: 18 ,gender: 1},
                  { id: 2, username: 'peter', age: 28 ,gender: 1},
                  { id: 3, username: 'andy', age: 38 ,gender: 0}
                ]
              }
            },
            // filters 定義局部過濾器,只可以在當前vue實例中使用
            filters: {
                genderFilter(gender) {
                  return gender === 1 ? '男' : '女'
                }
            }
          }
          </script>
          


          結語

          好了,以上就是今天要講的內容,本文簡單介紹了Vue的基礎使用,相信大家對Vue有了進一步的認識了,趕快動手操作起來吧,gogogo。

          內容是《Web前端開發之Javascript視頻》的課件,請配合大師哥《Javascript》視頻課程學習。

          DOM(Document Object Model,文檔對象模型):

          DOM是一個使程序和腳本能夠動態地訪問和更新文檔的內容、結構以及樣式,并獨立于平臺和語言的接口;其定義了訪問和處理文檔的標準方法;是針對HTML和XML文檔的一個API;

          DOM描繪了一個層次化的節點樹,允許開發者添加、移除和修改頁面的某一部分,從而重構整個文檔。

          DOM脫胎于Netscape及微軟創始的DHTML,但現在已經成為表現和操作頁面標記的真正的跨平臺、語言中立的方式;

          DOM與具體的編程語言無關,可以在C、Javascript、ActionScript、Java等語言中實現;

          DOM標準的目標是讓“任何一種程序設計語言”能操控使用“任何一種標記語言”編寫出的“任何一份文檔”;

          DOM 級別:

          DOM Level 1:由兩個模塊組成:DOM核心(DOM Core)和DOM HTML;專注于 HTML 和 XML 文檔模型,它含有文檔導航和處理功能;

          DOM Level 2:對 DOM1級做了擴展,添加了樣式表對象模型,并定義了操作附于文檔之上的樣式信息的功能性;同時還定義了一個事件模型,并提供了對 XML 命名空間的支持;

          DOM Level 3:對DOM2級做了擴展,規定了內容模型 (DTD 和 Schemas) 和文檔驗證,同時規定了文檔加載和保存、文檔查看、文檔格式化和關鍵事件;

          DOM Level 0:確切來說,不存在DOM0級,因為它不是 W3C 規范,而僅僅是對在 Netscape Navigator 3.0 和 Microsoft Internet Explorer 3.0 中的等價功能性的一種定義,實際上指的就是DHTML;

          1998年10月 DOM1級規范成為W3C的標準,為基本的文檔結構及查詢提供了接口;

          目前主流的瀏覽器都已實現了DOM1、基本實現了DOM2和3;

          DOM組成:

          • Core DOM:定義了一套標準的針對任何結構化文檔的對象,即用于XML和HTML的共用接口;
          • HTML DOM:在 DOM 核心的基礎上加以擴展,定義了一套標準的針對HTML文檔的接口對象;

          XML:

          XML 指可擴展標記語言(EXtensible Markup Language),它是一種標記語言,類似于HTML,它的設計宗旨是傳輸數據,而非顯示數據;

          XML 標簽沒有被預定義,需要開發者自定義標簽;

          XML 被設計為具有自我描述性,是W3C的推薦標準;

          XML 的用途:把數據從 HTML 分離、簡化數據共享、簡化數據傳輸、簡化平臺的變更;

          DOM樹:

          DOM可以將任何HTML或XML文檔描繪成一個由多層節點構成的結構;

          如HTML:

          <html>
          <head>
              <title>零點網絡</title>
          </head>
          <body>
              <h1>零點程序員</h1>
              <p>zeronetwork <a href="#">王唯</a></p>
          </body>
          </html>

          樹狀圖:


          樹狀圖

          每一個標簽是是文檔的一個節點,它表示一個Node對象;

          節點分為幾種不同的類型,每種類型分別表示文檔中不同的信息或標記;

          每種節點都擁有各自的特點、數據和方法,并且也與其他節點存在某種關系;

          節點之間的關系構成了層次,而所有頁面標記則表現為一個以特定節點為根節點的樹形結構;

          文檔節點(Document):是每個文檔的根節點;文檔節點只有一個子節點,即<html>元素,也稱為文檔元素,它是文檔最外層元素,其他所有元素都包含在文檔元素中;每個文檔只能有一個文檔元素;在XML中,沒有預定義的文檔元素,因此任何元素都可能成為文檔元素;

          Node接口:

          每一段標記都可以通過樹中的一個節點來表示,這個節點稱為Node;

          DOM1級定義一個Node接口,其用于抽象地表示文檔中一個獨立的部分;

          HTML元素通過元素節點表示,特性(attribute)通過特性節點表示,文檔類型通過文檔類型節點表示,注釋通過注釋節點表示,共12種節點類型,這些類型都繼承自一個基類型Node類型,因此所有節點類型都共享著相同的基本屬性和方法;

          Node類型圖:


          Node類型圖

          Document和Element類型與HTMLDocument和HTMLElement類型之間是有嚴格的區分的;Document類型代表一個HTML或XML文檔,而HTMLDocment只是代表一個HTML文檔,XMLDocument代表是XML文檔;Element類型代表該文檔中的一個元素,而HTMLElement只是HTML文檔中的元素,不是XMLDocument中的元素;

          HTMLElement的很多子類型代表HTML元素的具體類型,每個類型具有多個Javascript屬性,這些屬性對應具體的元素或元素組的HTML元素特性;這些具體的元素類也定義了額外的屬性和方法,它們并不是簡單的映射HTML元素及HTML元素特性;

          每個節點都有一個nodeType屬性,用于表明節點的類型;

          節點類型由在Node類型中定義的下列12個常數值來表示;

          • Node.ELEMENT_NODE(1)
          • Node.ATTRIBUTE_NODE(2)
          • Node.TEXT_NODE(3)
          • Node.CDATA_SECTION_NODE(4)
          • Node.ENTITY_REFERENCE_NODE(5)
          • Node.ENTITY_NODE(6)
          • Node.PROCESSING_INSTRUCTION_NODE(7)
          • Node.COMMENT_NODE(8)
          • Node.DOCUMENT_NODE(9)
          • Node.DOCUMENT_TYPE_NODE(10)
          • Node.DOCUMENT_FRAGEMENT_NODE(11)
          • Node.NOTATION_NODE(12)
          <div id="mydiv">零點程序員</div>
          <script>
          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.nodeType);  // 1
          console.log(Node.ELEMENT_NODE);  // 1
          if(mydiv.nodeType == Node.ELEMENT_NODE)
              alert("mydiv is an element");
          // IE8以下不支持,可以如此判斷
          if(mydiv.nodeType == 1)
          alert("IE8: mydiv is an element");
          </script>

          Node屬性:

          nodeName : String,節點的名字,取決于節點的類型;

          nodeValue : String,節點的值,取決于節點的類型;

          nodeType : Number,節點的類型常數值之一;

          注:對于元素節點,nodeName保存的始終是元素的標簽名,而nodeValue的值為null;因此在使用nodeName及nodeValue時,最好先檢測一下節點的類型;

          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.nodeType);  // 1
          console.log(mydiv.nodeName);  // DIV
          console.log(mydiv.nodeValue);  // null
          if(mydiv.nodeType == Node.ELEMENT_NODE){
              console.log(mydiv.nodeType);  // 1
              console.log(mydiv.nodeName);  // DIV
          }
          var txt = mydiv.firstChild;
          if(txt.nodeType == Node.TEXT_NODE){
              console.log("nodeType:" + txt.nodeType);  // 3
              console.log("nodeName:" + txt.nodeName);  // #text
              console.log("nodeValue:" + txt.nodeValue);  // 零點程序員
          }

          文檔中所有節點之間都存在著各種關系,理清這些關系是非常重要的;

          childNodes屬性:

          返回NodeList類型的所有子節點集合;

          NodeList是一種類數組的對象,用于保存一組有序的節點,可以通過方括號來訪問保存在其中的節點,其擁有length 屬性;

          <div id="mydiv">
              <h2>零點程序員</h2>
              <h3>zeronetwork</h3>
              <div>從事IT教育,開展<a href="#">Web前端</a>、后端開發教育</div>
          </div>
          <script>
          var mydiv = document.getElementById("mydiv");
          // dom.html:16 NodeList(7) [text, h2, text, h3, text, div, text]
          console.log(mydiv.childNodes); 
          console.log(mydiv.childNodes.length);  // 7
          var firstChild = mydiv.childNodes[1];  // 如果是0,即#text節點,此處是個空格
          console.log(firstChild);    // <h2>零點程序員</h2>
          console.log(firstChild.nodeName);  // H2
          var secondChild = mydiv.childNodes.item(3);
          console.log(secondChild);  // <h3>zeronetwork</h3>
          </script>

          注:mydiv有三個子節點,但length卻是7個,多出4個text節點,此text節點,實際上是代碼中的換行或空格,如果把html代碼刪除換行或空格,length結果就是3;

          NodeList對象的特點:是基于DOM結構動態執行查詢的結果,因此DOM結構的變化能夠自動反映在NodeList對象中;

          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.childNodes.length); // 7
          var p = document.createElement("p");
          p.innerText = "大師哥王唯";
          mydiv.appendChild(p);
          console.log(mydiv.childNodes.length);  // 8

          在實際應用中,有可能需要將NodeList對象轉換成數組:

          var mydiv = document.getElementById("mydiv");
          var arrNodes = Array.prototype.slice.call(mydiv.childNodes,0);
          console.log(arrNodes);

          children屬性:

          IE與其他瀏覽器對文本節點中空白符的解釋不一致,導致了children屬性出現;這個屬性是HTMLCollection的實例,其中只包含元素的子節點中那些也是元素的節點,即children列表中只包含Element元素;

          children并不是標準屬性,但所有的瀏覽器都實現了該屬性;

          var myList = document.getElementById("myList");
          var lis = myList.children;
          console.log(lis);
          console.log(lis.length);

          Text和Comment節點沒有children屬性;

          HTMLCollection類型:

          是一個接口,表示HTML 元素的集合,與NodeList非常類似,也是個類數組對象;元素在 HTMLCollection 對象中的順序與它們在文檔源代碼中出現的順序一樣;

          HTMLCollection類型的屬性和方法:

          item()方法:返回 HTMLCollection 中指定索引的元素;

          length屬性:返回 HTMLCollection 中元素的數量;

          namedItem()方法:返回 HTMLCollection 中指定 ID 或 name 屬性的元素;

          console.log(lis.item(0));
          console.log(lis[0]);  // 一般用這個代替
          console.log(lis.namedItem("myli"));
          console.log(lis["myli"]);  // 一般用這個代替

          與NodeList類型一樣,HTMLCollection對象也是實時動態的;

          parentNode屬性:

          指向節點的父節點;

          一個元素節點的父節點可能是一個元素(Element )節點,也可能是一個文檔(Document )節點,或者是個文檔碎片(DocumentFragment)節點;

          對于Document、DocumentFragment和Attr對象來說,其parentNode屬性為null,因為它們沒有父節點;

          另外,如果當前節點剛剛被建立,還沒有被插入到DOM樹中,則該節點的parentNode屬性也返回null;

          var elt = document.getElementById("elt");
          if(elt.parentNode){
              elt.parentNode.removeChild(elt);
          }

          parentElement屬性:

          返回當前節點的父元素節點,如果該元素沒有父節點,或者父節點不是一個 DOM 元素,則返回 null;parentElement是一個DOM元素對象(HTMLElement對象);

          if(elt.parentElement)
              elt.parentElement.style.backgroundColor = "purple";

          在早期,parentElement是ie專用的,而現在所有的瀏覽器都已經實現了,并且被納入了最新的DOM4規范中;

          parentElement匹配的是parent為Element的情況,而parentNode匹配的則是parent為Node的情況;Element是包含在Node里的,它的nodeType是1;一般情況parentNode可以取代parentElement的所有功能;

          最能體現兩者的區別是以下兩行代碼:

          console.log(document.documentElement);  // <html>
          console.log(document.documentElement.parentNode);  // #document
          console.log(document.documentElement.parentElement);  // null

          previousSibling: 前一個兄弟節點;如果這個節點就是第一個兄弟節點,那么該值為null;

          nextSibling : 后一個兄弟節點:如果這個節點就是最后一個兄弟節點,那么該值為null;

          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.parentNode);  // <body>
          var firstChild = mydiv.childNodes[0];  // 把0換成1或2
          console.log(firstChild.previousSibling);
          console.log(firstChild.nextSibling);
          if(firstChild.nextSibling === null)
              console.log("child is last node");
          else
          console.log("child is't last node");
          // 遍歷
          var elt = document.getElementById("elt");
          while(elt){
              console.log(elt.nodeName);
              elt = elt.nextSibling;
          }

          firstChild : 指向在childNodes集合中的第一個節點;

          lastChild : 指向在childNodes集合中最后一個節點;

          即firstChild始終指向childNodes[0];lastChild指向childNodes[someNode.childNodes.length - 1];

          var mydiv = document.getElementById("mydiv");
          var firstChild = mydiv.firstChild;
          var lastChild = mydiv.lastChild;
          console.log(firstChild);
          console.log(lastChild);

          注:當只有一個子節點的情況下,firstChild和lastChild指向同一個節點;如果沒有子節點,均為null;并不是每種節點都有子節點;

          <div id="mydiv"><h2>零點<span>程序員</span></h2></div>
          <script>
              var textChild = mydiv.firstChild.firstChild;
              console.log(textChild.nodeType);  // 3 文本節占
              console.log(textChild.childNodes); // 空的NodeList[]
          </script>

          textChild指的是零點,而不是零點<span>程序員</span>;

          ownerDocument 屬性:

          指向這個節點所屬的文檔節點(也就是頂層節點);

          任何節點都屬于它所在的文檔,任何節點都不能同時存在于兩個或多個文檔中,通過這個屬性不必層層回溯到頂端,而是直接訪問到文檔節點;

          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.ownerDocument); // #document
          var innerDiv = document.getElementById("innerDiv");
          console.log(innerDiv.ownerDocument); // #document
          console.log(mydiv.ownerDocument.documentElement);  // <html>

          節點層次關系圖:


          節點層次關系圖

          Node方法(操作節點):

          appendChild(node) : 將node添加到childNodes的末尾; 添加節點后,childNodes的新添節點、父節點及以前的最后一個子節點的關系指針都會相應地得到更新;更新后,appendChild()返回新增的節點;

          var mydiv = document.getElementById("mydiv");
          var p = document.createElement("p");
          p.innerText = "大師哥王唯";
          var returnP = mydiv.appendChild(p);
          console.log(mydiv.lastChild);  // <p>大師哥王唯</p>
          console.log(p === returnP);  // true

          如果傳入appendChild()的節點已經是文檔的一部分了,就將該節點從原來的位置轉移到新位置;即任何DOM節點不能同時出現在文檔中的多個位置上;

          <script>
          var mydiv = document.getElementById("mydiv");
          var yourdiv = document.getElementById("yourdiv");
          // 注:把最后的子元素的空白符刪除
          var returnDiv = yourdiv.appendChild(mydiv.lastChild);
          console.log(returnDiv === mydiv.lastChild);  // false
          console.log(returnDiv === yourdiv.firstChild);  // true
          </script>

          如果在調用appendChild()方法時,傳入了父節點的第一個子節點,那么,該節點就會成為父節點中的最后一個子節點,如:

          // 注意html中的換行空格
          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.firstChild);
          mydiv.appendChild(mydiv.firstChild);
          console.log(mydiv.firstChild);

          需要注意的問題;

          var divs = document.getElementsByTagName("div");
          var btn = document.createElement("input");
          btn.type = "button";
          btn.value = "按鈕";
          for(var i=0; i<divs.length; i++){
              console.log(divs[i]);
              divs[i].appendChild(btn);
          }

          本來的意圖是為了給所有的div添加input子元素,可卻終只是最后的div添加了,原因是一個元素只能有一個父元素,起先,第一個div的確添加了input的元素,便是循環中的appendChild()會讓元素從原來的位置轉移到新位置;

          改寫:把創建btn的代碼放到for循環內,即可達到目的;

          由于appendChild()返回的是被追加的子元素,所以在鏈式調用時不能隨便使用;

          var elt = document.createElement('p').appendChild(document.createElement('b'));
          console.log(elt);  // elt為<b></b>

          本意是返回一個p節點,并且這個p元素包含一個b的子節點,但實際上elt為b;

          // 改成
          var elt = document.createElement('p')
          elt.appendChild(document.createElement('b'));
          console.log(elt);  // <p>
          document.body.appendChild(elt);

          insertBefore(newnode, refnode)方法:

          在childNodes中的refnode之前插入newcode,并返回這個節點;

          插入節點后,被插入的節點(新節點)會變成參照節點的前一個兄弟節點;如果參照節點是null,則與appendChild()執行相同的操作;

          var mydiv = document.getElementById("mydiv");
          var p = document.createElement("p");
          p.innerText = "大師哥王唯";
          // 或者參照mydiv.firstChild節點
          var returnP = mydiv.insertBefore(p,null);  // 插入后成為最后一個子節點
          console.log(mydiv.lastChild);  // <p>大師哥王唯</p>
          var returnP = mydiv.insertBefore(p, mydiv.firstChild);  // 插入后成為第一個子節點
          console.log(mydiv.firstChild);  // <p>大師哥王唯</p>
          var returnP = mydiv.insertBefore(p, mydiv.lastChild);  // 插入到最后子節點的前面
          console.log(mydiv.childNodes[mydiv.childNodes.length - 2]);// <p>大師哥王唯</p>
          var returnP = mydiv.insertBefore(p,mydiv.childNodes[1]);  // 插入到任意節點的前面
          console.log(mydiv.childNodes[1]);  // <p>大師哥王唯</p>
          var yourdiv = document.getElementById("yourdiv");
          yourdiv.insertBefore(mydiv.childNodes[1],null);//把mydiv的子節點插入到yourdiv中

          參照節點refnode是必選參數,如果沒有,則傳null,否則會拋出異常;如果傳遞undefined,則會隱式轉換;

          // mydiv.insertBefore(elt); // 異常
          mydiv.insertBefore(elt,undefined); // undefined隱式轉換
          // mydiv.insertBefore(elt,"undefined"); // 異常

          如果給定的子節點是文檔中現有的節點,insertBefore() 會將其從當前位置移動到新位置;

          var elt = document.getElementById("elt");
          var mydiv = document.getElementById("mydiv");
          mydiv.insertBefore(elt,mydiv.firstChild); // elt被移到mydiv內的第一個位置

          提供一個使用索引位置插入節點的簡單函數;

          // 將child節點插入到parent中,使其成為第n個子節點
          function insertAt(parent, child, index){
              if(index < 0 || index > parent.childNodes.length)
                  throw new Error("invalid index.");
              else if(index == parent.childNodes.length)
                  parent.appendChild(child);
              else
                  parent.insertBefore(child, parent.childNodes[index]);
          }
          var mydiv = document.getElementById("mydiv");
          insertAt(mydiv,document.createTextNode("零點程序員"),mydiv.childNodes.length);
          沒有insertAfter()方法,不過,可以使用insertBefore()和nextSibling()來模擬它;
          // 如果refNode就是最后一個子節點,那么refNode.nextSibling為null
          Element.prototype.insertAfter = function(newNode,refNode){
              this.insertBefore(newNode, refNode.nextSibling);
          }
          mydiv.insertAfter(elt,mydiv.firstChild);  // 插入到第二個位置
          mydiv.insertAfter(elt,mydiv.lastChild);  // 插入到最后

          removeChild(node)方法:

          移除子節點,并返回被移除節點

          var mydiv = document.getElementById("mydiv");
          var deleteChild = mydiv.removeChild(mydiv.firstChild);
          console.log(deleteChild);
          // 刪除的節點還可以再次使用
          var yourdiv = document.getElementById("yourdiv");
          yourdiv.appendChild(deleteChild);
          console.log(yourdiv.firstChild);

          被移除的節點仍然還在文檔中,但它在文檔中已經沒有了自己的位置,但如果被刪除節點沒有變量引用它,在一定的時間內將會被內存管理器回收;

          但如果被刪除的子節點,一開始就被變量引用,即使該節點被刪除,其還被保持著引用;

          var mydiv = document.getElementById("mydiv");
          var deleteChild = mydiv.removeChild(mydiv.firstChild);
          console.log(deleteChild === mydiv.firstChild);  // false
          // 改成
          var deleteChild = mydiv.firstChild;
          var returnChild = mydiv.removeChild(deleteChild);
          console.log(deleteChild === returnChild); // true

          removeChild()方法是在父節點上調用的,所以刪除一個節點,一定先定位好父節點再刪除子節點,比如要刪除當前的元素,可以:

          node.parentNode.removeChild(node);

          刪除所有子節點;

          var mydiv = document.getElementById("mydiv");
          for(var i=mydiv.childNodes.length-1; i>=0; i--){
              console.log(mydiv.childNodes[i]);
              mydiv.removeChild(mydiv.childNodes[i]);
          }
          console.log(mydiv);

          為什么由后往前刪除,因為childNodes返回的列表是動態的,每一次訪問它都是被刪除一個后的列表;因此,如果只是單純的刪除所有子節點,可以:

          // 更簡單的刪除,或者把firstChild換成lastChild也可以
          while(mydiv.firstChild)
              mydiv.removeChild(mydiv.firstChild);

          replaceChild(newnode, oldnode) 方法:

          刪除一個子節點并用一個新的節點取而代之,即將childNodes中的oldnode 替換成newnode;

          會返回被替換的節點oldnode,并從文檔樹中刪除;

          <div id="yourdiv">temp</div>
          <script>
          var mydiv = document.getElementById("mydiv");
          var p = document.createElement("p");
          p.innerText = "大師哥王唯";
          var child = mydiv.replaceChild(p, mydiv.firstChild);
          console.log(child);
          var yourdiv = document.getElementById("yourdiv");
          if(yourdiv.hasChildNodes()){
              var child = yourdiv.replaceChild(mydiv.lastChild,yourdiv.firstChild);
          }
          </script>

          定義一個環繞節點的函數:

          function roundNode(outerNodeString,innerNode){
              // 假如參數為字符串而不是節點,將其當做元素的id
              if(typeof innerNode == "string")
                  innerNode = document.getElementById(innerNode);
              var parent = innerNode.parentNode;  // 取得父節點
              var outerNode = document.createElement(outerNodeString);
              parent.replaceChild(outerNode, innerNode);
              outerNode.appendChild(innerNode);
              return outerNode;
          }
          var mydiv = document.getElementById("mydiv");
          roundNode("div",mydiv).style.color = "red";

          cloneNode()方法:

          用于創建調用這個方法的節點的一個完全相同的副本;

          其接受一個參數,表示是否執行深復制,true為深復制,即復制節點及其整個子節點樹; false,則為淺復制,即只復制節點本身,節點所包含的文本也不會被復制,默認為false;

          var mydiv = document.getElementById("mydiv");
          var cloneDiv = mydiv.cloneNode(true);
          console.log(cloneDiv);

          <ul id="mylist">
              <li>HTML5</li>
              <li>CSS3</li>
              <li>Javascript</li>
          </ul>
          <script>
          var myList = document.getElementById("mylist");
          var deepList = myList.cloneNode(true);
          var shallowList = myList.cloneNode(false);
          console.log(deepList.childNodes.length);  // 7
          console.log(shallowList.childNodes.length); // 0
          </script>

          復制后的節點副本屬于文檔所有,但并沒有父節點,因此需要通過appendChild()、insertBefore或replaceChild將它添加文檔中;

          console.log(mydiv.parentNode);  // <body>
          console.log(cloneDiv.parentNode);  // null
          var yourdiv = document.getElementById("yourdiv");
          yourdiv.appendChild(cloneDiv);  // 不符合標準,因為有兩個id為mydiv元素
          console.log(cloneDiv.parentNode);  // <div id="yourdiv">

          cloneNode()方法不會復制添加到DOM節點中的Javascript屬性,如事件處理程序等,但會復制特性、子節點;但如果是在標簽中直接添加的on事件,也會被復制,因為它是被當作特性復制的;

          var myList = document.getElementsByTagName("ul")[0];
          var li3 = myList.childNodes[5]; // 第3個li
          li3.addEventListener("click",function(e){
              alert(this.innerText);
          },false);
          var deepList = myList.cloneNode(true);
          var yourdiv = document.getElementById("yourdiv");
          yourdiv.appendChild(deepList);

          如果原始節點設置了ID,并且克隆節點會被插入到相同的文檔中,那么就應該修改克隆節點的ID以保證其唯一性;

          console.log(deepList.id);  // mylist
          deepList.id = "deeplist";
          console.log(deepList.id);  // deeplist

          normalize()方法:

          合并節點,該方法唯一的作用就是處理文檔樹中的文本節點;由于解析器的實現或DOM操作等原因,可能會出現文本節點不包含文本,或者接連出現兩個文本節點的情況;當在某個節點上調用該方法時,就會在該節點的后代節點中查找上述兩種情況;如果找到了空文本節點,則刪除它;如果找到相鄰的文本節點,則將它們合并為一個文本節點;

          hasChildNodes()方法:

          判斷節點是否有子節點,會返回一個布爾值;

          var mydiv = document.getElementById("mydiv");
          console.log(mydiv.childNodes.length);
          console.log(mydiv.hasChildNodes());
          if(mydiv.hasChildNodes())
              console.log("有子節點");
          else
          console.log("無");
          // 如果源文件中有空白符,刪除的是空白文本節點
          if(mydiv.hasChildNodes())
              mydiv.removeChild(mydiv.firstChild);
          // 全部刪除
          while(mydiv.hasChildNodes())
              mydiv.removeChild(mydiv.firstChild);

          總結:判斷一個節點是否有子節點,有三種方式;

          node.firstChild !== null、node.childNodes.length > 0、node.hasChildNodes()方法;

          contains()方法:

          在實際開發中,經常需要知道某個節點是不是另一個節點的后代; 其接受一個后代節點,用于判斷是否為當前元素的后代節點,如果是,返回true,否則為false;

          console.log(document.documentElement.contains(document.body));
          // 檢查一個元素是否是body元素的后代元素且非body元素本身
          function isInPage(node){
              return (node === document.body) ? false : document.body.contains(node);
          }
          console.log(isInPage(mydiv));

          Web前端開發之Javascript-零點程序員-王唯


          主站蜘蛛池模板: 国产91大片精品一区在线观看 | 无码日韩精品一区二区人妻| 国产亚洲无线码一区二区 | 性色AV一区二区三区无码| 亚洲一区二区三区无码国产| 在线播放国产一区二区三区| 无码人妻一区二区三区兔费| 亚洲一区二区观看播放| 无码人妻啪啪一区二区| 精品视频无码一区二区三区| 日本一道高清一区二区三区| 欧洲精品一区二区三区| 精品人体无码一区二区三区| 亚洲中文字幕无码一区二区三区| 国产AV午夜精品一区二区入口 | 一区二区三区视频免费| 91一区二区三区四区五区| 成人区精品一区二区不卡| 亚洲视频一区二区三区四区| 国产成人高清亚洲一区91| 日韩av无码一区二区三区| 日韩一区二区三区在线观看| 99久久人妻精品免费一区 | 激情亚洲一区国产精品| 日本精品一区二区三区在线观看| 国产精品亚洲一区二区三区久久 | 无码精品一区二区三区| 精品一区二区三区四区| 国产成人精品视频一区| 午夜性色一区二区三区不卡视频 | 国产成人精品日本亚洲专一区| 中文字幕久久久久一区| 乱人伦一区二区三区| 免费看AV毛片一区二区三区| 亚洲国产高清在线精品一区| 久久久精品人妻一区亚美研究所| 国产观看精品一区二区三区| 国产亚洲福利精品一区二区| 日韩精品一区在线| 八戒久久精品一区二区三区| 国产一区二区三区在线观看影院 |