浏览器原理 27 # WebComponent

Posted 凯小默

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器原理 27 # WebComponent相关的知识,希望对你有一定的参考价值。

说明

浏览器工作原理与实践专栏学习笔记

什么是组件化?

特点:对内高内聚,对外低耦合。

对内各个元素彼此紧密结合、相互依赖,对外和其他组件的联系最少且接口简单。

阻碍前端组件化的因素

比如:在页面中嵌入第三方内容时,需要确保第三方的内容样式不会影响到当前内容,同样也要确保当前的 DOM 不会影响到第三方的内容。

CSS 是如何阻碍前端组件化的

例子:比如不同人分别写了不同的全局样式,个人测试没问题,但是合并时会出现 CSS 属性影响到其他外部的标签的样式

<style>
p {
      background-color: brown;
      color: cornsilk
   }
</style>
<p>time.geekbang.org</p>
<style>
p {
      background-color: red;
      color: blue
   }
</style>
<p>time.geekbang</p>

显然,CSS 的全局属性会阻碍组件化

DOM 也是阻碍组件化的一个因素,因为在页面中任何地方都可以直接读取和修改 DOM。

WebComponent 组件化开发

上面我们了解到了:CSS 和 DOM 是阻碍组件化的两个因素,WebComponent 给出的解决思路

WebComponent 提供了对局部视图封装能力,可以让 DOM、CSSOM 和 javascript 运行在局部环境中,这样就使得局部的 CSS 和 DOM 不会影响到全局。

WebComponent 是怎么实现组件化的

MDN:Web Components

Web Components 概念

Web Components旨在解决这些问题 — 它由三项主要技术组成,它们可以一起使用来创建封装功能的定制元素,可以在你喜欢的任何地方重用,不必担心代码冲突。

  • Custom elements(自定义元素):一组JavaScript API,允许您定义custom elements及其行为,然后可以在您的用户界面中按照需要使用它们。
  • Shadow DOM(影子DOM):一组JavaScript API,用于将封装的“影子”DOM树附加到元素(与主文档DOM分开呈现)并控制其关联的功能。通过这种方式,您可以保持元素的功能私有,这样它们就可以被脚本化和样式化,而不用担心与文档的其他部分发生冲突。
  • HTML templates(HTML模板)<template><slot> 元素使您可以编写不在呈现页面中显示的标记模板。然后它们可以作为自定义元素结构的基础被多次重用。

Web Components 使用

实现 web component 的基本方法通常如下所示:

  1. 创建一个类或函数来指定web组件的功能,如果使用类,请使用 ECMAScript 2015 的类语法。
  2. 使用 CustomElementRegistry.define() 方法注册您的新自定义元素 ,并向其传递要定义的元素名称、指定元素功能的类、以及可选的其所继承自的元素。
  3. 如果需要的话,使用 Element.attachShadow() 方法将一个 shadow DOM 附加到自定义元素上。使用通常的 DOM 方法向 shadow DOM 中添加子元素、事件监听器等等。
  4. 如果需要的话,使用 <template><slot> 定义一个html模板。再次使用常规DOM方法克隆模板并将其附加到您的shadow DOM中。
  5. 在页面任何您喜欢的位置使用自定义元素,就像使用常规HTML元素那样。

Web Components 例子

要使用 WebComponent 需要实现下面三步:

1.使用 template 属性来创建模板。

利用 DOM 可以查找到模板的内容,但是模板元素是不会被渲染到页面上的,在模板的内部定义样式信息。

<!-- 定义模板样式 -->
<template id="kxm-template">
  <style>
    p {
      background-color: green;
      color: white;
    }

    div {
      width: 200px;
      background-color: blue;
      border: 3px solid red;
      border-radius: 10px;
    }
  </style>
  <div>
      <p>kxm</p>
      <p>kaimo313</p>
  </div>
  <script>
    function foo() {
      console.log('template log');
    }
  </script>
</template>

2.创建一个类

  • 查找模板内容;
  • 创建影子 DOM;
  • 再将模板添加到影子 DOM 上。

