精读大型网站架构:前端架构模块化的方法及困境,自研框架Trick

news2025/2/21 21:12:34

模块化的方法

网页和网页之间有很多相似或者相同的模块,模块化就是把这些模块抽离并独立管理。而模块化的方法,就是把模块的HTML、CSS和JavaScript文件独立出来,然后通过某种方法关联到使用这些模块的网页上。

在介绍模块化的具体方法之前,需要清楚一个点,“可以模块化”和“值得模块化”是两个完全不同的概念。如果把所有可以模块化的模块都独立出来,那么会有很多零碎的模块,这样很大程度上又会回到混乱的局面。

因此,下面先说明什么样的模块值得被模块化。

首先,模块的颗粒度需要有一个清晰的界定。模块的界定最好是3.4.1小节中提到的模块层中的某个模块区域,而不是模块区域内的一些零碎控件组合,这样能避免模块过于零碎。

一些比较复杂、相对独立,而且需要被多个网页使用的模块值得被模块化。如播放器就是一个很好的例子,播放器比较复杂,并且很多网页都会用到播放器。这些模块被模块化后,会减轻很多工作量。

一些大部分网页都需要的模块值得被模块化,如标头(Header)、底部(Footer)。这些部分被模块化后,可以很好地集中管理,当发生样式变更时,能避免修改遗漏等情况发生。

在明确了哪些模块需要被模块化之后,我们开始讨论具体的模块化方法。

1.iframe

iframe是HTML原生支持的,iframe的作用是将一个网页嵌入另外的网页中,被模块化的播放器一般以这种方式嵌入页面。使用iframe嵌入模块无疑是最理想的方式,被iframe嵌入的模块本身是一个完整的网页,拥有自己独立的HTML、CSS和JavaScript文件,模块的内部是直观的。另外,由于模块是一个完整的网页,单独调试模块会很方便。

但是,过度使用iframe往往是不被提倡的,这是由于iframe会对网页性能带来一定影响,也会提高HTTP的请求次数,所以一个网页嵌入iframe的个数最好不要超过3个。

HTML使用iframe嵌入网页的方式如代码3.32所示,其中frameborder="0"表示消除iframe边框,allowfullscreen="true"表示允许iframe全屏显示,这两个属性一般都要设置。

代码3.32 HTML使用iframe嵌入网页

<html>
<iframe frameborder="0" allowfullscreen="true" src="/module/xxx.html?param=xx"></iframe>
</html>

如果父网页和iframe中被嵌入的网页同源(网页地址的域名和端口都一致),则它们之间是可以互相通信的,如代码3.33所示。

代码3.33 父网页和iframe中被嵌入的网页相互调用JavaScript函数

//网页调用iframe页面的play()函数,其中id_frame为iframe的id
document.getElementById("id_frame").contentWindow.play()
// iframe页面调用父页面的stop()函数
parent.window. stop();

2.以插件的方式

在3.3.1小节中提到过第三方插件,这些插件一般是由CSS和JavaScript文件组成的。模块也可以以这种方式构造,只需要封装好CSS文件和JavaScript文件即可,而模块的HTML部分则需要变成JavaScript中的字符串塞到JavaScript文件里。以3.4.3小节中的例子为例(搜索框加按钮这个模块区域),为了封装完整的模块,增加了初始化函数,初始化函数被调用后,模块内容才被添加到网页上。在调用初始化函数时,可以绑定回调函数,当按钮被单击后,调用该函数。搜索框加按钮模块化的例子如下,模块的CSS文件如代码3.34所示,模块的JavaScript文件如代码3.35所示,在引用模块的CSS和JavaScript文件后,页面使用模块的JavaScript代码如代码3.36所示。

代码3.34 模块的CSS文件

/* 设置搜索区域的输入框样式 */
.Body_Search_Input{
width: calc(100% - 100px - 10px);
margin-right: 10px;
float: left;
}
/* 设置搜索区域的“搜索”按钮样式 */
.Body_Search_Button{
width: 100px;
}

代码3.35 模块的JavaScript文件

