本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!css
本文主要讲述的是某种心路历程: 一枚小白成长到大厂须要的高级前端开发工程师。html
技术文,干货多,放心观看呢。前端
“来,喝!谁也别是个怂瓜蛋哈!”vue
说完,感情深,一口一口闷。node
喂喂,这人均低消¥1600
,你这么喝下去怕不是要把我喝垮咯。react
咱们leader层以上的人都被喊进了小黑屋开会,主题只有一个:要裁人。 这场看起来很是热闹的聚会,我自掏腰包撺的。你说,一家上市公司,要我给裁人名单,我该怎么作。webpack
“老大,来,喝一个。我在老大这学到了不少,技术是真牛哈!”web
碰杯,干了一个。面试
“我一直觉得 Redis
是那些写 Java
的人要搞的事情,没想到老大带着咱们把Redis
给搞出来了。这下之后都不用看那些服务端的脸色了! 老大,走一个!”算法
继续碰杯,走一个。 目前应该就属我喝了最多的钱下肚。
“老大,我是负责搞调优的。我之前只知道作作http缓存
,搞搞懒加载
什么的。没想到在老大这里学到了高了好几个维度的调优方法。来,老大陪我干一个。”
喂喂,不会已经上头了吧?不该该是你陪老大干一个嘛。 好吧,继续碰杯,再走一个。
“喂喂,你们听我讲一下,我给你们讲一个老大的糗事如何?”
团队里为数很少的妹纸,果真无论哪一行的女生都喜欢分享八卦这种东西。有人唱戏,天然有人捧场。一波波起哄下,一个小故事出来了。
“我刚来公司不久的时候,老大老是把我提交的代码给打回来,甚至有次,我提交了6次代码,每次都被打回来了。而后我就哭了,一半是气的,一半是怕的。我记得我当时还特地问了问大家,老大是否是特别严格,会把大家提交的代码打回来。而后大家告诉我,大家都没有被老大打回过。”
我开始捂住本身的脸,这种事情都能拿出来讲,也不光彩吧?
“而后吧,这都不是重点。重点是,那天晚上老大竟然请我吃饭了。白天打回个人代码有多狠,晚上请吃饭就有多卑微。老大边吃饭边给我道歉,说个人代码写得不符合标准,页面render
的次数多了。例如:”
function Com (){
const [price,setPrice] = useState(0); // 初始化0 第1次
useEffect(()=>{
// fetch data
setPrice(10.8); // 拿到真实价格 第2次
},[price])
return <div>¥{price}</div>
}
// 应该改为一下风格
Api.fetch().then(
(props)=>{
render(React.memo(Com),props)
}
)
function Com (props){
const [price,setPrice] = useState(props.price); // 初始化0 第1次
useEffect(()=>{
// code
},[price])
return <div>¥{price}</div>
}
复制代码
“大家你们看看,虽然我以为有道理,但由于这就打回了6次,老大,求你好好作我的吧😂。 最后说着道歉的话再继续让我修改代码。”
噢,耶!大章第一个没忍住,喝进去的酒都喷了出来。 我知道,你们就留我在原地尴尬😅。我依然保持着笑容,微笑着面对着惨淡的世界。
“哎,大家都等等,等等! 这算个啥? 有时候视觉都会给大家切那种毛玻璃效果的小图吧?然而,有一次老大以为咱们使用图片资源太多了,就逼着咱们用css写出毛玻璃的效果。 大家说,老大是否是个狠人!”
<div class="mark"> 老大喜欢手动整的毛玻璃效果</div>
复制代码
$img: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/388f7a44af6b4ea3af893a1fcbfadd71~tplv-k3u1fbpfcp-watermark.image';
body {
height: 100vh;
display: flex;
background-image: url($img);
}
.mark {
position: relative;
margin: auto;
width: 500px;
height: 400px;
background-color: rgba(255, 255, 255, 0.5);
overflow: hidden;
z-index: 10;
line-height: 300px;
text-align: center;
color: #FFFFFF;
font-size: 20px;
&::before {
content: "";
position: absolute;
background-position: top;
filter: blur(8px);
background-image: url($img);
background-repeat: no-repeat;
background-attachment: fixed;
background-size: cover;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
}
}
复制代码
啊,喂,喂!我记得我没逼你啊!咱们不是共同探讨的吗?
“哎呀,大家这跟个人事比起来都正常,不算啥好伐?”
不擅言辞的小白,咱们组少数几个女生,也是最漂亮的一个。上海本地儿人,姓白,对穿搭很在行。因此,自从她加入咱们第一天,来找她“滋事”的人就很多。有的是借工做上的原油来找她,有的甚至就干脆路过都要找她闲聊几句。哪怕她不怎么擅长应付这些人,但依然天天都有人……
唉,颜值即正义啊!可没想到小白也要吐槽我吗?
“大家是不知道,公司不是发了邮件说要组织舞蹈队,找老大报名。我吧没敢直接问老大,老大也不安排我,而后我天天穿不一样的好看的衣服,就但愿老大看见了能把我报名报上去。可结果呢,大家知道有多气吗!”
突然地,特别安静了一下子。大章着急催着。
“小白,你快接着说啊!你们都好奇你有多气呢!”
“结果嘞!我记得我换第三套衣服的时候,也就是第三天,老大忽然告诉我,叫我之后晚上要早点下班,不要总加班。原来老大是觉得我脱单处对象了!”
“噗~!”
好几个嘴里还喝着的没忍住,都喷了。而后笑了一地……
是啊,这群人太可爱了。我能带出这样一波人,我很知足。
终究仍是大章选择离开。这是我跟大章商量好的,我给他推荐一个很好的机会,他还能拿到这边的 N+1
赔偿。意外的是,小白来找我了。
小白说她的同学好友在隔壁团队,已经获得消息她在被裁名单上。小白想本身离开,而后拜托我去把她捞过来。有种一命换一命的感受。
小白还说,由于她的名字里带个“白”字,因此叫她小小白。小白求了我2天,这件事情得分开说。我不会主动让小白离开,是由于小白的技术还不够,不能和大章同样能出去独当一面。若是是小白因本身的事提出离职,我或许没办法阻拦,但这种一命换一命的作法,我实在没办法苟同。
能正常执行需求,代码书写经验足。
能理解经常使用技术栈的原理,并在代码层面写出性能较好的代码,且能hold住一个项目。
能独挡一面。能及时解决线上响应的问题,并能提供技术解决方案。
能单独承担商业解决方案。
技术上广度要有,还要有深度。管理方面能hold住业务,能搞好团队建设,能提高团队总体实力水平。
大章已经资深边缘徘徊了,而小白还处于中级阶段。至于小小白,只在初级阶段。
最终,小白和小小白请我吃了碗酸辣粉,搞定了我。小小白,22岁,工做了2年。20岁就名校毕业,真的是个学霸了。一碗酸辣粉的时间就把优化算法给我讲了透。
基于混沌时间序列分析的神经网络了解一下?
噢,不懂是吧?换个方式说,这种优化算法主要用途之一是用来预测。例如用来预测下一个月内的用户行为,分析出用户画像。你手机里那些购物APP是否是都有按你的喜爱推荐商品的功能?为啥购物APP能知道你喜欢哪一类商品?其中的手段之一就是预测。
我了个去,人才啊!我突然以为我庙小了。我想推荐小小白去算法团队,被拒绝了。不是被算法团队拒绝了,是被小小白拒绝了。小小白说要在我这修炼半年,修炼好了就去大厂,让曾经那些看不起她以为她菜的人仰着头看她。
好咯。年轻真好,那就开始修炼吧。我很愿意看见半年后小小白来打个人脸,去打那些人的脸。Flag就立在这了。
作好一件事情,成就一批人。 这是我一直在作的事情。
人来了以后,我作了第一次技术分析。没别的,先把React Hooks原理搞懂。
renderWithHooks
ReactCurrentDispatcher
对象mountWorkInProgressHook
生成hooks链表mountState
来初始化 useState
dispatchAction
来控制无状态组件的更新mountEffect
初始化 useEffect
mountMemo
初始化 useMemo
mountRef
初始化 useRef
updateWorkInProgressHook
找到对应的 hooks
更新 hooks
链表updateState
获得最新的 state
updateEffect
更新 updateQueue
updateMemo
判断 deps
,获取or更新缓存值update
获取 ref
对象先举个栗子:
import {useState,useEffect,memo} from 'react';
const Com = React.memo(({name})=><div>{name}</div>)
function App(){
const [ num , setNumber ] = useState(0);
const [ name , setName ] = useState('小白');
const handerClick=()=>{
for(let i=0; i<5;i++ ){
setTimeout(() => {
setNumber(num+1)
console.log(num)
}, 1000)
}
}
useEffect(()=>{
setName(num % 2 ? '小白' : '小小白')
},[num])
return <div> <Com name={name}/> <button onClick={ handerClick } >{ num }</button> </div>
}
复制代码
咱们去看 useState
的源码: react/src/ReactHooks.js
export function useState(initialState){
const dispatcher = resolveDispatcher(); // 1
return dispatcher.useState(initialState);
}
复制代码
噢,useState(initialState)
等价于 dispatcher.useState(initialState)
。 dispatcher
从中文意思上是 调度员 的意思。 也就是说你调用 useState
的时候只是通知了调度员去调度真正的 useState
。
dispatcher
又是什么?看源码。
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current
return dispatcher
}
复制代码
噢,dispatcher
是从 ReactCurrentDispatcher
身上来。咱们来把这个此分析一下,react 当前的(current)调度员(Dispatcher)。
也就是说,到这里 Dispatcher
就已经安排好了。
就是说,你的 App 组件 是何时被调用的? React 16 版本的架构能够分为三层:
Scheduler
(调度层):调度任务的优先级,高优任务优先进入协调器Reconciler
(协调层):构建 Fiber 数据结构,比对 Fiber 对象找出差别, 记录 Fiber 对象要进行的 DOM 操做Renderer
(渲染层):负责将发生变化的部分渲染到页面上咱们知道 render
一个组件 首先要构建 组件的 Fiber 链表
。因此咱们来看协调层的源码:react-reconciler/src/ReactFiberBeginWork.js
renderWithHooks(
null, // current Fiber
workInProgress, // workInProgress Fiber
Component, // 函数组件自己
props, // props
context, // 上下文
renderExpirationTime,// 渲染 ExpirationTime
);
……
renderWithHooks(
current, // current Fiber
workInProgress, // workInProgress Fiber
Component, // 函数组件自己
props, // props
context, // 上下文
renderExpirationTime,// 渲染 ExpirationTime
);
复制代码
咱们先看 renderWithHooks
几个咱们咱们最熟悉的参数。Component
是函数自己,props
是咱们传给函数组件的信息,context
表明当前的上下文。
那,有木有可能咱们的 Component
就是在 renderWithHooks
方法里被调用的?接着看源码(精简了一下)。
function renderWithHooks( current, workInProgress, Component, props, secondArg, nextRenderExpirationTime, ) {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.expirationTime = NoWork;
// 3 很重要!
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg); // 2
// code ...
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children; // end
}
复制代码
噢,renderWithHooks
的返回值是 children, 而 children = Component(props, secondArg);
破案了,咱们的函数组件就是在 renderWithHooks
被调用且最终 return
回来。
咱们再回到 3 ,ReactCurrentDispatcher.current
是否是前面没解释清楚的 调度员 的归宿?! 解释一下这行代码: 当 current
为 null
或者 current
的 memoizedState
属性为 null
就把 HooksDispatcherOnMount
赋值给咱们的调度员, 不然就把HooksDispatcherOnUpdate
赋值给咱们的调度员。
从这两名称上又能看出个大概来,一个是 Mount
的 调度员,一个是 Update
的调度员。那也就是说,初始化 hooks
的时候就是 Mount
调度员,要更新的时候就是 Update
调度员?!
ok,案子到这算是破了80%了。
workInProgress
: 从名称分析,就是工做进度 或者 正在进行中的工做 的意思吧? 那它是个对象吧? 那对象身上确定会有一些属性用来描述不一样信息对吧?
workInProgress.memoizedState
:
useState
,保存 state
信息useEffect
,保存 effect
对象useMemo
, 保存缓存的值
和 deps
useRef
, 保存 ref
对象。也就是说,workInProgress.memoizedState 存放的是 咱们所使用的hooks 的信息。
这里的 workInProgress.updateQueue
后面再提。
useState
的时候发生了什么。先看精简源码。
function mountState( initialState ){
const hook = mountWorkInProgressHook();
//若是 initialState为函数,则执行initialState函数。
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null, // 待更新的内容
dispatch: null, // 调度函数
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState, // 最新一次渲染的 state
});
// 负责更新的函数
const dispatch = (queue.dispatch = (dispatchAction.bind(
null,
currentlyRenderingFiber,
queue,
)))
return [hook.memoizedState, dispatch];
}
复制代码
噢,这个代码明显要更容易分析一些。
const hook = mountWorkInProgressHook();
initialState
进行判别。接着将 initialState
赋值给 hook.memoizedState
和 hook.baseState
queue
,信息看注释。const [x,setX] = useState(initialState);
复制代码
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) 复制代码
对照上述代码,S 表明 什么? A 表明什么?
setX
就是调用了 dispatchAction
吧? 源码中显示 dispatchAction
已经有了 currentlyRenderingFiber
, queue
两个参数了,那 setX
传入的参数应该就是第三个参数 action
了吧?
dispatchAction
到底干了什么?function dispatchAction(fiber, queue, action) {
// code ...
// step 1 : 初始化要更新的信息
const update= {
expirationTime,
suspenseConfig,
action,
eagerReducer: null,
eagerState: null,
next: null,
}
// 断定是否是首次更新
const pending = queue.pending;
if (pending === null) { // 证实第一次更新
update.next = update;
} else { // 不是第一次更新
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
const alternate = fiber.alternate;
// 判断当前是否在渲染阶段
if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)) {
// code ...
} else {
// code ...
// 剩下的事情交由 调度层 去完成。
scheduleUpdateOnFiber(fiber, expirationTime);
}
}
复制代码
唉,大体看看Fiber对象上有哪些属性吧。
type Fiber = {
/************************ DOM 实例相关 *****************************/
// 标记不一样的组件类型, 值详见 WorkTag
tag: WorkTag,
// 组件类型 div、span、组件构造函数
type: any,
// 实例对象, 如类组件的实例、原生 dom 实例, 而 function 组件没有实例, 所以该属性是空
stateNode: any,
/************************ 构建 Fiber 树相关 ***************************/
// 指向本身的父级 Fiber 对象
return: Fiber | null,
// 指向本身的第一个子级 Fiber 对象
child: Fiber | null,
// 指向本身的下一个兄弟 iber 对象
sibling: Fiber | null,
// 在 Fiber 树更新的过程当中,每一个 Fiber 都会有一个跟其对应的 Fiber
// 咱们称他为 current <==> workInProgress
// 在渲染完成以后他们会交换位置
// alternate 指向当前 Fiber 在 workInProgress 树中的对应 Fiber
alternate: Fiber | null,
/************************ 状态数据相关 ********************************/
// 即将更新的 props
pendingProps: any,
// 旧的 props
memoizedProps: any,
// 旧的 state
memoizedState: any,
/************************ 反作用相关 ******************************/
// 该 Fiber 对应的组件产生的状态更新会存放在这个队列里面
updateQueue: UpdateQueue<any> | null,
// 用来记录当前 Fiber 要执行的 DOM 操做
effectTag: SideEffectTag,
// 存储要执行的 DOM 操做
firstEffect: Fiber | null,
// 单链表用来快速查找下一个 side effect
nextEffect: Fiber | null,
// 存储 DOM 操做完后的副租用 好比调用生命周期函数或者钩子函数的调用
lastEffect: Fiber | null,
// 任务的过时时间
expirationTime: ExpirationTime,
// 当前组件及子组件处于何种渲染模式 详见 TypeOfMode
mode: TypeOfMode,
};
复制代码
在 React 16 中,将整个任务拆分红了一个一个小的任务进行处理,每个小的任务指的就是一个 Fiber 节点的构建。
至于Fiber链表。
React
经过链表结构找到下一个要执行的任务单元。 要构建链表结构,须要知道每个节点的:
父级节点
是谁子级节点
是谁,要知道他的下一个兄弟节点
是谁。上面已经把Fiber 对象 身上挂的属性挪列很详细了。须要你去瞅瞅。
当全部DOM的Fiber对象生成完毕,那须要执行DOM操做的Fiber就会构建出Fiber链表。至于构建Fiber 链表的原理是什么,以下代码(不是源码,只是为了看得更清晰,手动写了一波。但愿你有空也手动写一遍):
import React from "react"
const jsx = (
<div id="a"> <div id="b1"> <div id="c1"> <div id="d1"></div> <div id="d2"> <div id="e1"></div> <div id="e2"></div> </div> </div> <div id="c2"></div> </div> <div id="b2"></div> </div>
)
const container = document.getElementById("root")
/** * 1. 为每个节点构建 Fiber 对象 * 2. 构建 Fiber 链表 * 3. 提交 Fiber 连接 */
// 建立根元素 Fiber 对象
const workInProgressRoot = {
stateNode: container,
props: {
children: [jsx]
}
}
let nextUnitOfWork = workInProgressRoot
function workLoop(deadline) {
// 若是下一个要构建的执行单元存在而且浏览器有空余时间
while (nextUnitOfWork && deadline.timeRemaining() > 0) {
// 构建执行单元并返回新的执行单元
nextUnitOfWork = performUnitOfWork(nextUnitOfWork)
}
// 若是全部的执行单元都已经构建完成
if (!nextUnitOfWork) {
// 进入到第二个阶段 执行 DOM 操做
commitRoot()
}
}
// Fiber 工做的第一个阶段
function performUnitOfWork(workInProgress) {
// 构建阶段向下走的过程
// 1. 建立当前 Fiber 节点的 DOM 对象并存储在 stateNode 属性中
// 2. 构建子级 Fiber 对象
beginWork(workInProgress)
// 若是子级存在
if (workInProgress.child) {
// 返回子级 构建子级的子级
return workInProgress.child
}
// 开始构建阶段向上走的过程
// 若是父级存在
while (workInProgress) {
// 构建 Fiber 链表
completeUnitOfWork(workInProgress)
// 若是同级存在
if (workInProgress.sibling) {
// 返回同级 构建同级的子级
return workInProgress.sibling
}
// 同级不存在 退回父级 看父级是否有同级
workInProgress = workInProgress.return
}
}
function beginWork(workInProgress) {
// 若是 Fiber 对象没有存储其对应的 DOM 对象
if (!workInProgress.stateNode) {
// 建立 DOM 对象并存储在 Fiber 对象中
workInProgress.stateNode = document.createElement(workInProgress.type)
// 为 DOM 对象添加属性
for (let attr in workInProgress.props) {
if (attr !== "children") {
workInProgress.stateNode[attr] = workInProgress.props[attr]
}
}
}
// 建立子级 Fiber 对象
if (Array.isArray(workInProgress.props.children)) {
// 记录上一次建立的子级 Fiber 对象
let previousFiber = null
// 遍历子级
workInProgress.props.children.forEach((child, index) => {
// 建立子级 Fiber 对象
let childFiber = {
type: child.type,
props: child.props,
return: workInProgress,
effectTag: "PLACEMENT"
}
// 第一个子级挂载到父级的 child 属性中
if (index === 0) {
workInProgress.child = childFiber
} else {
// 其余子级挂载到本身的上一个兄弟的 sibling 属性中
previousFiber.sibling = childFiber
}
// 更新上一个子级
previousFiber = childFiber
})
}
}
function completeUnitOfWork(workInProgress) {
let returnFiber = workInProgress.return
if (returnFiber) {
// 链头上移
if (!returnFiber.firstEffect) {
returnFiber.firstEffect = workInProgress.firstEffect
}
// lastEffect 上移
if (!returnFiber.lastEffect) {
returnFiber.lastEffect = workInProgress.lastEffect
}
// 构建链表
if (workInProgress.effectTag) {
if (returnFiber.lastEffect) {
returnFiber.lastEffect.nextEffect = workInProgress
} else {
returnFiber.firstEffect = workInProgress
}
returnFiber.lastEffect = workInProgress
}
}
}
// Fiber 工做的第二阶段
function commitRoot() {
// 获取链表中第一个要执行的 DOM 操做
let currentFiber = workInProgressRoot.firstEffect
// 判断要执行 DOM 操做的 Fiber 对象是否存在
while (currentFiber) {
// 执行 DOM 操做
currentFiber.return.stateNode.appendChild(currentFiber.stateNode)
// 从链表中取出下一个要执行 DOM 操做的 Fiber 对象
currentFiber = currentFiber.nextEffect
}
}
// 在浏览器空闲的时候开始构建
requestIdleCallback(workLoop)
复制代码
自从上次分享了 React Hooks 相关的东西,安生了2个星期。小小白刻苦学习,公司加班写需求,回家继续加班写代码。
我是否是逼得人太紧了?
狗叫了(个人手机响铃声)。
“你怎么回事,运营说线上出故障了!刚答应你留我的,你就这样报答个人……”
啪!我把电话挂了。啥也没说,直接挂了。我迅速回到位置上,小小白泪流满面就差哭晕在工位上了。我这时候是否是应该喷一句: MMP,出了事就知道哭,出了问题你却是第一时间告诉我啊!
事情是这样的,小小白作了一个活动页,内容是让用户点击屏幕抢红包。问题是,用户能够点到了就抢到了红包,理论上只要手速够快,屏幕能识别用户的手势就能抢到。也就是说你点1千次,就能抢到1千个红包。 固然了,这样的说法太夸张。用户最多抢到200块的红包,后面抢再多也不会超过200块钱。
用户手速不可能快到那个程度,可小小白的活动页,抢到三、4个红包的时候动画就显得很卡顿。用户明明点选到了却没抢到红包,问题就被活动运营上报了。
了解事情大概后,个人电话已经拨给某个运营老大。
“老铁,这个问题状况如何了?”
“目前已经有几十笔投诉了,不过预计接下来流量很大。我如今已经让手下暂停推广这个活动页了。你那边怎么搞?”
“老铁,这样。你先别让暂停推广手段,你那边的流程照常进行,半小时内不要拿真正的活动地址,你能够先整个预热活动。”
电话那边沉默了十几秒。我知道她会帮个人。
“你能肯定半小时后一切正常吗?”
“能。”
“那事情要是搞定了,跟我约会。”
啪!手机丢进抽屉,神特么约会,上帝来了也别打扰我!
我走到小小白,她还趴着那哭。小白在安慰,看到我过来。
“老大,怎么作?”
“让开。”
我坐在小白的位置上。
“锁屏密码。”
“110gaosuwoyaodidiao” (110告诉我要低调)
我是记得小白今天是有一个自动埋点的SDK要上线的,不过预计是晚上,白天还在作最后的回归。
“小白,我能够相信你吗?”
“能够。”
我打开vscode,在sdk上面埋伏了一段骇客代码。主要作了几件事情:
这样作很危险。没有测试,没有验证。甚至不合法。 但没有比如今更差劲的结果了。
commit 代码。直接越过测试上预发环境(上线前的模拟线上环境)。
“小白,拿你手机出来。”
我看了下时间,已通过去15分钟了。这时候小小白中止哭了,应该是被我吓的。
“小小白,5分钟内把测试那边的机器拿来跑一下。能作到吗?”
小小白机械式的点了点头,随后跑去拿手机了。
我盯着手机时间,21分钟的时候,上线。小小白却忽然拉住我。
“老大,这里有个小米6,仍是有点卡顿。”
我看了一眼,点击红包的时候会有轻微的抖动。随即没理小小白,上线。用个人权限强行开了一个自测迭代上线。
操做完之后,就是排队等待上线了。我忽然问了句小小白。
“小小白,此次我能相信你吗?”
我看到了小小白眼里的慌张,但她仍是咬了咬下嘴唇,对我点了点头。我回位置上拿出手机,老板的未接数量呛死了个人屏幕。
我走到老板办公室,敲开了门。老板把水杯摔到门口。
“你如今牛逼了啊!不把我放眼里了啊!运营老大把事情捅到业务方那边,问题都上升到欧总(CEO)那了。你他吗躲什么躲,第一时间解决问题知道吗?”
唉。运营部也真是一地鸡毛,那我运营老铁是个北方妹纸,估计就是个运营老四老五,至于那个老大,我和他闹过矛盾。此次估计就是他把事情捅上去了。
我伪装淡定的关上门,隔绝一下外面的声音。个人直觉告诉我,个人那些娃确定都在直勾勾的看着我。
“老板,到底发生什么事了。”
“你手底下的人搞出故障了,你还问我什么事?!”
我看了下时间,差很少了。我当着老板面,拿出手机,拨电话给老铁,打开外音。
“喂,啥事?我如今忙着搞活动。”
“不是出故障了吗?”
“出故障了?你吗? 我这边活动正常在进行,没听到谁说出了问题呀。”
“好了。那你先忙。”
我挂掉电话,眼睛疑惑的看着老板:“老板,我今天好像就是一个新活动页上线了,骗用户抢红包的。”
说完。我拿出手机,装模做样找到活动页,本身玩了一下子。玩过了以后玩把游戏结果给老板看。
“老板,这不是挺好的嘛?”
接着我又装模做样打给小小白。
“喂,今天的活动页是你作的吧? 你拿几个手机过来,我在总监办公室这。”
意料以外,小小白进来了,我看她脸色竟然看不出一丝哭过痕迹,要多淡定就有多淡定。我让小小白每一个机器都演示了一遍。
“老板,你从哪听风就是雨?活动页开局有几十单投诉不是很正常吗?我刚在跟我丈母娘聊彩礼的事儿,没注意我那个手机响,至于吗?”
从老板办公室出来,我清晰的听见了身后小小白大口吸气的声音。
“怕了?走,今天有上线需求的人一块儿吃个夜宵。”
半夜,线上所有验证完毕。 撸串的时候,我告诉你们。
“出了问题要淡定,了不得就是被开除了。那个总监看我不顺眼不是一天两天了,我就敢怼他! 你们不只要有写代码的能力,更重要的事要有解决问题的能力……”
其实有些事情你们都心领神会,小小白喝了点啤酒,酒量差,又哭了一回。
“故障”事件过去之后,小小白不是缠着小白就是缠着我,目的是想知道如何解决那个抢红包卡顿的问题。
咱们来分析一下。卡顿现象,通常能够经过用户反馈或性能监控来发现。好比咱们接到用户投诉说活动页卡致使抢不了红包了,而后在性能平台上查看卡顿指标后,发现页面出现连续 10 帧超过 40ms ,这就属于严重卡顿。
如何处理呢?
先肯定一下是否是服务端出了数据问题。这些能够经过抓包或者查看服务端异常日志能够作到。
若是问题出在前端,通常是两种情形:浏览器的主线程与合成线程调度不合理,以及计算耗时操做。
拿这个活动页(红包雨)来讲,用户投诉说卡顿,那是直观感觉,大几率和动画效果有关。动画颇有不少种,但主要仍是是css计算。
浏览器的主线程
主要负责运行 JavaScript
,计算 CSS 样式
,元素布局
,合成线程主要负责绘制UI
。当 width、height、margin、padding
等做为 transition
值动画时,主线程压力很大,由于计算 CSS 样式
也是耗时的。此时如何用 transform
来代替直接设置 margin、padding
。
举个栗子。加入表明红包的那个DOM元素 margin-left: 0px
,最终红包在 margin-left: 100px
的时候被点击了,那这个过程假如step = 1,那从0 ~ 100 就须要浏览器主线程计算100次,每次计算完都要合成线程绘制到 GPU 再渲染到屏幕上。 这个过程,浏览器压力可想而知有多大。
但若是用 tranform:translate(-100px,0)
,浏览器主线程计算1次,合成线程就能 0 直接绘制100 。 其中差距可想而知。
另外一方面,就是计算耗时操做。
Virtual DOM
的出现就是为了解决频繁操做DOM致使的性能卡顿问题。若是要操做100个DOM
,那先把操做信息存在Virtual DOM
上,待全部操做完毕再讲Virtual DOM
一次性更新到真实DOM上。 React 和 Vue走的不正式 Virtual DOM 路线吗?
js计算。例如抢红包有大有小金额,js是单线程,已经在响应用户操做了,你这时候在要js去计算,那不就只能让js单线程先计算金额,用户的行为暂时放一边,你说用户感受不到卡顿就有鬼了。这类问题解决方法有两种。
这里有个知识点提一下。
页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时,用户会感受到卡顿。
1s 60帧,每一帧分到的时间是 1000/60 ≈ 16 ms,若是每一帧执行的时间小于16ms,就说明浏览器有空余时间。
若是任务在剩余的时间内没有完成则会中止任务执行,继续优先执行主任务,也就是说 requestIdleCallback
老是利用浏览器的空余时间执行任务。
某个周末,上午11点。我还在睡梦中(太累了),小小白打来电话。
“喂,老大你还在睡觉呐!小白说要打羽毛球,把你们都叫上了,就差你了。”
啪!我把电话扔了。 还没过一下子,手机开始夺命连环call模式。好,我输了,我错了,我捡回来……
“喂,最好给我一个不喷你的理由。不然拉黑!”
“&%@!¥%*(·”
“说人话!”
“我说你个死渣男,昨晚又在哪鬼混了?以前答应个人约会你怕不是早就忘记了吧?!”
好吧。运营老铁你好,我错了。你都知道我是个死渣男,那忘记了是个人错吗?
“老铁,我叫你老铁,你会找老铁约会吗?”
“就算不约会,吃个饭就这么难吗?”
电话那头忽然很正经的来了这么一句。我感受彷佛有点不太对头。
“我立刻要滚蛋了,你就不能请我吃顿饭吗?你抽点时间吃个饭仍是能够的吧?”
我赶紧电话挂掉,微信群已经爆炸了。组织架构调整,运营老大升VP了,运营老铁要离职了。个人睡意顿时全无,又一个很是令我疯狂的想法出如今脑海里。
这傻姑娘不会是惧怕运营老大找她的麻烦,清算上次的帐了。上次的事情已经定性了,就是个误会。但VP是有很大权限的,要是翻案……
因此,成年人作任何事情都是须要付出代价的,职场也不例外。我很庆幸我有一些关系很铁的同事,他们更像是个人朋友。
新的周一,老铁已经走了。呵,这要是没领导赞成,哪能这么快就走了。我看着我手底下这些娃,都在负责各自手上的事。我再看看领导办公室,我突然间也有了决断。我也是时候该离开了。
就跟小小白约定的那样,等半年吧。因而,我开始了疯狂的技术分享之路。
其实,前端性能最重要的指标是“快
”,实在快不了的再选择“做弊或者欺诈
”的手段。
快
方面:
请求资源
Html、js、css、图片等静态资源本质上都从服务器上获取。可服务器响应客户端请求是须要时间的,返回的资源体积越大,耗时越长。因此想要快,有三方面考虑。
webpack
打包, treeShaking
、codeSplit等
,尽可能保证打包后的体积不要太大。CDN
,这也是变相的减少了包体积。代码里直接引用CDN
的连接。native
进行预请求,再将关键数据直接交给h5便可http缓存
本地缓存。
例如native对图片资源进行缓存接口缓存。
加载方式
骨架屏
了解一下。NSR
了解一下。CSR
了解一下webview 优化
并行初始化
App
时,系统就建立 WebView
和加载模板,这样 WebView 初始化
和 App 启动就能够并行进行了,这大大减小了用户等待时间。资源预加载
。资源预加载,是指提早在初始化的 WebView 里面放置一个静态资源列表,后续加载东西时,因为这部分资源已经被强缓存了,页面显示速度会更快。那么,要预加载的静态资源通常能够放:
ps: 别小瞧webview这些,作好了能给你减小100-200ms的时间。
APP启动阶段的优化方案
页面白屏阶段的优化方案
首屏渲染阶段的优化方案
DOM性能优化
memo、usememo
Render
次数性能平台
我意识到这个时间节点到来的时候,是由于我接到了一个陌生电话,是背调公司来打听小小白状况的。
是啊,这半年也快过去了。小小白成长的速度也是跟作火箭同样。我认真看了看这群可爱的小伙伴,我发现你们都能开始独当一面了,即便我离开了你们也能混得开。
我偷偷用另一个微信加了小小白好友,以能够帮她内推的名义和小小白打得火热。唉,果真年轻就是好骗,这种稍微留个心眼就能被戳穿的伎俩忽悠得小小白舒舒服服的。
我先要来了小小白的简历,给了一些建议,并帮忙修改。
其实最干净的简历是要保证HR、面试官能迅速对你的简历进行匹配。那样,即不会造成误会,也不会浪费双方的时间。例若有些人的简历喜欢把全部了解的技术都写上,例如了解node.js ,结果面试官就往node方面往死里问,结果可想而知。
我的履历 !== 工做经历。
我的履历更像是你对本身以往工做内容的一种 述职
。举个栗子:
1. 负责toB/toC业务相关项目设计
2. 对ToB、ToC业务的前端开发和管理,把控项目进度,推动合做方达成目标有丰富经验。
3. 喜欢研究新的技术,对能提升项目性能的极其热衷,并致力于将其更新至线上产品。
复制代码
必不可少的内容之一。除了把工做时间交待清楚,还但愿把你当前的角色写清楚。固然还有你的在公司负责过的业务内容。
请按照如下模版书写
效果/结果 是什么
: 例如你作了什么,提高了下单转化率,赚了多少钱,完成了多少KPI。但愿用数字来扎眼。我但愿你的自我评价能直接将你的厉害的方面直抒胸臆。
例如;
不但愿看见精通、熟悉、了解
等字眼。由于一千我的眼里就有一千个哈姆雷特,也许你的精通是面试官的熟悉呢? 与其如此还不如交待明白你对此的技术研究程度。
万字了哈!小小白的故事其实没讲完,剧情有些狗血。就是小小白最终去了想去的大厂,而后发现了事实真相。
不过已经不重要了,结果是好的,曲终人散后各自安好即是。
再见,小小白。
再见,渣男。
小小白最后打电话喷个人,谁让我这么“舔狗式”的作法。即便真相被戳穿了依然打死不认可😂😂😂。
(祝,君安好。但愿你能从这篇长文故事里获得自我成长! 另外,本篇没交待清楚的细节可能会在下一篇文。)