28 _ WebComponent:像搭积木一样构建Web应用

在上一篇文章中我们从技术演变的角度介绍了PWA,这是一套集合了多种技术的理念,让浏览器渐进式适应设备端。今天我们要站在开发者和项目角度来聊聊WebComponent,同样它也是一套技术的组合,能提供给开发者组件化开发的能力。

那什么是组件化呢?

其实组件化并没有一个明确的定义,不过这里我们可以使用10个字来形容什么是组件化,那就是:对内高内聚,对外低耦合。对内各个元素彼此紧密结合、相互依赖,对外和其他组件的联系最少且接口简单。

可以说,程序员对组件化开发有着天生的需求,因为一个稍微复杂点的项目,就涉及到多人协作开发的问题,每个人负责的组件需要尽可能独立完成自己的功能,其组件的内部状态不能影响到别人的组件,在需要和其他组件交互的地方得提前协商好接口。通过组件化可以降低整个系统的耦合度,同时也降低程序员之间沟通复杂度,让系统变得更加易于维护。

使用组件化能带来很多优势,所以很多语言天生就对组件化提供了很好的支持,比如C/C++就可以很好地将功能封装成模块,无论是业务逻辑,还是基础功能,抑或是UI,都能很好地将其组合在一起,实现组件内部的高度内聚、组件之间的低耦合。

大部分语言都能实现组件化,归根结底在于编程语言特性,大多数语言都有自己的函数级作用域、块级作用域和类,可以将内部的状态数据隐藏在作用域之下或者对象的内部,这样外部就无法访问了,然后通过约定好的接口和外部进行通信。

JavaScript虽然有不少缺点,但是作为一门编程语言,它也能很好地实现组件化,毕竟有自己的函数级作用域和块级作用域,所以封装内部状态数据并提供接口给外部都是没有问题的。

既然JavaScript可以很好地实现组件化,那么我们所谈论的WebComponent到底又是什么呢?

阻碍前端组件化的因素

在前端虽然HTML、CSS和JavaScript是强大的开发语言,但是在大型项目中维护起来会比较困难,如果在页面中嵌入第三方内容时,还需要确保第三方的内容样式不会影响到当前内容,同样也要确保当前的DOM不会影响到第三方的内容。

所以要聊WebComponent,得先看看HTML和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>

上面这两段代码分别实现了自己p标签的属性,如果两个人分别负责开发这两段代码的话,那么在测试阶段可能没有什么问题,不过当最终项目整合的时候,其中内部的CSS属性会影响到其他外部的p标签的,之所以会这样,是因为CSS是影响全局的。

我们在《23 | 渲染流水线:CSS如何影响首次加载时的白屏时间?》这篇文章中分析过,渲染引擎会将所有的CSS内容解析为CSSOM,在生成布局树的时候,会在CSSOM中为布局树中的元素查找样式,所以有两个相同标签最终所显示出来的效果是一样的,渲染引擎是不能为它们分别单独设置样式的。

除了CSS的全局属性会阻碍组件化,DOM也是阻碍组件化的一个因素,因为在页面中只有一个DOM,任何地方都可以直接读取和修改DOM。所以使用JavaScript来实现组件化是没有问题的,但是JavaScript一旦遇上CSS和DOM,那么就相当难办了。

WebComponent组件化开发

现在我们了解了CSS和DOM是阻碍组件化的两个因素,那要怎么解决呢?

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

了解了这些,下面我们就结合具体代码来看看WebComponent是怎么实现组件化的。

前面我们说了,WebComponent是一套技术的组合,具体涉及到了Custom elements(自定义元素)、Shadow DOM(影子DOM)HTML templates(HTML模板),详细内容你可以参考MDN上的相关链接。

下面我们就来演示下这3个技术是怎么实现数据封装的,如下面代码所示:

<!DOCTYPE html>
<html>

<body>
<!–
一:定义模板
二:定义内部CSS样式
三:定义JavaScript行为
–>
<template id="geekbang-t">
<style>
p {
background-color: brown;
color: cornsilk
}

        div {
            width: 200px;
            background-color: bisque;
            border: 3px solid chocolate;
            border-radius: 10px;
        }
    &lt;/style&gt;
    &lt;div&gt;
        &lt;p&gt;time.geekbang.org&lt;/p&gt;
        &lt;p&gt;time1.geekbang.org&lt;/p&gt;
    &lt;/div&gt;
    &lt;script&gt;
        function foo() {
            console.log('inner log')
        }
    &lt;/script&gt;