function ControlSearchBar(data) {
//插入的模板,模块的HTML部分
var template = `<input class="form-control Body_Search_Input">
<button class="btn btn-primary Body_Search_Button" >搜索
</button>`;
//记录参数,包括目标div的id和回调函数
var targetId = data["id"];
var callBackFunction = data["callBackFunction"];
//初始化函数
this.initialize = function(){
//向目标div插入模板代码
document.getElementById(targetId).innerHTML = template;
//绑定单击事件
var button = document.getElementById(targetId).getElementsBy
TagName('button')[0];
button.addEventListener("click", function(){
if(callBackFunction){
return;
}//获取输入内容并返回
var input = document.getElementById(targetId).getElementsByTag
Name('input')[0];
callBackFunction(input.value);
});
}
}

代码3.36 网页使用模块的JavaScript代码

//“搜索”按钮被单击后回调
var DoSearch = function(data){
…
}
var SearchbarInfo = {
"id":"id_Search", //目标div的id
"callBackFunction":DoSearch //回调函数的函数名
};
var Searchbar = new ControlSearchBar(SearchbarInfo); //新建一个模块对象
Searchbar.initialize();

以上是以插件形式做的模块化,这么做的好处是,网页可以像使用插件一样使用模块,非常方便。这样的做法有一个不好的地方,就是需要把HTML塞到JavaScript里,由于模块本身已经不是一个完整的网页,这样便使得模块不能以网页的形式打开,对模块本身的调试比较麻烦。另外,由于HTML需要转换成JavaScript的字符串,所以HTML部分如果内容太多的话,维护起来还是很麻烦的。

说明:例子中的代码写法不是唯一的标准,这里只是让读者有一个以插件形式实现模块化的具体感受。

3.利用框架

一些框架是提供模块化功能的,但一般有两种方式,一种方式是类似于插件的方式,需要把HTML部分塞到JavaScript里,如Vue.js、React.js等,由于它跟上面“以插件的方式”中介绍的形式类似,所以在这里不展开介绍;另一种方式是提供独立的HTML、CSS和JavaScript代码空间,开发者根据框架的规则开发和关联模块后,再通过一些额外的辅助工具来编译或构造前端工程,如Angular 2及后续版本,这里我们称它们为模块化框架。

这些模块化框架相对于插件形式的模块化,由于加入了额外的辅助工具生成一部分代码,所以这些模块化框架对于模块的构造形式相对简单。

而且由于可以独立HTML、CSS和JavaScript代码,所以也相对直观一些。对于关联模块而言,也会有更直观的关联语法。以Angular 2及后续版本为例,搜索框加按钮模块化的例子如下,其中,CSS文件的代码如代码3.37所示,HTML文件的代码如代码3.38所示,TypeScript文件的代码如代码3.39所示,其他HTML引用模块如代码3.40所示。

说明:Angular 2及后续版本只能用TypeScript作为脚本语言。

TypeScript其实是JavaScript的超集,最后TypeScript还是会被编译成JavaScript。

代码3.37 模块的CSS文件

/* 设置搜索区域的输入框样式 */
.Body_Search_Input{
width: calc(100% - 100px - 10px);
margin-right: 10px;
float: left;
}
/* 设置搜索区域的“搜索”按钮样式 */
.Body_Search_Button{
width: 100px;
}

代码3.38 模块的HTML文件

<!--只需要模块自身的标签,不需要<body></body>等标签 -->
<input class="form-control Body_Search_Input" [(ngModel)]="searchText">
<button class="btn btn-primary Body_Search_Button" (click)="search()" >搜索</button>

代码3.39 模块的TypeScript文件

import { Component, OnInit } from "@angular/core";@Component({
selector: "app-search", //定义模块名称
templateUrl: "./ app-search.html", //引用代码3.38的HTML文件
styleUrls: ["./app-search.css"] //引用代码3.37的CSS文件
})
export class SearchComponent implements OnInit {
public searchText: string = ""; //与代码3.38中的input双向绑定的变量
constructor() {}
ngOnInit() {}
public search(): void { //单击“搜索”按钮触发函数
…
this.searchText; //通过searchText变量即可获取输入框的值
…
}
}

代码3.40 引用模块(其他HTML文件)

<!--只需要用模块名称作为标签即可引用到别的HTML文件中-->
<div>
…
<app-search ></app-search>
…
</div>

