前端工程化浅析
-
目标:通过技术进步与经验积累带来的方案,来解决项目开发、测试、维护阶段遇到的低效、繁琐问题
-
工程化是一种思想,技术是一种实践
-
为什么要工程化:提高效率【开发、测试、维护阶段】
-
规范化、模块化、组件化、自动化
规范化
- 是项目可维护性的基石
- 版本管理及开发流程规范
- 多人协作
gitgit flow- 简化git操作
- 行为规范
- 版本管理及开发流程规范
git flow 常用分支
- Production 分支
也就是我们经常使用的Master分支,这个分支最近发布到生产环境的代码,最近发布的Release, 这个分支只能从其他分支合并,不能在这个分支直接修改
- Develop 分支
这个分支是我们是我们的主开发分支,包含所有要发布到下一个Release的代码,这个主要合并与其他分支,比如Feature分支
- Feature 分支
这个分支主要是用来开发一个新的功能,一旦开发完成,我们合并回Develop分支进入下一个Release
- Release分支
当你需要一个发布一个新Release的时候,我们基于Develop分支创建一个Release分支,完成Release后,我们合并到Master和Develop分支
- Hotfix分支
当我们在Production发现新的Bug时候,我们需要创建一个Hotfix, 完成Hotfix后,我们合并回Master和Develop分支,所以Hotfix的改动会进入下一个Release
- 编写规范
- 脚本、样式、目录结构、命名、路由等等
模块化
- 按照逻辑将代码进行模块划分
CSS模块化解决方案
- 核心思想:通过样式生效规则来避免冲突
scoped:组件里的样式不会影响其他组件- 给DOM节点添加
data-v-version属性,进行一一对应 - 参考掘金
- 给DOM节点添加
CSS in JS:以脚本模块来写样式,封装好的样式模块可直接调用- React常用
- 将CSS样式作为一个组件
- 参考Instagram
- 预编译:使样式成为脚本中的变量
- BEM:
Block_Element-Modifier,按照规则手写CSS,并在模块内增加相应class- ElementUI
- 借助可编程的CSS来写,
Sass等
Shadow DOM
JS模块化解决方案
Node.js——CommonJS- 通过require()引入模块依赖,require函数可以引入Node的内置模块、自定义模块和npm等第三方模块。
- ES6 module
- ES6的模块化已经不是规范了,而是JS语言的特性。
- 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- 模块化规范是运行时加载,ES6 模块是编译时输出接口。
- ES6的模块化已经不是规范了,而是JS语言的特性。
组件化
- 组件化与模块化的核心思想都在于分治,带来的好处是团队协作效率和项目可维护性的提升
- Vue、React
- 什么是组件
- UI为主
- 以UI块为划分标准
- 逻辑为主
- 按照功能逻辑划分
- UI为主
- 组件:由特定逻辑和UI进行的高内聚、低耦合的封装体
自动化
- 是工程化的核心
- 自动初始化
- vue-cli
- 事半功倍
- 自动构建、打包
- webpack
- 自动测试
- karma,jest
- 单元测试、集成测试、端到端测试、UI测试
- 自动部署
- Jenkins
写一个基于Node.js的cli
-
捕获用户输入的参数和命令,获得参数并触发回调
- 使用
commander库
const program = require('commander') program.on('--help', _=>{}) program.command('init').action((name,options) => {}) - 使用
-
触发询问与用户交互
inquirer库
const inquirer = require('inquirer') inquirer.prompt({ type: 'confirm', nameL 'name', message: '是否将页面发布', default: true }).then(answer => {}) -
执行命令
child_process、发送HTTP请求http -
增强交互效果
chalk
Webpack构建项目
- 配置参数
- module下的rules属性是最关键的,定义了什么文件用什么插件编译
- 不同环境的配置进行区分
- base/dev/prod
- 集成进来的工具的插件配置单独放置
- babel/eslint等
- env配置使用.browserslistrc文件单独放置
JS动画原理与实现
动画的基本原理
- 定时器改变对象的属性
- 根据新的属性重新渲染动画
JS动画
-
采用增量的方式实现动画,虽然简单,但是不容易精确控制
-
通过时间控制更合适,比如设置角度
requestAnimationFrame(function update() { if(!startTime) { startTime = Date.now() } const p = (Date.now() - startTime)/T block.style.transform = `rotate(${360*p}deg)` requestAnimationFrame(update) }) -
进行通用化,比如用canvs渲染
function update({ context }, { time }) { context.clearRect(0, 0, 512, 512); context.save(); context.translate(100, 100); context.rotate(time * 0.005); context.fillStyle = '#00f'; context.fillRect(-50, -50, 100, 100); context.restore(); } class Ticker { tick(update, context) { let count = 0; let startTime = Date.now(); requestAnimationFrame(function next() { count++; const time = Date.now() - startTime; if (update(context, { count, time }) !== false){ requestAnimationFrame(next); } }) } } -
使用Timing类
class Timing { constructor({ duration, easing } = {}) { this.startTime = Date.now(); this.duration = duration; this.easing = easing || function (p) { return p }; } get time() { return Date.now() - this.startTime; } get p() { return this.easing(Math.min(this.time / this.duration, 1.0)); } } class Ticker { tick(update, context, timing) { let count = 0; timing = new Timing(timing); requestAnimationFrame(function next() { count++; if (update(context, { count, timing }) !== false) { requestAnimationFrame(next); } }); } } -
匀速运动
- update 传入
200*timing.p
-
自由落体运动
- p -> p^2
-
摩擦力
- p -> p*(2-p)
-
可以通过随机组合实现更多效果
-
贝塞尔函数
-
椭圆轨迹
- 根据代数方程/参数方程
-
周期运动
-
椭圆周期运动,如下:
function update({ target }, { timing }) { const x = 150 * Math.cos(Math.PI * 2 * timing.p) const y = 100 * Math.sin(Math.PI * 2 * timing.p) target.style.transform = `translate(${ x }px, ${ y }px)` } const ticker = new Ticker(); ticker.tick(update, { target: block }, { duration: 2000, iterations: 10 }) -
连续运动
- 返回一个promise,用await来逐步执行
-
逐帧动画
- 使用background-position来改变图片位置
- 使用
SetInterval()每隔一段时间换一次class
-
Web Animation API
- 传入关键帧,与CSS是对应的
- 封装成promise,达到逐个小球运动的效果
前端性能优化
- 为什么做:与用户体验相关,决定了用户去留。希望发现网站的性能瓶颈,从而提升用户体验
RAIL模型——以用户为中心
- Response
- 响应:50ms处理事件
- 目标:在100ms内响应用户输入
- 指导:
- 50ms内处理用户输入事件,确保100ms内反馈用户可视的响应
- 对于开销大的任务可分隔任务处理,或者放到worker进程中执行,避免影响用户交互
- 处理时间超过50ms的操作,始终给与反馈(进度和活动指示器)
- 响应:50ms处理事件
- Animation
- 动画:10s 一帧
- 目标:10ms或更短的时间内生成一帧
- 1s内生成60帧,再减去开销
- 视觉平滑
- 指导:动画尽量不要处理逻辑,提高达到60fps的机会
- 动画类型:滚动、视觉动画、拖拽动画
- 目标:10ms或更短的时间内生成一帧
- 动画:10s 一帧
- Idle
- 空闲时间
- 目标:最大化空闲时间以增加页面在100ms内响应用户输入的几率
- 指导:利用空闲时间完成推迟的工作
- 空闲时间
- Load
- 加载:5s内呈现交互内容
- 目标:首屏加载连接3G缓慢的中档移动设备5s内呈现可交互内容
- 非首屏加载应该在2s内完成
- 指导:
- 测试用户常用设备和网络连接情况的性能
- 优化关键渲染路径以接触阻止渲染
- 启动渐进式渲染和在后台执行一些工作
- 影响加载性能的因素:
- 网络速度
- 硬件
- 解析JS
- 会阻止页面加载
- 目标:首屏加载连接3G缓慢的中档移动设备5s内呈现可交互内容
- 加载:5s内呈现交互内容
延迟与用户反应:
- 100ms 以内用户会感觉可以立即获得结果
工具篇
- lighthouse
- 可以选择设备类型、功能、限制CPU等
- chrome DevTools
实战篇
浏览器渲染场景
- JS/CSS =》 计算样式 =》 布局 =》 绘制 =》 渲染层合并
- JS/CSS =》 计算样式 =》 绘制 =》 渲染层合并
- JS/CSS =》 计算样式 =》 渲染层合并
csstriggers.com查看每个属性影响的范围
浏览器渲染流程
- JS
- 实现动画,操作DOM
- Style
- 产出渲染树
- Layout
- 盒模型,确切的位置和大小
- Paint
- 栅格化,完整显示
- Composite
- 渲染层合并
- 解析html建立dom树
- 解析css构建render树(将CSS代码解析成树形的数据结构,然后结合DOM合并成render树)
- 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上。

- 使用chrome DevTools的performance选项查看性能
- 在sources中可以查看代码的耗时情况
- 优化方向:尽量不要在设置样式之后读取它的样式属性
- 用transform属性来移动元素
- 在sources中可以查看代码的耗时情况
性能优化方向
- 加载
- 资源效率优化
- 图片优化
- 字体优化
- 关键渲染路径优化
- 渲染
- JS执行优化
- 避免大型复杂的布局
- 渲染层合并
其他优化方法
- 样式表放在head标签中,脚本放在body结束前**。
- 会减少页面首屏出现的时间,使页面内容逐步呈现,改善用户体验,防止“白屏”。
- js的下载和执行会阻塞Dom树的构建(严谨地说是中断了Dom树的更新),所以script标签放在首屏范围内的HTML代码段里会截断首屏的内容。
- 使用GPU加速
- 使用CDN
- 尽可能使用字体图标,减少图片的使用
- 将多个样式表或者脚本文件合并到一个文件中,可以减少HTTP请求的数量从而缩短效应时间。
- 避免重定向
- 当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载。
- 设置缓存
- 通过在响应头添加cache-control字段