IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Web组件API -> 正文阅读

[JavaScript知识库]Web组件API

Web组件API

最近学完 Vue 后发现它封装的 DOM 实在是太强大了。使用 Es6 Moudule 或 CommonJs Module 实现封装 Js 模块化比较简单。但是想要封装 DOM 真没那么简单。看了红宝书 JavaScript API 中的 Web组件 API,发现里面的内容跟 Vue 用到的方法很像,所以我就先看一下做一些基础知识储备,等哪天有能力了再模仿Vue自己封装一个 DOM,(感觉有点天真,哈哈哈 ~~)

知识库

  1. template模板
  2. 影子 DOM
  3. 自定义标签

一、template 模板

在 Web 组件之前,一直缺少基于 HTML 解析构建 DOM 子树,然后在需要时再把这个子树渲染出 来的机制。一种间接方案是使用 innerHTML 把标记字符串转换为 DOM 元素,但这种方式存在严重的 安全隐患。另一种间接方案是使用 document.createElement()构建每个元素,然后逐个把它们添加 到孤儿根节点(不是添加到 DOM),但这样做特别麻烦,完全与标记无关。 相反,更好的方式是提前在页面中写出特殊标记,让浏览器自动将其解析为 DOM 子树,但跳过渲染,这正是 HTML 模板的核心思想,而 teamplate 标签正式为这个目的而生的。

特点

template 里面的内容会被存放在document-fragment 且不会被渲染出来

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div class="context">

  </div>
  <template id="template">
    <div class='user'>
      <div class="name">小明</div>
      <div class="info">爱学习</div>
    </div>
  </template>
  <script>
    const user = document.getElementById("template").content;
    console.log(user);

  </script>
</body>
</html>

在这里插入图片描述

让 template 里面所有节点转移在 dom 节点上

  1. 剪切方式(转移后 template 所有节点清空)

     <script>
        const userFragment = document.getElementById("template").content;
        document.getElementById("context").appendChild(userFragment);
     </script>
    
    

在这里插入图片描述

  1. 复制方式 (转移后 template 所有节点还在)
 const userFragment = document.getElementById("template").content;
 document.getElementById('context').append(document.importNode(userFragment, true))

在这里插入图片描述

二、影子DOM

影子 DOM (shadow DOM),通过它可以将一个完整的DOM树作为节点添加到父DOM树。这样可以实现DOM封装,意味着 CSS 样式和 CSS 选择父可以限制在 影子DOM 子树而不是整个顶级 DOM 树中。

使用影子 DOM

向 DOM 父节点插入了三个独立的 DOM 子节点

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    for (let color of ['red', 'green', 'blue']) {
      const div = document.createElement('div');
      document.body.appendChild(div);
       // 为 div 添加影子 DOM 
      const shadowDOM = div.attachShadow({ mode: 'open' });
      shadowDOM.innerHTML = ` 
 <p>Make me ${color}</p> 
 <style> 
 p { 
 color: ${color}; 
 } 
 </style> 
 `;
    }

  </script>
</body>

</html>

在这里插入图片描述

影子 DOM 槽位

默认槽位

<slot>标签指示浏览器在哪里放置原来的HTML

为一个DOM节点添加影子DOM后,由于影子DOM的显示优先级高于宿主DOM的内容,宿主DOM的内容就不会被显示,而使用DOM槽位可以映射DOM宿主的内容到影子DOM槽位里面

    document.body.innerHTML = ` 
<div id="foo"> 
 <p>Foo</p> 
</div> 
`;
// 为 div 添加 影子 DOM,使用 slot 把 <p>Foo</p> 映射进来了
    document.querySelector('div')
      .attachShadow({ mode: 'open' })
      .innerHTML = `<div id="bar"> 
 <slot></slot> 
 <div>`

在这里插入图片描述

使用槽位改写红绿蓝三色树

  for (let color of ['red', 'green', 'blue']) { 
     const divElement = document.createElement('div'); 
     divElement.innerText = `Make me ${color}`;  // 先添加到 div
     document.body.appendChild(divElement) 
     divElement 
     .attachShadow({ mode: 'open' }) 
     .innerHTML = ` 
     <p><slot></slot></p>  
     <style> 
     p { 
     color: ${color}; 
     } 
     </style> 
     `;  // 使用 slot 映射宿主的内容就进来
} 

在这里插入图片描述

命名槽位(named slot)实现多个投射

通过匹配的 slot/name 属性对实现的。带有 slot="foo"属性的元素会被投射到带有 name="foo"的上。

       document.body.innerHTML = ` 
    <div> 
     <p slot="foo">Foo</p> 
     <p slot="bar">Bar</p> 
    </div> 
    `;
        document.querySelector('div')
          .attachShadow({ mode: 'open' })
          .innerHTML = ` 
     <slot name="bar"></slot> 
     <slot name="foo"></slot> 
     `;

在这里插入图片描述

事件重定向

如果影子 DOM 中发生了浏览器事件(如 click),那么浏览器需要一种方式以让父 DOM 处理事件。 不过,实现也必须考虑影子 DOM 的边界。为此,事件会逃出影子 DOM 并经过事件重定向(event retarget) 在外部被处理,效果有点像是事件冒泡。

    document.body.innerHTML = ` 
<div οnclick="console.log('Handled outside:', event.target)">Bar</div> 
`;
    // 添加影子 DOM 并向其中插入 HTML 
    document.querySelector('div')
      .attachShadow({ mode: 'open' })
      .innerHTML = ` 
        <slot></slot>
<button οnclick="console.log('Handled inside:', event.target)">Foo</button> 
`;