以上例子的模块化形式确实很好,模块代码独立,引用时也只需要通过标签的形式引入即可。但是上面的例子忽略了很多细节,如果真的使用这些框架实现模块化的话,会发现它有点颠覆我们对网页开发的认知。这些框架会让网页开发变得复杂起来,需要开发者学习一套新的规则,开发出来的网站对框架也有强依赖。

笔者不推荐使用这些模块化框架,因为当使用这些框架的过程中出现问题时,网上能查到的资料十分有限,而官方文档的有些描述也是模棱两可,经常在一个问题上需要花很大的精力才能解决,所以在不知不觉中反而会浪费更多的时间。

在笔者过去经历的一个失败的项目里,同事们真的是各出奇招,但是不可改变的是,项目进度完全失控,网站会有源源不断的问题出现。因此,使用这种模块化方式是有代价的,那就是过高的学习成本。综上,虽然这些模块化的框架能提供更好的模块化方式,但是,如果没有过成功的项目经验,或者还没学会这些模块化框架之前,最好不要使用。

对于大型网站而言更需要慎重,因为如果有相当一部分开发人员不会使用这个模块化框架的话,那么一定会造成很大的项目失控风险。

说明:这里不推荐使用的是一些改变普通网页开发模式,且需要很高学习成本的框架,如Angular 2及后续版本。像Vue.js这种轻量级且不需要过多学习成本的框架还是值得使用的。

现今前端模块化的困局

模块化必然是前端架构发展的方向,现在的前端工程,通过3.5.1小节中介绍的方法勉强能做到模块化,但是这样的模块化形式不够好。iframe形式的网页嵌入会对性能带来问题,不能在一个网页中多处使用;插件的形式没有一个完整的网页结构,单独测试时会比较麻烦;使用一些模块化框架的话,学习曲线又过于陡峭,引用了很多工具,会把简单的网页开发变得复杂。

所以个人认为,好的前端模块化应该具备以下几个优点:

  • 模块的嵌入是简单的,尽量少的给网站带来性能问题。

  • 模块本身是完整的网页结构,可以单独调试。

  • 虽然不可避免地引用一些其他工具,但应该尽量保持普通网页开发的模式及简单性。

自研框架Trick

未来模块化的发展方向应该会解决3.5.2小节中提到的几个问题。而解决这些问题的关键,其实是解决一个网页怎么拥有多个HTML文件的问题。网页本身是只允许拥有一个HTML文件的(除去iframe和一些框架外),让一个网页拥有多个HTML文件似乎是不可能的,除非某一天浏览器支持这样的做法。

但是在计算机的世界里,有一个万能法则,如果A不能到达B的话,那么可以在A和B之间增加一个C作为跳板。也就是说,虽然我们不能直接让浏览器支持多个HTML文件,但可以通过一些手段,把多个HTML文件自动合并成一个。

基于上述考虑,笔者做了一个框架,这个框架将页面分成网页布局层和模块层。网页布局层中的网页只负责网页布局和引用模块;模块层里的每个模块都拥有独立的HTML、JavaScript和CSS文件,可单独调试。于是网页变成了一个沙盘,网页负责拼接和关联这些模块,如图3.43所示。

图3.43 自制的框架

页面布局层引用模块时,只需要如代码3.41所示即可引用Searchbar模块,框架会自动加载模块的CSS和JavaScript文件,HTML部分会自动替换到<!--@@Searchbar@@-->的位置,自动把多个HTML文件合并成一个。

代码3.41 页面引用模块

<div>
…
<!--@@Searchbar@@-->
…
</div>

由于自动加载是JavaScript脚本完成的,性能上不允许作为生产环境的产物,所以额外加了一个编译器,当需要生成生产环境代码时,编译器可以自动拼接这些代码,将页面代码和模块代码拼接到一起。

这个框架是一个顶层框架,只是做了拼接的工作,所以不影响使用其他JavaScript库、组件工具箱和框架。除此之外,模块代码是可以单独抽离并放到下一个前端工程里的,因为一个好的框架,除了有强大的功能以外,还需要有成长性,能让使用者把当前项目的积累作用在下一个项目当中。

