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)之间的关系的?

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

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

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    python 各种画图(2D 3D)-1 _matplotlib 官方网站笔记

    背景 需利用python进行3D可视化处理&#xff0c;用于分析python得到的数据的正确性。 知识学习 python高阶3D绘图---pyvista模块&#xff0c;mayavi模块&#xff0c;pyopengl模块&#xff0c;MoviePy模块基础使用-CSDN博客 python用于3D绘图的模块比较多&#xff0c;pyvist…

    目标2亿欧元!四年两次募资,全球最早专注于量子投资的Quantonation再次加码

    Quantonation Ventures 是全球第一家专注于深度物理和量子技术的早期风险投资公司。4月10日&#xff0c;该公司宣布其第二只专门用于量子技术的早期基金 Quantonation II 首次募资完成&#xff0c;目前已募资 7000 万欧元&#xff0c;而目标为 2 亿欧元。 首次募资就募到了将…

    《QT从基础到进阶·四十一》无法解析的外部符号及生成事件加入QT打包命令报错问题

    其他无法解析的外部符号&#xff1a; 无法解析的外部符号 "public: virtual struct QMetaObject const * __cdecl ML_AddinManger::metaObject(void)const "… 无法解析的外部符号 “public: virtual void * __cdecl ML_AddinManger::qt_metacast(char const *)” (?…

    toefl listening_托福听力

    x.1 课程介绍 x.1.1 课程介绍 考试介绍 注意事项如下&#xff0c; x.1.2 分数设定和方法论 x.2.1 细节题解法 x.2.2 对话主旨题解法 听力对话不要扣分&#xff1b; 内容主旨题&#xff0c;以what开头&#xff1b; 目的主旨题&#xff0c;以why开头&#xff1b; 目的主旨题…

    SpringCloud中注册中心Nacos的下载与使用步骤

    1.前言 Nacos&#xff08;Dynamic Naming and Configuration Service&#xff09;是阿里巴巴开源的一款服务发现和配置管理工具。它可以帮助用户自动化地进行服务注册、发现和配置管理&#xff0c;是面向微服务架构的一个重要组成部分。 2.下载 链接&#xff1a;https://pan.b…

    奶茶店、女装店、餐饮店是高危创业方向,原因如下:

    关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 现在的俊男靓女们&#xff0c;心中都有一个执念&#xff1a; (1)想证明自己了&#xff0c;开个奶茶去…… (2)想多赚点钱了&#xff0c;加盟餐饮店去…… (3)工作不顺心了&#xff0c;搞个女装店去…… 但凡抱着…

    回溯--字母迷宫

    1.题目描述 字母迷宫游戏初始界面记作 m x n 二维字符串数组 grid&#xff0c;请判断玩家是否能在 grid 中找到目标单词 target。 注意&#xff1a;寻找单词时 必须 按照字母顺序&#xff0c;通过水平或垂直方向相邻的单元格内的字母构成&#xff0c;同时&#xff0c;同一个单…

    Windows系统下DOS命令

    Windows系统下DOS命令 1. 与文件操作相关1.1 mkdir&#xff0c;md命令1.2 rmdir、rd命令1.3 dir命令1.4 start命令1.5 echo命令1.6 type命令1.7 copy命令1.8 move命令1.9 copy和move的区别1.10 del命令1.11 rename命令1.12 attrib命令1.13 fsutil命令1.14 assoc命令 2. 与网络相…

    数据持久化第六课-ASP.NET运行机制

    数据持久化第六课-ASP.NET运行机制 一.预习笔记 1.动态网页的工作机制通常分为以下几个阶段&#xff1a; 1&#xff09;使用动态Web开发技术编写Web应用程序&#xff0c;并部署到Web服务器。 2&#xff09;客户端通过在浏览器中输入地址&#xff0c;请求动态页面。 3&#…

    机器学习之数学基础(六)~时间复杂度和空间复杂度

    目录 算法背景 background 1. 时间复杂度 Time Complexity 1.1 时间复杂度分类 1.1.1 O(1) 常数阶 1.1.2 O(n) 线性阶 1.1.3 O(n^2) 平方阶 1.1.4 O(logn) 对数阶 1.1.5 O(nlogn) 线性对数阶 1.1.6 O(2^n) 指数阶 1.1.7 O(n!) 阶乘阶 1.1.8 时间复杂度分类 1.2 时…

    03-07Java自动化之JAVA基础之循环

    JAVA基础之循环 一、for循环 1.1for循环的含义 for&#xff08;初始化语句;条件判断;条件控制或–&#xff09;{ ​ //代码语句 } 1、首先执行初始话语句&#xff0c;给变量一个起始的值 2、条件判断进行判断&#xff0c;为true&#xff0c;执行循环体中的代码语句 ​ …

    3DGS语义分割之LangSplat

    LangSplat是CVPR2024的paper. 实现3DGS的语义分割&#xff08;可文本检索语义&#xff09; github: https://github.com/minghanqin/LangSplat?tabreadme-ov-file 主要思想是在3DGS中加入了CLIP的降维语义特征&#xff0c;可用文本检索目标&#xff0c;实现分割。 配置环境&…

    网线水晶头为什么要按标准线序打

    网线接水晶头为什么要按照线序接&#xff1f; 减少串扰和增强信号质量&#xff1a; 双绞线的设计是为了减少信号间的串扰&#xff08; Crosstalk&#xff09;&#xff0c;每一对线芯在传输过程中通过相互扭绞抵消外部电磁干扰。按照标准线序接线能够确保每一对线芯之间的信号传…

    Ubuntu server 24 (Linux) 安装部署smartdns 搭建智能DNS服务器

    SmartDNS是推荐本地运行的DNS服务器&#xff0c;SmartDNS接受本地客户端的DNS查询请求&#xff0c;从多个上游DNS服务器获取DNS查询结果&#xff0c;并将访问速度最快的结果返回给客户端&#xff0c;提高网络访问速度和准确性。 支持指定域名IP地址&#xff0c;达到禁止过滤的效…

    Pinia的介绍、使用及持久化

    Pinia介绍 什么是Pinia&#xff1f; Pinia 是 Vue 的最新 状态管理工具&#xff0c;状态就是数据。 通俗地讲&#xff1a;Pinia 是一个插件&#xff0c;可以帮我们管理 vue 通用的数据 (多组件共享的数据)。 比如一份数据有多个组件需要使用&#xff0c;在学Pinia之前我们需…

    Accelerate 笔记:保存与加载文件

    保存和加载模型、优化器、随机数生成器和 GradScaler 使用 save_state() 将上述所有内容保存到一个文件夹位置使用 load_state() 加载之前通过 save_state() 保存的状态通过使用 register_for_checkpointing()&#xff0c;可以注册自定义对象以便自动从前两个函数中存储或加载 …

    vue3+electron+typescript 项目安装、打包、多平台踩坑记录-mac+linux(包括国产化系统)

    上一章《vue3electrontypescript 项目安装、打包、多平台踩坑记录》&#xff0c;我们讲了vue3electrontypescript的项目安装和windows 32位、64位的打包。这一节我们来看下mac和linux平台的打包和一些坑。 mac 经过上一章我们的踩坑后&#xff0c;再到mac环境&#xff0c;这里…

    “雪糕刺客”爆改“红薯刺客”,钟薛高给了消费品牌哪些启示?

    夏日袭来&#xff0c;一支价格高昂却让人眼前一亮的雪糕&#xff0c;曾一度成为市场热议的焦点。然而&#xff0c;随着消费者对性价比的日益关注&#xff0c;曾经的“雪糕刺客”钟薛高&#xff0c;其创始人林盛近期以直播带货红薯开启他的还债之路&#xff0c;高打情怀“直播自…

    关于序列化与反序列化解题

    1、[安洵杯 2019]easy_serialize_php <?php$function $_GET[f];function filter($img){$filter_arr array(php,flag,php5,php4,fl1g);$filter /.implode(|,$filter_arr)./i;return preg_replace($filter,,$img); }if($_SESSION){unset($_SESSION); }$_SESSION["use…

    《数据资产》专题:《数据资产》如何确权、估值? 《数据产权》如何明确、保护?

    2020 年 04 月 10 日&#xff0c;《中共中央国务院 关于“构建更加完善的要素市场化配置体制机制”的意见》正式公布&#xff0c;将数据确立为五大生产要素&#xff08;土地、资本、劳动力以及技术&#xff09;之一&#xff0c;数据要素市场化已成为建设数字中国不可或缺的一部…