從原生web元件到框架元件原始碼(二)

房東家的貓發表於2020-11-02

innerHTML outerHTML textContent innerText 區別

<div id="test">
  <span>sdsdsdsd <span>555</span></span>
  bbbb
</div>
  • innerHTML

​ 從物件的起始位置到終止位置的全部內容,包括Html標籤

<span>sdsdsdsd <span>555</span></span>bbbb

  • innerText

    從起始位置到終點位置的內容,去掉HTML內容,並且如果裡面有多個標籤或者迭代子代都是去除標籤的

    sdsdsdsd 555 bbb

  • outerHTML

    除了包含innerHTML的全部內容外,還包含物件標籤本身

  • textContent

    跟innerText 返回的結果一樣

重要

根據W3C標籤,儘量用innerHTML,textContent,而少用innerText,outerHTML

動態訪問DOM

方法 描述
document.createElement(``tag) 建立並返回一個HTML標籤tag
element.appendChild(``child) 新增的元素child裡面element
element.insertAdjacentHTML(``pos,``html) 將程式碼插入htmlelement
element.insertAdjacentElement(``pos,``node) 將元素node插入element

正常情況下我們使用的時候

class AppElement extends HTMLElement {

  name = this.getAttribute("name") || "Desconocido";

  connectedCallback() {
    const element = document.createElement("div");
    element.className = "element";
    this.appendChild(element);

    const innerElement = document.createElement("div");
    innerElement.textContent = this.name;
    element.appendChild(innerElement);
  }
}
資料型別 特定型別 標籤 描述
HTMLElement HTMLDivElement <div> 看不見的分隔層(塊中)。
HTMLElement HTMLSpanElement <span> 不可見的分隔層(線上)。
HTMLElement HTMLImageElement <img> 圖片。
HTMLElement HTMLAudioElement <audio> 音訊容器。

inserAdjacentHTML

element.insertAdjacentHTML(position, text);

position

插入內容的位置

  • 'beforebegin':元素自身的前面。
  • 'afterbegin':插入元素內部的第一個子節點之前。
  • 'beforeend':插入元素內部的最後一個子節點之後。
  • 'afterend':元素自身的後面。

text

  • 解析為html,並插入到DOM樹上,字串型別
// 原為 <div id="one">one</div> 
var d1 = document.getElementById('one'); 
d1.insertAdjacentHTML('afterend', '<div id="two">two</div>');

// 此時,新結構變成:
// <div id="one">one</div><div id="two">two</div>

insertAdjacentElement(position, element);

個人覺得這個用的比較多

position

DOMString 表示相對於該元素的位置;必須是以下字串之一:

  • 'beforebegin': 在該元素本身的前面.
  • 'afterbegin':只在該元素當中, 在該元素第一個子孩子前面.
  • 'beforeend':只在該元素當中, 在該元素最後一個子孩子後面.
  • 'afterend': 在該元素本身的後面.
<div id="one">one</div>
<script>
  let span = document.createElement('span');
  span.textContent='3333';
  let one=document.querySelector('#one');
  one.insertAdjacentElement('beforebegin',span)
</script>

content.cloneNode(deep)

deep為 true 的時候,就是建立一個深層克隆,為false就是淺拷貝

我們要目標這個適用於自定義元素

// 建立一個自定義元件
const template = document.createElement("template");
template.innerHTML = `
  <div class="element">
    <div class="name"></div>
  </div>`;

// 講自定義元件拷貝到AppElement 裡面
class AppElement extends HTMLElement {

  name = this.getAttribute("name") || "Desconocido";

  connectedCallback() {
    const markup = template.content.cloneNode((true));
    markup.querySelector(".name").textContent = this.name;
    this.appendChild(markup);
  }
}

shadow DOM

影子DOM

在javascript不同框架出現後,他們設計了Virstual DOM: 頁面DOM的記憶體中副本,可以直接管理更改,以便後面轉換為文件的真實DOM,目的是加快優化頁面的DOM更改

例如:react 引入虛擬DOM,已檢測更改(樹之間的差異), 更新受影響的節點,然後再將其傳遞給真實的dom

語法

const div = document.createElement("div");
const shadow = div.attachShadow({ mode: "open" });
div.shadowRoot === shadow; // true

mode 定義了shadow DOM的封裝模式,建立shadow Dom元素將具有一個屬性,通過.shadowRoot 訪問

封裝css

我們知道css具有全域性特性,Shadow DOM 中css不會影響文件的css,也不會使全域性css傳遞到shaDow DOM 的css,就是具有沙箱形式

  <style>
    h1{
      color:red;
    }
  </style>