点击影子DOM,先触发 影子DOM处理函数,再触发宿主事件

在这里插入图片描述

点击宿主DIV,只触发了宿主事件

在这里插入图片描述

三、自定义元素

自定义元素为 HTML 元素引入了面向对象编程的风格。基于这种风格,可以创 建自定义的、复杂的和可重用的元素,而且只要使用简单的 HTML 标签或属性就可以创建相应的实例。

创建自定义元素

自定义元素的威力源自类定义。例如,可以通过调用自定义元素的构造函数来控制这个类在 DOM 中每个实例的行为

   class FooElement extends HTMLElement {
      constructor() {
        super();
        console.log('x-foo')
      }
    }
    // 定义组件名称
    customElements.define('x-foo', FooElement);
    document.body.innerHTML = ` 
<x-foo>x-foo</x-foo> 
<x-foo>x-foo</x-foo> 
<x-foo>x-foo</x-foo> 
`;

在这里插入图片描述

在这里插入图片描述

如果自定义元素继承了一个元素类,那么可以使用 is 属性和 extends 选项将标签指定为该自定义 元素的实例

    class FooElement extends HTMLDivElement {
      constructor() {
        super();
        console.log('x-foo')
      }
    }
    customElements.define('x-foo', FooElement, { extends: 'div' });
    document.body.innerHTML = ` 
<div is="x-foo">x-foo</div> 
<div is="x-foo">x-foo</div> 
<div is="x-foo">x-foo</div> 
`;

在这里插入图片描述

在这里插入图片描述

添加 Web 组件内容

因为每次将自定义元素添加到 DOM 中都会调用其类构造函数,所以很容易自动给自定义元素添加 子 DOM 内容。虽然不能在构造函数中添加子 DOM(会抛出 DOMException),但可以为自定义元素添 加影子 DOM 并将内容添加到这个影子 DOM 中

    class FooElement extends HTMLElement {
      constructor() {
        super();
        // this 引用 Web 组件节点
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.innerHTML = ` 
 <p>I'm inside a custom element!</p> 
 `;
      }
    }
    customElements.define('x-foo', FooElement);
    document.body.innerHTML += `<x-foo></x-foo`;

在这里插入图片描述

使用 teamplate 优化

为避免字符串模板和 innerHTML 不干净,可以使用 HTML 模板和 document.createElement() 重构这个例子

   const template = document.querySelector('#x-foo-tpl');
    class FooElement extends HTMLElement {
      constructor() {
        super();
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(template.content.cloneNode(true));
      }
    }
    customElements.define('x-foo', FooElement);
    document.body.innerHTML += `<x-foo></x-foo`;
自定义元素生命周期方法

constructor(): 在创建元素实例或将已有 DOM 元素升级为自定义元素时调用。

connectedCallback():在每次将这个自定义元素实例添加到 DOM 中时调用

disconnectedCallback():在每次将这个自定义元素实例从 DOM 中移除时调用

attributeChangedCallback():在每次可观察属性的值发生变化时调用。在元素实例初始化 时,初始值的定义也算一次变化。

adoptedCallback():在通过 document.adoptNode()将这个自定义元素实例移动到新文档 对象时调用。

 class FooElement extends HTMLElement {
      constructor() {
        super();
        console.log('ctor');
      }
      connectedCallback() {
        console.log('connected');
      }
      disconnectedCallback() {
        console.log('disconnected');
      }
    }
    customElements.define('x-foo', FooElement);
    const fooElement = document.createElement('x-foo');
    // ctor 
    document.body.appendChild(fooElement);
    // connected 
    document.body.removeChild(fooElement);
    // disconnected

反射自定义元素属性

自定义元素既是 DOM 实体又是 JavaScript 对象,因此两者之间应该同步变化。换句话说,对 DOM 的修改应该反映到 JavaScript 对象,反之亦然。要从 JavaScript 对象反射到 DOM,常见的方式是使用获 取函数和设置函数。下面的例子演示了在 JavaScript 对象和 DOM 之间反射 bar 属性的过程

 document.body.innerHTML = `<x-foo></x-foo>`;
    class FooElement extends HTMLElement {
      constructor() {
        super();
        this.bar = true;
      }
      get bar() {
        return this.getAttribute('bar');
      }
      set bar(value) {
        this.setAttribute('bar', value)
      }
    }
    customElements.define('x-foo', FooElement);
    console.log(document.body.innerHTML);
// <x-foo bar="true"></x-foo>

另一个方向的反射(从 DOM 到 JavaScript 对象)需要给相应的属性添加监听器。为此,可以使用 observedAttributes()获取函数让自定义元素的属性值每次改变时都调用 attributeChangedCallback()

  class FooElement extends HTMLElement {
      static get observedAttributes() {
        // 返回应该触发 attributeChangedCallback()执行的属性
        return ['bar'];
      }
      get bar() {
        return this.getAttribute('bar');
      }
      set bar(value) {
        this.setAttribute('bar', value)
      }
      attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue !== newValue) {
          console.log(`${oldValue} -> ${newValue}`);
          this[name] = newValue;
        }
      }
    }
    customElements.define('x-foo', FooElement);
    document.body.innerHTML = `<x-foo bar="false"></x-foo>`;
    // null -> false 
    document.querySelector('x-foo').setAttribute('bar', true);
    // false -> true
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:32:45  更:2021-11-14 21:33:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 18:33:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码