&lt;/template&gt;
&lt;script&gt;
    class GeekBang extends HTMLElement {
        constructor() {
            super()
            //获取组件模板
            const content = document.querySelector('#geekbang-t').content
            //创建影子DOM节点
            const shadowDOM = this.attachShadow({ mode: 'open' })
            //将模板添加到影子DOM上
            shadowDOM.appendChild(content.cloneNode(true))
        }
    }
    customElements.define('geek-bang', GeekBang)
&lt;/script&gt;


&lt;geek-bang&gt;&lt;/geek-bang&gt;
&lt;div&gt;
    &lt;p&gt;time.geekbang.org&lt;/p&gt;
    &lt;p&gt;time1.geekbang.org&lt;/p&gt;
&lt;/div&gt;
&lt;geek-bang&gt;&lt;/geek-bang&gt;

</body>

</html>

详细观察上面这段代码,我们可以得出:要使用WebComponent,通常要实现下面三个步骤。

首先,使用template属性来创建模板。利用DOM可以查找到模板的内容,但是模板元素是不会被渲染到页面上的,也就是说DOM树中的template节点不会出现在布局树中,所以我们可以使用template来自定义一些基础的元素结构,这些基础的元素结构是可以被重复使用的。一般模板定义好之后,我们还需要在模板的内部定义样式信息。