综上,笔者个人认为,现今比较流行的模块化框架,都过于追求模块化的完备性,而让简单的网页开发变得十分复杂。在笔者个人的理解里,网页开发其实很简单,网页就是一个HTML文件加上几个JavaScript文件和几个CSS文件,而好的模块化方式,应该是在保持普通网页开发模式的前提下,保留模块本身的网页性质(能独立运行调试)的同时,可以方便地引用和使用模块。

本文给大家讲解的内容是大型网站架构的技术细节:前端架构,模块化

  1. 下篇文章给大家讲解的内容是大型网站架构的技术细节:前端架构,单页应用

  2. 觉得文章不错的朋友可以转发此文关注小编;

  3. 感谢大家的支持!

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

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

相关文章

consul--基础--05--api

consul–基础–05–api 1、介绍 主要接口是RESTful HTTP API&#xff0c;该API可以用来增删查改nodes、services、checks、configguration。所有的endpoints主要分为以下类别 kv&#xff1a;Key/Value存储agent&#xff1a;Agent控制catalog&#xff1a;管理nodes和serviceshe…

数据结构-例题实训作业-二叉树相关

第1关:以先序的方式建立二叉树 任务描述 本关任务:以先序的方式建立二叉树并显示(顺时针90度后看) 相关知识 为了完成本关任务,你需要掌握: 1.二叉树的概念 2.二叉树的先序遍历方式 3.二叉树的遍历。 编程要求 在以下空白处补写代码,以先序方式完成二叉树的建立。 //…

计算机组成原理浮点数表示

浮点数表示 浮点数的表示分为阶码和尾数&#xff1b; 比如3.026*1011;阶码是11;尾数是3.026&#xff1b; 对于阶码&#xff1a; 阶符为正&#xff0c;小数点向后移n位&#xff08;n表示阶的大小&#xff09;; 阶符为负&#xff0c;小数点向前移n位&#xff08;n表示阶的大小&a…

基础IO(上)——Linux

文章目录1.储备知识2. 文件描述符2.1 c接口2.2 直接使用系统接口2.3 open函数返回值2.4 文件描述符fd2.5 周边文件3. 重定向3.1 输出重定向3.2 输出重定向3.3 追加重定向3.4 dup4. 如何理解一切皆文件&#xff1f;1.储备知识 对文件的操作范畴&#xff1a; 在系统角度理解文件 …

R语言生物群落数据统计分析

R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂&#xff0c;涉及众多统计分析方法。本教学以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线&#xff0c;通过多个来…

中医-通过舌象判断身体状况

本文分享通过舌象判断身体的整体状况&#xff08;中医角度&#xff09;&#xff0c;得出一个可供辨证的参考&#xff0c;并且可以根据舌象做出相关的饮食调整&#xff0c;本文主讲理论&#xff0c;相关舌象图片易引人不适&#xff0c;如需找相关图片&#xff0c;可根据本文中的…

【SpringBoot】一文了解SpringBoot配置高级

文章目录前言ConfigurationProperties使用场景小结宽松绑定/松散绑定&#x1f315;博客x主页&#xff1a;己不由心王道长&#x1f315;! &#x1f30e;文章说明&#xff1a;SpringBoot配置高级&#x1f30e; ✅系列专栏&#xff1a;SpringBoot &#x1f334;本篇内容&#xff1…

javaweb JavaScript快速入门 对象 BOM DOM 事件监听

JavaScript 引入方式 1.内部脚本&#xff1a;将 JS代码定义在HTML页面中 2.外部脚本&#xff1a;将 JS代码定义在外部 JS文件中&#xff0c;然后引入到 HTML页面中 JavaScript 基础语法 windows.alert可以省略windows var: 1.作用域为全局变量 2.变量可以重复定义 &#xf…

半桥LLC谐振变换器及同步整流MATLAB仿真(一)

在开关电源中&#xff0c;LLC谐振变换器是最常见的DC-DC变换器之一。 LLC谐振电路早在上世纪80年代就已经提出&#xff0c;到如今仍有广泛的应用&#xff0c;可见其优越性。其优点表现在&#xff1a; 1.LLC的开关器件能实现软开关&#xff0c;开关损耗小 2.效率高、功率密度大 …