<app-element></app-element>
<span>33233</span>
<script>
  customElements.define("app-element", class App extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }
    connectedCallback() {
      this.shadowRoot.innerHTML = `
      <style>
        span {
          background: steelblue;
          padding: 5px;
          color: white;
        }
      </style>
      <div>
        <span>CSS</span>
        <h1>3333</h1>
      </div>
    `;
    }
  });
</script>
<h1>h1</h1>

我們發現,css被完全隔離開了

自定義元素

<app-element></app-element>

<script>
  customElements.define("app-element", class extends HTMLElement {
    connectedCallback() {
      this.innerHTML = "<div>Hello, friend!</div>";
    }
  });
</script>

在自定義元素裡面插入 shadow DOM

<app-element>
  <div class="container">Contenido previo del elemento</div>
</app-element>

<script>
  customElements.define("app-element", class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }

    connectedCallback() {
      this.shadowRoot.innerHTML = "<div>Hello, friend!</div>";
    }
  });
</script>

我們發現建立頁面上的Shadow DOM 後,頁面上.container 的dom被隱藏了

換句話說當我們附加shadow DOM 時,它將隱藏Light DOM,但是儘管Light DOM 被隱藏了,但是可以通過瀏覽器查詢到程式碼

<app-element>
  #shadow-root (open)
    <div>Hello, friend!</div>
  <div class="container">Contenido previo del elemento</div>
</app-element>

我們this.shadowRoot.innerHTML用於修改Shadow DOM。如果使用,我們將this.innerHTML只修改Light DOM

插槽

<slot> 插槽中插入我們放置的Light DOM

connectedCallback() {
      this.shadowRoot.innerHTML = "<div>Hello, friend! <slot>預設插槽</slot></div>";
    }

我們發現預設插槽會被填充.container 的內容,當<app-element> 裡面為空的話,會填充預設的插槽

命名插槽

<app-element>
  <h2 slot="name">Manz</h2>
  <span slot="role">Developer</span>
  <p slot="description">I hate Internet Explorer.</p>
</app-element>

<script>
  customElements.define("app-element", class extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({ mode: "open" });
    }

    connectedCallback() {
      this.shadowRoot.innerHTML = `
        <div class="card">
          <slot name="name"></slot>
          <slot name="description"></slot>
          <slot name="role"></slot>
        </div>
      `;
    }
  });
</script>

通過在外部定義css,修改插槽中的css,通過我們給出<slot></slot>給出一個預設資訊

::sloted 偽類

    connectedCallback() {
      this.shadowRoot.innerHTML = `
        <style>
          ::slotted(h2) { color: blue; }  
        </style>
        <div class="card">
          <slot name="name"></slot>
          <slot name="description"></slot>
          <slot name="role"></slot>
        </div>
      `;
    }

會優先考慮全域性css,但是如果有!important 會考慮優先順序進行替換

正常情況下,全域性的css大於自身的

  ::slotted(h2) { color: blue; }  

插槽的事件檢測

事件 描述
slotchange 當它檢測到插槽元素關聯已更改時,將觸發該事件。

們將像處理任何事件一樣使用.addEventListener()該事件,使用有<slot>問題的事件來監聽它並檢測它何時被觸發,並執行關聯的功能

const slot = this.shadowRoot.querySelector("slot");
slot.addEventListener("slotchange", () => console.log("¡El slot ha cambiado!"));

例子,能監控到變化

element.setAttribute('slot', slotName);
// element.assignedSlot = $slot
element.removeAttribute('slot');
// element.assignedSlot = null

提地寫了一個完整的demo,方便理解

<app-element>
  <button onClick="add()">++</button>
  <h1>xxxx</h1>
</app-element>

js部分

  class AppElement extends HTMLElement {
    constructor() {
      super();
      this.attachShadow({mode: "open"});
    }
    slot;
    connectedCallback() {
      // 新增到頁面上
      this.shadowRoot.innerHTML = `<slot></slot>`;
      // 查詢到slot,並且監控屬性的變化
      this.slot = document.querySelector('app-element').shadowRoot.querySelector('slot');
      this.slot.addEventListener('slotchange',e=>{
        console.log('觸發');
      })
    }
  }

  customElements.define("app-element", AppElement)
// 點選的時候修改屬性
  function add() {
    let slot = document.querySelector('app-element').shadowRoot.querySelector('slot');
    slot.setAttribute('name','333')
  }

相關文章