可以把影子 DOM 看成是一个作用域,其内部的样式和元素是不会影响到全局的样式和元素的,而在全局环境下,要访问影子 DOM 内部的样式或者元素也是需要通过约定好的接口的。

<!-- 创建一个类 -->
<script>
  class Kaimo313 extends HTMLElement {
    constructor() {
      super()
      // 获取组件模板
      const content = document.querySelector('#kxm-template').content;
      // 创建影子DOM节点
      const shadowDOM = this.attachShadow({ mode: 'open' });
      // 将模板添加到影子DOM上
      shadowDOM.appendChild(content.cloneNode(true));
    }
  }
  // 使用 customElements.define 来自定义元素
  customElements.define('kaimo-313', Kaimo313);
</script>

3.使用该元素

<!-- 使用该元素 -->
<kaimo-313></kaimo-313>
<div>
  <p>kxm</p>
  <p>kaimo313</p>
</div>
<kaimo-313></kaimo-313>

完整的代码展示:

<!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>12.Web Components 例子</title>
</head>
<body>
  <h1>kxm 测试 Web Components 例子</h1>
  <!-- 定义模板样式 -->
  <template id="kxm-template">
    <style>
      p {
        background-color: green;
        color: white;
      }

      div {
        width: 200px;
        background-color: blue;
        border: 3px solid red;
        border-radius: 10px;
      }
    </style>
    <div>
        <p>kxm</p>
        <p>kaimo313</p>
    </div>
    <script>
      function foo() {
        console.log('template log');
      }
    </script>
  </template>
  <!-- 创建一个类 -->
  <script>
    class Kaimo313 extends HTMLElement {
      constructor() {
        super()
        // 获取组件模板
        const content = document.querySelector('#kxm-template').content;
        // 创建影子DOM节点
        const shadowDOM = this.attachShadow({ mode: 'open' });
        // 将模板添加到影子DOM上
        shadowDOM.appendChild(content.cloneNode(true));
      }
    }
    // 使用 customElements.define 来自定义元素
    customElements.define('kaimo-313', Kaimo313);
  </script>
  <!-- 使用该元素 -->
  <kaimo-313></kaimo-313>
  <div>
    <p>kxm</p>
    <p>kaimo313</p>
  </div>
  <kaimo-313></kaimo-313>
</body>
</html>

使用影子 DOM 的输出效果

在这里插入图片描述

浏览器如何实现影子 DOM

影子 DOM 的作用

  1. 影子 DOM 中的元素对于整个网页是不可见的;
  2. 影子 DOM 的 CSS 不会影响到整个网页的 CSSOM,影子 DOM 内部的 CSS 只对内部的元素起作用。

影子 DOM 示意图

影子 DOM 都有一个 shadow root 的根节点

在这里插入图片描述

浏览器怎么实现 DOM API 无法直接查询到影子 DOM 的内部元素?

当通过 DOM 接口去查找元素时,渲染引擎会去判断 kaimo-313 属性下面的 shadow-root 元素是否是影子 DOM,如果是,那么就直接跳过 shadow-root 元素的查询操作。

浏览器怎么实现渲染出来的效果就是影子 DOM 内部定义的样式?

当生成布局树的时候,渲染引擎也会判断 kaimo-313 属性下面的 shadow-root 元素是否是影子 DOM,如果是,那么在影子 DOM 内部元素的节点选择 CSS 样式的时候,会直接使用影子 DOM 内部的 CSS 属性。

以上是关于浏览器原理 27 # WebComponent的主要内容,如果未能解决你的问题,请参考以下文章

WebComponent

聚合物2.0 webcomponent带有切片的饼图选项抛出错误 - 无法定义toLowerCase未定义

Angular 9 Elements:如何创建一个独立的 WebComponent(包含所有依赖项)?

当 dom 中不再存在元素时,SetInterval 继续在 webcomponent 中运行

Polymer WebComponent中的Momentjs - 全局未定义

迷你avalonjs框架