[附源码]计算机毕业设计JAVA 宠物医院管理系统

[附源码]计算机毕业设计JAVA 宠物医院管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

计算结构体大小(内存对齐原则)struct、union、class

这篇博客详细的介绍结构体的大小sizeof&#xff1a;union、struct、class。 一、不同数据类型所占的内存大小&#xff1a; 二、union联合体的结构体大小 1、关注点&#xff1a; &#xff08;1&#xff09;联合体的大小为所有成员变量中所占字节数最大的&#xff1b; &#xf…

【Redis 常用五大数据类型】

常用五大数据类型 官方获取redis常见数据类型操作命令&#xff1a; http://www.redis.cn/commands.html 1.Redis键(key) keys * 查看当前库所有key (匹配&#xff1a;keys *1) exists key 判断某个key是否存在 type key 查看你的key是什么类型 del key 删除指定的key数据 unli…

springmvc1:初探springmvc

一.springmvc简介 1.springmvc是什么&#xff1f; ①mvc解释 m&#xff1a;模型层&#xff08;javabean处理数据&#xff09;例如pojo类 Service或Daov&#xff1a;视图层&#xff08;页面展示数据&#xff09;c&#xff1a;控制层&#xff08;接受请求和响应浏览器&#xf…

python基于PHP+MySQL 校园二手闲置商品交易系统

每年我国都有几百万的学生参加高考,当学生接到高校的录入通知书的那一刻就要学会独立生活了,大多数学生一般都要奔袭到离家几百设置几千公里之外的地方进行求学,这对初次离开父母远离家乡求学的学子来说是人生的一次挑战。为了能够更好的适应高校的环境,他们的入校之初或者在日…

【Qt】QMainWindow |QDialog对话框

文章目录1、QMainWindow1.1 菜单栏1.2 工具栏1.3 状态栏1.4 停靠部件1.5 核心部件&#xff08;中心部件&#xff09;1.6 使用UI文件创建窗口1.6.1 UI设计窗口介绍1.6.2 菜单1.1.6.1 添加/删除菜单栏1.1.6.2 添加菜单1.1.6.3 添加菜单项1.1.6.4 添加多级菜单1.6.3 工具1.1.6.1 添…

机器学习入门(六)神经网络初识

目录 一、模型解释 1.1 用人脑解释 1.2 用模型解释 二、通过异或门的神经网络理解偏置量、神经网络的传播 2.1 与门的神经网络表示 2.2 或门的神经网络表示 2.3 异或门的神经网络表示 三、多物体分类 一、模型解释 1.1 用人脑解释 神经网络是模拟人的神经元&#…

Windows上的实用CMD命令

Windows上的实用CMD命令查看系统信息是x64位查看电脑基本诊断信息检查电脑支持的最大运行内存查看内存信息查看Windows电脑上次的启动时间查看显卡信息查看电脑当前的用户名查看当前的用户是不是电脑的管理员用户查看路由信息 route print查看Windows 的version申明&#xff1a…

从心灰意冷到自学Java3个月顺利拿到offer,多亏这份文档

跳槽时时刻刻都在发生&#xff0c;但是我建议大家跳槽之前&#xff0c;先想清楚为什么要跳槽。切不可跟风&#xff0c;看到同事一个个都走了&#xff0c;自己也盲目的开始面试起来&#xff08;期间也没有准备充分&#xff09;&#xff0c;到底是因为技术原因&#xff08;影响自…

Allegro172版本Shape避让方形盘不出现弧形操作指导

Allegro172版本Shape避让方形盘不出现弧形操作指导 Allegro172版本避让方形焊盘的时候,不会像166版本一样避让成方形,如下图 只需要按照下面的操作就可以实现和166版本方形避让一样的效果 打开shape Global dynamic parameter 选择Void Controls Rectangle pad viod cor…

java常见题

1.数组和链表结构简单对比&#xff1f;&#xff08;ArrayList和linkedList&#xff09; 数组是一段连续的空间。 大小固定 可能大小不够用或者有浪费 数组查询比较方便&#xff0c;根据下标就可以直接找到元素&#xff0c;时间复杂度O(1)&#xff1b;增加和删除比较复杂&…