opy-Paste 是一件非常有效的开发方式,但是它们一点儿也不适合维护——为了改一个拼写错误,要去修改代码中的七八个文件,打人的心都有了。
如果万一我们是要替换这七八个文件的相应代码,那么就会更加地痛苦。在后端里,我们只需要修改相应的 Java、Go、JavaScript、Python 等语言相关文件的代码。而在前端我们需要修改 HTML/JavaScript/CSS 文件,而哪怕使用的 React 这样的框架里,我们也要修改一个文件的多个地方。
于是乎,作为一个专业的程序员,我们都在不断地寻找方式来复用代码(PS:复制/粘贴从本质上也是一种复用)。
经验总结的复用
经验总结型复用,指的是结合组织和项目的经验,提取出其中的共同部分,以便于在其它项目中继续使用。事实上,所有类型的复用都是经验型复用。因此,这里的经验总结型复用,专指于用在组织内部的复用。从我的认识来看,有以下四类:
脚手架
脚手架是一种快速创建新应用的方式。在脚手架里,我们会总结出过往经验中的模式、代码,将这些模式和代码融入我们其中。其中特色就是结合常用的各种框架,并将它们结合到一起,如后端的:Spring Boot + Spring Eureka + Feign + Zuul 等,如前端的 React + Redux + React Router 等(PS:Angular 就没这么复杂)。
市面上的主流框架,本身是提供了相应的脚手架功能。基于此,脚手架可以分为两类:
两者都有各自的优缺点。框架官方的脚手架缺少一些团队、组织特定的因素。而自制的脚手架则需要团队长期维护。不过,出于种种原因(诸如 KPI),我们都会维护自己的脚手架,你说呢?
组件库(客户端)
组件库,对于每个 Web 项目来说,都是必不可少的元素。它适用于客户端开发的 UI 复用。组件库本身分为三个层级:基础 UI 组件、复合组件、业务组件 。
一般而言,我们会使用第三方的基础 UI 组件库。在那的基础之上,封装自己的业务组件库。又或者是,再对基础 UI 组件库进行二次封装,以降低对第三方组件库的依赖,让其变成可替换的组件库。
模式库
模式库其本质仍然是一个代码集,它将我们常用的代码提取出一个公共的类库中。按分类上来说,组件库也是模式库的一种。为了方便于服务端与客户端开发区别,我将组件库独立出来。
模式库,是出于共用的目的而提取出来的。在不同的项目中,它的表现形式略有差异:
两种方式也是各有优缺点。前者维护容易出错,后者更新不方便。
模板和模板应用
组件库和模板,实质上是设计系统的一部分。设计系统是一组相互关联的设计模式与共同实践的,以连贯组织来达成数字产品的目的。它包含了以下的五部分:
而模板应用,则是在模板的基础上,进一步地整合而成,用于帮助开发人员快速的构建某一类型的应用。对应于其它类型的应用而言,则要判断是否会出现相似的应用。
工具
上述的四种方式,是比较常见的方式。而随着,我们项目数量的变多,开发人员数量的膨胀,它们开始变得麻烦。我们便需要编写一些工具,以节省大量的人力成本。
CLI
这里的 CLI 是指自制的 CLI,它与我们编写的一系列自动化代码工具相互配合,形成自己的解决方案。
其交互诸如于:
choices: [ "React", "Angular", "Vue"] [?] What Framework do you want to do? > React Angular Vue
我们便可以将把配置、组件安装等一系列的工作自动化。
Schematics
Schematics 来自于 Angular 团队,其本质上也是 CLI 的一种,只是它相对于 CLI 来说,编程起来更加的简单。它将我们在编程 CLI 过程中的一些通用模式,整合出来融入了代码中。换句话来说,它相当于是前端工具中的 Angular、React——只需要编写业务逻辑,而不需要关注于基础架构。
它是现代 Web 的工作流程工具; 它可以将修改应用于您的项目,例如创建新组件或更新代码以修复依赖项中的重大变更(PS:有点类似于后端数据库脚本的味道)。还可以向现有项目添加新的配置选项或框架。
编程器插件
编程器插件,是一个非常有意思的思路。我们可以编写一个编辑器插件,在插件中加入我们常见的代码、模式和模板等。如在 VS Code 中,我们只需要创建对应的:
就此可以用于代码生成和智能感知。对于一个框架来说,我们只需要定制好框架相应的组件、模式代码,就可以复用它们。
设计系统与代码生成
当我们有了一个成体系的设计系统,就可以使用诸如 Storybook 这样的框架来优化组件的使用。它可以让我们在查看组件文档的同时,配置上相应的组件参数,最后我们只需要复制结果代码,到我们的工程中使用即可。
其与一般的组件库使用相比,更加的轻便,易于使用。
下一步,我们就是等 AI 来生成代码了。对于拥有设计系统的项目而言,我们可以直接通过类似于 Sketch2Code 的工具,直接将我们的设计转换为代码。但是,实质上这是一种更复杂的模式。对于拥有设计系统的项目来说,我们可以将设计转换为元数据。
结论
降低程序员的代码量,就是效率的提升。
者:佚名来源:前端大全
如今依赖 JavaScript 提供交互的网站越来越多。虽然 JavaScript 可以提供愉快的体验,但同时也带来了一些负面影响:
鉴于这些缺点,我们可以依靠浏览器提供的原生解决方案,这种方式不仅可以降低成本,而且还可以享受社区创建的 Web 标准专业知识带来的优势。通常,这些解决方案的代码量较少,因此还可以减少开发团队的维护工作(比如无需更新使用的库)。
在本文中,我们来探讨一下部分可供大多数用户使用的原生解决方案。我会给出一些示例,但不会深入探讨所有细节。
在介绍各种技术之前,首先我需要提醒一下使用 JavaScript 的一大缺点:浏览器只有一个线程来控制页面的渲染。在运行 JavaScript 时,浏览器会延迟用户交互事件与界面更新。这就很讨厌了,因为你会觉得页面没有响应你的操作,或者感觉动画很卡。
谷歌工程师 Philip Walton 对这方面优化进行了详细阐述,感兴趣可以前往查看:https://calendar.perfplanet.com/2020/html-and-css-techniques-to-reduce-your-javascript/
开发团队的日常工作比较喜欢功能强大的设备,因此会掩盖 JavaScript 带来的负面影响。但不要忘记在功能有限的设备上做定期测试。
以下是用JavaScript实现此操作的两种方法:
当页面包含大量需要截断的文本时,页面显示会被延迟。此外,这两种解决方案会完全截断文本,有可能会影响到搜索引擎或辅助技术的恢复。页面中元素的字体大小或宽度也有可能发生变化。考虑所有的情况很麻烦。
-webkit-line-clamp 是一个原生的 CSS 属性。十年前在 Safari 中引入,如今已被广泛使用,其他浏览器出于兼容性原因也采用了这个属性,而且已成了标准。你需要一些其他带前缀的属性来实现所需的行为。虽然使用带有前缀的属性性有点烦人,但是标准中已经详细描述了该前缀,因此这样做不会有风险。
除了 IE 和 Firefox 68 之前的版本外,所有浏览器都支持该属性。下面的例子说明了具体的使用方式。
HTML :
<div class="demo-grid">
<div class="demo-card">
<img width="200" height="200" src="https://placekitten.com/200/200?image=1" alt="" class="demo-img">
<h1 class="line-clamp demo-title">
Spill litter box, scratch at owner, destroy all furniture, especially couch
</h1>
</div>
<div class="demo-card">
<img width="200" height="200" src="https://placekitten.com/200/200?image=2" alt="" class="demo-img">
<h1 class="line-clamp demo-title">
Claws in the eye of the beholder
</h1>
</div>
<div class="demo-card">
<img width="200" height="200" src="https://placekitten.com/200/200?image=3" alt="" class="demo-img">
<h1 class="line-clamp demo-title">
Relentlessly pursues moth eat too much then proceed to regurgitate all over living room carpet while humans eat dinner
</h1>
</div>
</div>
<!-- Titles via http://www.catipsum.com/ -->
CSS:
/* By default, truncate the text abruptly */
.line-clamp {
/* Careful computing the max-height, it needs to match n * line-height */
max-height: calc(2 * 1.15 * 1.5rem);
overflow: hidden;
}
/* For capable browsers, truncate with an ellipsis */
/* To simulate a browser without support, you can add a s after clamp in the following line */
@supports (-webkit-line-clamp: 2) {
.line-clamp {
/* Remove the made up max-height */
max-height: none;
/* Those three properties are mandatory, so is overflow: hidden that we defined earlier */
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
}
}
/* Extra code for the look of the demo */
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem,1fr));
gap: 1rem;
margin: 1rem;
}
.demo-card {
background-color: #E2E8F0;
border-radius: 0.5rem;
display: flex;
flex-direction: column;
padding: 0.5rem;
}
.demo-img {
align-self: center;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
}
.demo-title {
font-size: 1.5rem;
margin: 0;
}
该解决方案没有性能或页面内容偏移的问题,而且还不会影响搜索引擎或辅助技术。但是,它不适合拥有多个子项的元素。
有时候,你希望页面的某个部分始终显示在视野范围内,比如标题、工具栏或购物车。我经常遇到这种行为,但能够正确实现的却很少。
如果想通过 JavaScript 实现这个功能,则必须监听频繁触发的滚动事件。大多数解决方案经常会通过限流或去抖动的技术去除大部分事件。现如今,我们可以使用 IntersectionObserver ,仅在元素进入或离开窗口时接收事件。这样做的效率更高。
在检测到元素进入或离开窗口时,我们需要从 position:relative 切换到 position:fixed 。这需要浏览器重新计算大量元素的大小和位置(我们称之为页面重新布局),这种做法的代价很大。我们需要确保周围元素不会四处移动,而且不会导致内容跳跃。
如果在元素进入或退出窗口时,渲染被阻塞(如果在滚动的同时协调使用动画,则很可能发生阻塞),那么切换将被进一步延迟。
CSS 有一个属性 position:sticky 可以实现这种行为,而且还没有性能、响应性或内容跳跃的问题:只要浏览器可以滚动,它就会将元素准确地定位到你声明的位置上。你可以利用 top 、 bottom 、 left 或 right 选择定位。
html
CSS
除了IE和旧版的 Chrome 或 Firefox 之外,所有浏览器都支持sticky。对于这些旧版的浏览器,元素仅支持默认值 position:static,而且不会处理 top、bottom、left 和 right 的值。如果你需要支持这些浏览器,则请记住这一点。旧版的 Safari 需要 -webkit-sticky 前缀。
但是,这个属性有一个限制:无法根据元素是否sticky来改变元素的外观,比如使用:stuck之类的伪类。这是 CSS常见的限制。在这种情况下,我建议使用 position:sticky 来设置sticky属性,同时结合 IntersectionObserver来改变其外观(注意不要改变大小,以防止内容跳跃)。
如果想在JavaScript中实现这一点,你需要定期执行改变滚动位置的JavaScript。为了动画能够流畅地运行,在整个动画运行过程中,不能有其他JavaScript阻塞渲染。
此外,你还需要选择一个计时函数。为了看起来很自然,可能需要针对每个操作系统使用不同的计时函数,才能符合该操作系统的常规做法。
CSS有一个属性 scroll-behavior: smooth 和 {behavior: 'smooth'},可以代替JavaScript的 scroll 、 scrollTo 和 scrollIntoView ,将所有有关计时的决定都交给CSS。这样可能更符合常用设备的常规做法。
Safari 尚不支持此功能(除非启用隐藏选项),但大多数情况下,这不是什么大问题。
html
CSS
无论是JavaScript版还是原生版,你都需要注意两个可访问性方面的问题:遵循尽可能减少动画和页面移动的设置,以及确保焦点正确移动。
通过这种方式,可以创建幻灯片、水平列表,吸附到每张图片或每一节,让它们占据整个窗口。
为了创建幻灯片,我们需要监听:
正确处理所有指针事件(鼠标事件或触碰事件),并处理鼠标指针离开区域的事件非常需要技巧。如果能正确处理这些事件,就可以相应地移动元素。每次移动都可能导致昂贵的重新布局,破坏显示效果。
如果每一节都占据整个窗口或遇到水平列表,我们必须监听所有滚动事件,并用我们需要的滚动处理替换。获得理想的效果非常困难,因为我们需要完整地控制原生的滚动行为。
不论何种情况,你都需要根据原始的页面移动速度和距离来确定是否应该移动到下一个项目。如果你的选择不符合系统的行为,就会给用户造成困扰。
CSS的滚动吸附功能可以处理该行为。在滚动的容器中,定义 scroll-snap-type 来指示吸附的方向,以及吸附必定发生还是仅在接近吸附点时发生。然后在容器的子元素中定义 scroll-snap-align 来标明吸附点。
下面的演示完全没有使用JavaScript。它还使用了 scroll-behavior 来提示用户使用正常的滚动机制。
选中复选框,即可使用 IntersectionObserver 在缩略图中高亮显示当前的图片。
html
CSS
JS
所有现代浏览器都支持该行为。还有另一种语法(https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Scroll_Snap/Browser_compat)但我不建议使用。它只会增加测试的负担,而你可以依靠优雅降级来解决该问题。在不支持滚动吸附的浏览器中,该功能将会降级为正常的滚动。
不论对于鼠标的情况还是触摸屏的情况,由于使用了浏览器的滚动功能,该方法要比JavaScript的方法流畅许多。
用JavaScript实现该功能需要使用类似于 <img src="..." srcset="..." alt="..."> 的语法。当图像接近视口时,使用Javascript改变图像的属性,以加载并显示图片。
这种方法的主要缺点就是,在相应的JavaScript执行之前图像不会被显示。而且这种情况发生的频率远超你的想象。搜索引擎也很难看到图像,因为本质上图像并不存在,而且爬虫也不会滚动屏幕。
选择何时触发加载非常重要。怎样根据当前的带宽来决定当图像距离视口多远时进行加载?是否应当考虑滚动的速度?
去年,所有浏览器(Safari除外)都实现了 <img> 元素的 loading="lazy" 属性。如果你的网站会加载所有图像,那么可以尝试下这个属性。几乎不需任何代价,就可以让网站加载变快。
如果你已经使用了某种延迟加载技术,那么在Safari支持该属性之前,你需要根据自己的情况做决定。是否值得放弃Safari的延迟加载,来换取更简单的代码?
目前,触发下载的规则由各个浏览器决定,可能不是最佳时机。不过有一点可以确定,浏览器的决定会越来越理想,而不需要改变任何代码!
我希望这篇文章可以给你一些启示,下次在寻找某个 JavaScript 库来实现某项功能时,可以考虑一下这些技术。此外,你也可以看看其他我没有提及的 HTML 或 CSS 技术(比如 <details> 和 <summary> ,或 <datalist> )。浏览器在不断发展,会不断带来惊喜,用户也会受益!
少未使用或者正在使用的 JavaScript 代码是现代 Web 开发者的一个必备技能,也是提升页面加载速度的一个关键因素。
随着软件工程的发展,人们总是希望网站速度更快、效率更高、JavaScript 有效载荷更小。未使用的 JavaScript 会给 Web 应用程序增加不必要的负担,并降低整体性能。
在本文中,您将了解到一系列模式和技巧,这些模式和技巧可用于通过删除未使用的 JavaScript 来减少这种臃肿,从而帮助您节省时间、优化性能和提高效率。
简单地说,未使用的 JavaScript(通常称为Dead Code)是指 Web 应用程序不使用或不需要的任何代码,但却存在于您发送给浏览器的最终 JavaScript 包中。这些“僵尸代码”处于休眠状态,会增加 JavaScript 包的整体大小,从而影响页面性能。
造成 JavaScript 程序中未使用代码的原因有很多。最明显的原因是您可能添加了不再需要的代码,但在最终的包中忘记删除它。这些可能是在您的应用程序中不再被执行、调用或使用的函数、类或变量。另一个原因可能是未使用的依赖项。换句话说,您可能在代码中使用了一个您不需要的第三方JavaScript依赖项。更糟糕的是,这些依赖项可能还带有它们自己未使用的JavaScript,进一步增加了项目中不必要的庞杂性。
您可以通过几种方法从 Web 应用程序中移除未使用的 JavaScript。这些提示和模式将帮助您将更强大、更高效的 JavaScript 包发送到页面上,无论您是使用纯 JavaScript 还是任何专用的库或框架,如 React、SolidJS 或 Vue.js。
代码拆分是一种将 JavaScript 代码拆分为更小、更可管理的模块的技术。然后,您可以按需或并行网络请求加载这些块,这意味着您不必每次都加载整个 JavaScript 包,只需加载必要的部分。
想象一下,您有一个像下面这样的单个JavaScript包:
// 60kb file
<script src="mainbundle.js"></script>
您可以将其分割成小块,只在需要时下载:
<script async defer src="chunk1.js"></script> // 20 kb file
<script async defer src="chunk2.js"></script> // 20 kb file
<script async defer src="chunk3.js"></script> // 20 kb file
这一策略减少了初始化和下载 JavaScript 脚本的主线程的整体网络负载。如果您使用的是 Next.js 或 Vue 等 JavaScript 库或框架,您就不需要手动执行此操作,因为大多数现代 JavaScript 框架都默认支持代码拆分。
不过,您可以将某些组件仅仅给服务器端使用。这有助于框架 "聪明地" 将 JavaScript 分割成更小的块,这些块不应与客户端 JavaScript 块捆绑在一起。许多企业都在生产中使用这种策略,这足以说明它的效率。
Tree shaking 是指消除僵尸代码,即应用程序不需要的 JavaScript。许多流行的捆绑程序(如 webpack、Rollup 或 Vite)在构建 JavaScript 块并将其发送到浏览器时,都会使用Tree shaking方法。
为确保捆绑包中的Tree shaking,请始终在 JavaScript 组件中使用现代 ES6 语法,即导入和导出语法:
// default import
import Navbar from 'Components'
// named import
import { Navbar } from 'Components'
// default export
export default Navbar
// named export
export { Navbar }
现代 ES6 语法可帮助您的捆绑程序识别死代码,这与 ESLint 如何指出是否存在导入组件但该组件未在任何地方被使用类似。
捆绑包中的 JavaScript 越少,浏览器下载捆绑包所需的时间就越短。为确保您的 JavaScript 尽可能精简,请务必在发布前将其最小化。
对 JavaScript 进行最小化处理,可以去掉代码中的空白、语法高亮、注释和其他在最终产品构建中不需要的部分。这些不必要的代码段会占用捆绑包中的空间,成为死代码。
即使是看似简单的 JavaScript 代码,也可以进行压缩和修改。下面是一个精简前的简单 JavaScript 代码示例:
// add function
const add=(a, b)=> {
return a + b
}
// call add function
add(3, 4)
压缩之后的代码:
const add=(d,a)=>d+a;add(3,4);
想象一下对 JavaScript 进行最小化会对大型捆绑包产生怎样的影响!
JavaScript 压缩比想象中的要容易得多。您可以从 Terser、Ugligy、babel-minify 等许多在线 JavaScript 压缩工具中进行选择。
一个可以节省大量网络带宽时间的小窍门是始终异步加载 JavaScript。
其中一种方法是在 JavaScript 脚本中添加 async 和 defer。这将自动处理 JavaScript 的下载,并且在加载 JavaScript 时不会延迟或阻止 HTML 的解析或呈现。
async 和 defer 属性处理 JavaScript 脚本下载和执行的顺序略有不同。您可以选择最适合自己项目的方式。
async JavaScript 脚本的工作原理如图所示:
defer JavaScript 脚本的工作原理如图所示:
在许多情况下,同时添加 async 和 defer 就能正确完成工作。下面是一个如何编写的示例:
<script async defer src="bundle.js"></script>
由于有了 ES6 模块,现在可以在纯 JavaScript 中实现动态导入。当您想按条件地加载 JavaScipt 模块或脚本时,动态导入尤其有用。下面是动态导入的代码示例:
import('./utility.js')
.then((module)=> {
// use utility code here
})
.catch((error)=> {
// catch errors
});
与在文件顶部导入每个 JavaScript 组件相比,这种策略使我们能够在满足特定条件后请求 JavaScript 捆绑程序。请看下面的代码片段,其中只有在附加事件监听器后才会加载模块:
const $dashboard=document.getElementById('dashboard');
$dashboard.addEventListener('click', ()=> {
import('./utility.js')
.then((module)=> {
// Use utility module APIs
module.callSomeFunction()
})
// catch unexpected error here
.catch((error)=> {
console.error("Oops! An error has occurred");
});
});
JavaScript 中的懒加载是一种简单但相当有用的模式。如果操作得当,懒加载可以帮助你节省网络带宽。基本规则是只加载当前需要的 JavaScript 模块。
您可以遵循的一种模式是始终按照视口高度加载 JavaScript。假设您有一个非常庞大的用户列表。您可能不想加载第 300 个用户的信息,因为这些信息在当前视口中并不需要,甚至不可见。
针对这种特殊的使用情况,有一些非常出色的库,它们委托 JavaScript 模块仅在指定用户(如第 300 个用户)到达当前视口时加载信息。
懒加载不仅限于列表。图片、视频、大量节点等资产也可以懒加载。例如,你可以在浏览器下载实际图片及其相关 JavaScript 之前放置一个占位符。
这些模式不仅可以帮助您减少 JavaScript 代码,还能极大地改善用户体验。
随着 JavaScript 生态系统的不断完善,您的代码和使用的库也应跟上任何变化。这将有助于使您的代码在未来的版本中更加健壮和安全。
如果您在代码中使用任何第三方库,请确保始终使用最新版本,并检查它们是否已被废弃。许多库,尤其是由志愿者维护的小型开源库,可能会意外过时或被废弃。
在基于 npm 或 Node.js 的开发环境中,您可以运行以下代码来检查库是否被弃用:
npx depcheck
外部依赖关系或第三方库的选择会影响 JavaScript 的传输量。请务必确保您为项目选择的任何库都得到了积极维护,使用了最新的 ECMAScript 版本,最重要的是,它们都是轻量级的。
在本文中,我们讨论了可以用来减少 JavaScript 的交付量并提高项目性能的八种方法。因每个项目都不尽相同,使用的架构和模式也不尽相同,本文提到的很多技巧对您的项目可能都适用,但有些可能不适用,在使用时需要根据实际环境和情况灵活选择。
*请认真填写需求信息,我们会在24小时内与您取得联系。