其次,我们需要创建一个GeekBang的类。在该类的构造函数中要完成三件事:

    • 查找模板内容;
    • 创建影子DOM;
    • 再将模板添加到影子DOM上。
    • 上面最难理解的是影子DOM,其实影子DOM的作用是将模板中的内容与全局DOM和CSS进行隔离,这样我们就可以实现元素和样式的私有化了。你可以把影子DOM看成是一个作用域,其内部的样式和元素是不会影响到全局的样式和元素的,而在全局环境下,要访问影子DOM内部的样式或者元素也是需要通过约定好的接口的。

      总之,通过影子DOM,我们就实现了CSS和元素的封装,在创建好封装影子DOM的类之后,我们就可以使用customElements.define来自定义元素了(可参考上述代码定义元素的方式)。

      最后,就很简单了,可以像正常使用HTML元素一样使用该元素,如上述代码中的<geek-bang></geek-bang>

      上述代码最终渲染出来的页面,如下图所示:

      使用影子DOM的输出效果

      从图中我们可以看出,影子DOM内部的样式是不会影响到全局CSSOM的。另外,使用DOM接口也是无法直接查询到影子DOM内部元素的,比如你可以使用document.getElementsByTagName('div')来查找所有div元素,这时候你会发现影子DOM内部的元素都是无法查找的,因为要想查找影子DOM内部的元素需要专门的接口,所以通过这种方式又将影子内部的DOM和外部的DOM进行了隔离。

      通过影子DOM可以隔离CSS和DOM,不过需要注意一点,影子DOM的JavaScript脚本是不会被隔离的,比如在影子DOM定义的JavaScript函数依然可以被外部访问,这是因为JavaScript语言本身已经可以很好地实现组件化了。

      浏览器如何实现影子DOM

      关于WebComponent的使用方式我们就介绍到这里。WebComponent整体知识点不多,内容也不复杂,我认为核心就是影子DOM。上面我们介绍影子DOM的作用主要有以下两点:

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

      那么浏览器是如何实现影子DOM的呢?下面我们就来分析下,如下图:

      影子DOM示意图

      该图是上面那段示例代码对应的DOM结构图,从图中可以看出,我们使用了两次geek-bang属性,那么就会生成两个影子DOM,并且每个影子DOM都有一个shadow root的根节点,我们可以将要展示的样式或者元素添加到影子DOM的根节点上,每个影子DOM你都可以看成是一个独立的DOM,它有自己的样式、自己的属性,内部样式不会影响到外部样式,外部样式也不会影响到内部样式。

      浏览器为了实现影子DOM的特性,在代码内部做了大量的条件判断,比如当通过DOM接口去查找元素时,渲染引擎会去判断geek-bang属性下面的shadow-root元素是否是影子DOM,如果是影子DOM,那么就直接跳过shadow-root元素的查询操作。所以这样通过DOM API就无法直接查询到影子DOM的内部元素了。

      另外,当生成布局树的时候,渲染引擎也会判断geek-bang属性下面的shadow-root元素是否是影子DOM,如果是,那么在影子DOM内部元素的节点选择CSS样式的时候,会直接使用影子DOM内部的CSS属性。所以这样最终渲染出来的效果就是影子DOM内部定义的样式。

      总结

      好了,今天就讲到这里,下面我来总结下本文的主要内容。

      首先,我们介绍了组件化开发是程序员的刚需,所谓组件化就是功能模块要实现高内聚、低耦合的特性。不过由于DOM和CSSOM都是全局的,所以它们是影响了前端组件化的主要元素。基于这个原因,就出现WebComponent,它包含自定义元素、影子DOM和HTML模板三种技术,使得开发者可以隔离CSS和DOM。在此基础上,我们还重点介绍了影子DOM到底是怎么实现的。

      关于WebComponent的未来如何,这里我们不好预测和评判,但是有一点可以肯定,WebComponent也会采用渐进式迭代的方式向前推进,未来依然有很多坑需要去填。

      思考时间

      今天留给你的思考题是:你是怎么看待WebComponents和前端框架(React、Vue)之间的关系的?

      欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/675651.html

    如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

    相关文章

    微信支付(可复用)

    3.1微信支付 本项目选择小程序支付 参考&#xff1a;产品中心 - 微信支付商户平台微信支付商户平台提供各类支付产品满足商家通过微信支付收款的需求&#xff1b;平台提供智慧经营&#xff0c;现金红包&#xff0c;代金券等运营工具&#xff0c;助力商家更好的玩转营销&#x…

    重生奇迹mu格斗家介绍

    出生地&#xff1a;勇者大陆 性 别&#xff1a;男 擅 长&#xff1a;近距离攻击、技能以PVP为主战斗风格 转 职&#xff1a;格斗大师&#xff08;3转&#xff09; 介 绍&#xff1a;以PVP战斗模式为主的格斗家&#xff0c;依角色养成配点不同&#xff0c;可发展成以力量体力…

    恒创科技:无法与服务器建立安全连接怎么解决?

    在使用互联网服务时&#xff0c;有时会出现无法与服务器建立安全连接的问题&#xff0c;此错误消息通常出现在尝试访问需要安全连接的网站(例如使用 HTTPS 的网站)时&#xff0c;这可能是由于多种原因造成的&#xff0c;以下是一些常见的解决方法&#xff0c;帮助你解决问题。 …

    AI来了,产品经理该怎样面对它?

    AI终于来了&#xff0c;我们一方面期待着它可能给我们生活带来的变化&#xff0c;另一方面又担忧它可能带给我们巨大的风险和挑战。 AI带来的影响 AI不确定性的风险有很多&#xff0c;例如有人关注它是否成为“奥创”&#xff0c;但对我们大多数人来说这样的风险还很遥远&#…

    Java1.8+ idea hbuilder+ uniapp、vue上门家政小程序APP源码开发

    Java1.8 idea hbuilder uniapp、vue上门家政小程序APP源码开发 家政服务系统是一种专为家庭提供全方位服务的综合性系统。该系统通过整合多种服务功能和智能化管理&#xff0c;旨在提高家庭生活的质量和效率。 家政服务系统技术开发环境&#xff1a; 技术架构&#xff1a;spri…

    怎么制作在线研学活动报名系统?教你快速搞定

    易查分小程序&#xff1a;提升研学活动体验&#xff0c;智慧管理新选择 在教育多元化的今天&#xff0c;学校组织的研学活动可以为学生提供更多实践学习、探索世界的机会。不过&#xff0c;对于老师来说&#xff0c;活动的报名和管理常常比较复杂&#xff0c;导致工作量增加。…

    工业相机识别电路板元器件:彩色与黑白的区别

    工业相机用于识别电路板上的元器件时&#xff0c;选择彩色相机或黑白相机取决于具体应用需求和条件。彩色相机能提供更丰富的信息&#xff0c;但处理复杂度较高&#xff1b;黑白相机则在处理速度和精度上具有优势。理解它们的区别和各自的优缺点&#xff0c;有助于在具体项目中…

    软件功能测试内容简析,第三方软件测试机构进行功能测试的好处

    软件功能测试是指对软件产品的各项功能进行验证和确认的过程。它是软件开发过程中非常重要的一环&#xff0c;通过对软件的功能进行全面测试&#xff0c;可以确保软件在交付给用户之前达到预期的质量要求。 在进行功能测试时&#xff0c;需要包括以下几个方面的测试内容&#…

    docker运行centos提示Operation not permitted

    1、在docker中运行了centos7镜像 2、进入到centos容器中使用systemctl命令时提示 systemctl Failed to get D-Bus connection: Operation not permitted 3、解决办法 在运行centos镜像的时候加上--privileged参数 4、附上docker官网命令说明截图

    驱动芯片退饱和保护(DESAT)

    驱动芯片退饱和保护&#xff08;DESAT&#xff09; 1.概述2.短路能力评估3.驱动芯片的退饱和保护功能介绍3.1 退饱和工作原理3.2 退饱和电路的关键组成和影响因素 4.驱动芯片的退饱和保护功能的调试4.1 如何增加 DESAT 充电电流4.2 如何调整 DESAT 阈值电压4.3 如何使用 OC 功能…

    Chrome 调试技巧

    1. alert 在最早的时候&#xff0c;javascript 程序员调试代码都是通过 alert 进行&#xff0c;但 alert 会让整个程序被打断&#xff0c;并且还有一个很大的缺点&#xff0c;调试完成之后&#xff0c;如果忘记将 alert 删除 or 注释掉&#xff0c;导致别人访问该页面时会莫名…

    基于System-Verilog实现DE2-115开发板驱动HC_SR04超声波测距

    目录 前言 一、SystemVerilog——下一代硬件设计语言 与Verilog关系 与SystemC关系 二、实验原理 2.1 传感器概述&#xff1a; 2.2 传感器引脚 2.3 传感器工作原理 2.4 整体测距原理及编写思路 三、System-Verilog文件 3.1 时钟分频 3.2 超声波测距 3.3 数码管驱动…

    【EI会议/稳定检索】2024年机械、传感与自动控制国际会议(MSAC 2024)

    2024 International Conference on Machinery, Sensing, and Automatic Control 2024年机械、传感与自动控制国际会议 【会议信息】 会议简称&#xff1a;MSAC 2024 大会地点&#xff1a;中国贵阳 会议官网&#xff1a;www.msaciac.com 会议邮箱&#xff1a;msacsub-paper.com…

    TMS320F280049 ECAP模块--总览(0)

    ECAP 特性&#xff1a; 4个32bit的事件时间戳寄存器&#xff1b; 4个连续时间戳捕获事件的边沿极性可选上升沿、下降沿 4个事件中每个都能触发中断 4个事件都能做单词触发 可以连续捕获4个事件 绝对的捕获时间戳 差异模式捕获 不使用捕获模式时&#xff0c;可以配置输出…

    【设计模式】JAVA Design Patterns——Factory Method(虚拟构造器模式)

    &#x1f50d;目的 为创建一个对象定义一个接口&#xff0c;但是让子类决定实例化哪个类。工厂方法允许类将实例化延迟到子类 &#x1f50d;解释 真实世界例子 铁匠生产武器。精灵需要精灵武器&#xff0c;而兽人需要兽人武器。根据客户来召唤正确类型的铁匠。 通俗描述 它为类…

    esp32芯片选型网页链接

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/bd340b1360dc45ec9a4286f64c95b39d.png esp32芯片选型网页链接

    采用C#、Python和Qt开发上位机好看的界面也不是多的事儿

    采用C#、Python和Qt开发上位机好看的界面也不是多的事儿

    iOS18新功能大爆料,打破常规,全面升级,这些变化不容错过!

    众所周知&#xff0c;苹果 iOS 操作系统近年来都没有发生重大变化&#xff0c;主要是添加小部件、锁屏编辑和手机屏幕编辑等功能&#xff0c;再加上bug偏多&#xff0c;以至于越来越多iPhone用户不愿意再升级系统了。这一点&#xff0c;从 iOS 17 明显降低的安装率中就能看出一…

    他人项目二次开发——慎接

    接了一个朋友的项目——开发及运营迭代差不多2年多了&#xff0c;整体样子移动端和PC都能正常使用&#xff0c;但后期的扩展性及新功能添加出现瓶颈。 因此给了一部分钱&#xff0c;让我接手来开发——重构架构。 背景说明 朋友公司的技术人员是我帮忙招聘的&#xff0c;相关技…

    前端工程化工具系列(六)—— VS Code(v1.89.1):强大的代码编辑器

    VS Code&#xff08;Visual Studio Code&#xff09;是一款由微软开发的强大且轻量级的代码编辑器&#xff0c;支持多种编程语言&#xff0c;并提供了丰富的扩展插件生态系统。 这里主要介绍如何使用配置 ESLint、Stylelint 等插件来提升开发效率。 1 自动格式化代码 最终要…