我终于把你送进了大厂

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!css

前言

本文主要讲述的是某种心路历程: 一枚小白成长到大厂须要的高级前端开发工程师。html

技术文,干货多,放心观看呢。前端

0. 最后的狂欢

“来,喝!谁也别是个怂瓜蛋哈!”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写出毛玻璃的效果。 大家说,老大是否是个狠人!”

image.png

<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;
    }
}
复制代码

啊,喂,喂!我记得我没逼你啊!咱们不是共同探讨的吗?

“哎呀,大家这跟个人事比起来都正常,不算啥好伐?”

不擅言辞的小白,咱们组少数几个女生,也是最漂亮的一个。上海本地儿人,姓白,对穿搭很在行。因此,自从她加入咱们第一天,来找她“滋事”的人就很多。有的是借工做上的原油来找她,有的甚至就干脆路过都要找她闲聊几句。哪怕她不怎么擅长应付这些人,但依然天天都有人……

唉,颜值即正义啊!可没想到小白也要吐槽我吗?

“大家是不知道,公司不是发了邮件说要组织舞蹈队,找老大报名。我吧没敢直接问老大,老大也不安排我,而后我天天穿不一样的好看的衣服,就但愿老大看见了能把我报名报上去。可结果呢,大家知道有多气吗!”

突然地,特别安静了一下子。大章着急催着。

“小白,你快接着说啊!你们都好奇你有多气呢!”

“结果嘞!我记得我换第三套衣服的时候,也就是第三天,老大忽然告诉我,叫我之后晚上要早点下班,不要总加班。原来老大是觉得我脱单处对象了!”

“噗~!”

好几个嘴里还喝着的没忍住,都喷了。而后笑了一地……

是啊,这群人太可爱了。我能带出这样一波人,我很知足。

1.作好一件事情,成就一批人

终究仍是大章选择离开。这是我跟大章商量好的,我给他推荐一个很好的机会,他还能拿到这边的 N+1 赔偿。意外的是,小白来找我了。

小白说她的同学好友在隔壁团队,已经获得消息她在被裁名单上。小白想本身离开,而后拜托我去把她捞过来。有种一命换一命的感受。

小白还说,由于她的名字里带个“白”字,因此叫她小小白。小白求了我2天,这件事情得分开说。我不会主动让小白离开,是由于小白的技术还不够,不能和大章同样能出去独当一面。若是是小白因本身的事提出离职,我或许没办法阻拦,但这种一命换一命的作法,我实在没办法苟同。

我认为的前端能力等级

  1. 初级 -- 能正常执行需求,代码书写经验足。
  2. 中级 -- 能理解经常使用技术栈的原理,并在代码层面写出性能较好的代码,且能hold住一个项目。
  3. 高级 -- 能独挡一面。能及时解决线上响应的问题,并能提供技术解决方案。
  4. 资深 -- 能单独承担商业解决方案。
  5. 专家 -- 技术上广度要有,还要有深度。管理方面能hold住业务,能搞好团队建设,能提高团队总体实力水平。

大章已经资深边缘徘徊了,而小白还处于中级阶段。至于小小白,只在初级阶段。

最终,小白和小小白请我吃了碗酸辣粉,搞定了我。小小白,22岁,工做了2年。20岁就名校毕业,真的是个学霸了。一碗酸辣粉的时间就把优化算法给我讲了透。

基于混沌时间序列分析的神经网络了解一下?

噢,不懂是吧?换个方式说,这种优化算法主要用途之一是用来预测。例如用来预测下一个月内的用户行为,分析出用户画像。你手机里那些购物APP是否是都有按你的喜爱推荐商品的功能?为啥购物APP能知道你喜欢哪一类商品?其中的手段之一就是预测。

我了个去,人才啊!我突然以为我庙小了。我想推荐小小白去算法团队,被拒绝了。不是被算法团队拒绝了,是被小小白拒绝了。小小白说要在我这修炼半年,修炼好了就去大厂,让曾经那些看不起她以为她菜的人仰着头看她

好咯。年轻真好,那就开始修炼吧。我很愿意看见半年后小小白来打个人脸,去打那些人的脸。Flag就立在这了。

作好一件事情,成就一批人。 这是我一直在作的事情。

2. 修炼第一步,搞懂React原理才能写好React代码。

人来了以后,我作了第一次技术分析。没别的,先把React Hooks原理搞懂。

  • 函数组件执行函数
    • 执行函数组件 renderWithHooks
    • 改变 ReactCurrentDispatcher 对象
  • 初始化hooks
    • 经过 mountWorkInProgressHook 生成hooks链表
    • 经过 mountState 来初始化 useState
    • 经过 dispatchAction 来控制无状态组件的更新
    • 经过 mountEffect 初始化 useEffect
    • 经过 mountMemo 初始化 useMemo
    • 经过 mountRef 初始化 useRef
  • 更新hooks
    • 经过 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>
}

复制代码

Q1:当你使用了hooks( 例如 useState)时,发生了什么?**

咱们去看 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

Q2: 那调度员 dispatcher 又是什么?

看源码。

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current
  return dispatcher
}
复制代码

噢,dispatcher 是从 ReactCurrentDispatcher 身上来。咱们来把这个此分析一下,react 当前的(current)调度员(Dispatcher)。

也就是说,到这里 Dispatcher 就已经安排好了。

Q3: 函数组件是何时被调用的?

就是说,你的 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 是否是前面没解释清楚的 调度员 的归宿?! 解释一下这行代码: 当 currentnull 或者 currentmemoizedState 属性为 null 就把 HooksDispatcherOnMount 赋值给咱们的调度员, 不然就把HooksDispatcherOnUpdate 赋值给咱们的调度员

从这两名称上又能看出个大概来,一个是 Mount 的 调度员,一个是 Update 的调度员。那也就是说,初始化 hooks 的时候就是 Mount 调度员,要更新的时候就是 Update 调度员?!

ok,案子到这算是破了80%了。

Q4: workInProgress 是什么,workInProgress.memoizedState又是什么?

workInProgress: 从名称分析,就是工做进度 或者 正在进行中的工做 的意思吧? 那它是个对象吧? 那对象身上确定会有一些属性用来描述不一样信息对吧?

workInProgress.memoizedState

  • 使用 useState ,保存 state 信息
  • 使用 useEffect ,保存 effect 对象
  • 使用 useMemo , 保存缓存的值deps
  • 使用 useRef , 保存 ref 对象。

也就是说,workInProgress.memoizedState 存放的是 咱们所使用的hooks 的信息。

这里的 workInProgress.updateQueue 后面再提。

Q5: 调用 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];
}

复制代码

噢,这个代码明显要更容易分析一些。

  1. 先拿到 hook 的信息 也就是 const hook = mountWorkInProgressHook();
  2. 对入参 initialState 进行判别。接着将 initialState 赋值给 hook.memoizedStatehook.baseState
  3. 接下来就申明了一个队列 queue,信息看注释。
  4. 申明调度函数 dispatch , 用来更新 state
  5. 返回一个数组,方便咱们解构。也就是 :
const [x,setX] = useState(initialState);
复制代码

那 dispatchAction 又是什么?

function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) 复制代码

对照上述代码,S 表明 什么? A 表明什么

setX 就是调用了 dispatchAction 吧? 源码中显示 dispatchAction 已经有了 currentlyRenderingFiber, queue 两个参数了,那 setX 传入的参数应该就是第三个参数 action 了吧?

Q6: 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);
  }
}

复制代码

Q7: Fiber 又是什么? Fiber 链表又是什么?

唉,大体看看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 节点的构建。

image.png

至于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)
复制代码

2. 上线1小时后,小小白哭了

自从上次分享了 React Hooks 相关的东西,安生了2个星期。小小白刻苦学习,公司加班写需求,回家继续加班写代码。

我是否是逼得人太紧了?

狗叫了(个人手机响铃声)。

“你怎么回事,运营说线上出故障了!刚答应你留我的,你就这样报答个人……”

啪!我把电话挂了。啥也没说,直接挂了。我迅速回到位置上,小小白泪流满面就差哭晕在工位上了。我这时候是否是应该喷一句: MMP,出了事就知道哭,出了问题你却是第一时间告诉我啊!

事情是这样的,小小白作了一个活动页,内容是让用户点击屏幕抢红包。问题是,用户能够点到了就抢到了红包,理论上只要手速够快,屏幕能识别用户的手势就能抢到。也就是说你点1千次,就能抢到1千个红包。 固然了,这样的说法太夸张。用户最多抢到200块的红包,后面抢再多也不会超过200块钱。

用户手速不可能快到那个程度,可小小白的活动页,抢到三、4个红包的时候动画就显得很卡顿。用户明明点选到了却没抢到红包,问题就被活动运营上报了。

了解事情大概后,个人电话已经拨给某个运营老大。

“老铁,这个问题状况如何了?”

“目前已经有几十笔投诉了,不过预计接下来流量很大。我如今已经让手下暂停推广这个活动页了。你那边怎么搞?”

“老铁,这样。你先别让暂停推广手段,你那边的流程照常进行,半小时内不要拿真正的活动地址,你能够先整个预热活动。”

电话那边沉默了十几秒。我知道她会帮个人。

“你能肯定半小时后一切正常吗?”

“能。”

“那事情要是搞定了,跟我约会。”

啪!手机丢进抽屉,神特么约会,上帝来了也别打扰我!

我走到小小白,她还趴着那哭。小白在安慰,看到我过来。

“老大,怎么作?”

“让开。”

我坐在小白的位置上。

“锁屏密码。”

“110gaosuwoyaodidiao” (110告诉我要低调)

我是记得小白今天是有一个自动埋点的SDK要上线的,不过预计是晚上,白天还在作最后的回归。

“小白,我能够相信你吗?”

“能够。”

我打开vscode,在sdk上面埋伏了一段骇客代码。主要作了几件事情:

  • 全部 margin、padding 要通过主线程反复计算的css属性换算成 translate
  • 红包价格相关的计算所有延后计算。先记录用户的点击数据,统一在当次活动时间结束后统一作计算。
  • 注入 requestIdleCallback

这样作很危险。没有测试,没有验证。甚至不合法。 但没有比如今更差劲的结果了。

commit 代码。直接越过测试上预发环境(上线前的模拟线上环境)。

“小白,拿你手机出来。”

我看了下时间,已通过去15分钟了。这时候小小白中止哭了,应该是被我吓的。

“小小白,5分钟内把测试那边的机器拿来跑一下。能作到吗?”

小小白机械式的点了点头,随后跑去拿手机了。

我盯着手机时间,21分钟的时候,上线。小小白却忽然拉住我。

“老大,这里有个小米6,仍是有点卡顿。”

我看了一眼,点击红包的时候会有轻微的抖动。随即没理小小白,上线。用个人权限强行开了一个自测迭代上线。

操做完之后,就是排队等待上线了。我忽然问了句小小白。

“小小白,此次我能相信你吗?”

我看到了小小白眼里的慌张,但她仍是咬了咬下嘴唇,对我点了点头。我回位置上拿出手机,老板的未接数量呛死了个人屏幕。

我走到老板办公室,敲开了门。老板把水杯摔到门口。

“你如今牛逼了啊!不把我放眼里了啊!运营老大把事情捅到业务方那边,问题都上升到欧总(CEO)那了。你他吗躲什么躲,第一时间解决问题知道吗?”

唉。运营部也真是一地鸡毛,那我运营老铁是个北方妹纸,估计就是个运营老四老五,至于那个老大,我和他闹过矛盾。此次估计就是他把事情捅上去了。

我伪装淡定的关上门,隔绝一下外面的声音。个人直觉告诉我,个人那些娃确定都在直勾勾的看着我。

“老板,到底发生什么事了。”

“你手底下的人搞出故障了,你还问我什么事?!”

我看了下时间,差很少了。我当着老板面,拿出手机,拨电话给老铁,打开外音。

“喂,啥事?我如今忙着搞活动。”

“不是出故障了吗?”

“出故障了?你吗? 我这边活动正常在进行,没听到谁说出了问题呀。”

“好了。那你先忙。”

我挂掉电话,眼睛疑惑的看着老板:“老板,我今天好像就是一个新活动页上线了,骗用户抢红包的。”

说完。我拿出手机,装模做样找到活动页,本身玩了一下子。玩过了以后玩把游戏结果给老板看。

“老板,这不是挺好的嘛?”

接着我又装模做样打给小小白。

“喂,今天的活动页是你作的吧? 你拿几个手机过来,我在总监办公室这。”

意料以外,小小白进来了,我看她脸色竟然看不出一丝哭过痕迹,要多淡定就有多淡定。我让小小白每一个机器都演示了一遍。

“老板,你从哪听风就是雨?活动页开局有几十单投诉不是很正常吗?我刚在跟我丈母娘聊彩礼的事儿,没注意我那个手机响,至于吗?”

从老板办公室出来,我清晰的听见了身后小小白大口吸气的声音。

“怕了?走,今天有上线需求的人一块儿吃个夜宵。”

半夜,线上所有验证完毕。 撸串的时候,我告诉你们。

“出了问题要淡定,了不得就是被开除了。那个总监看我不顺眼不是一天两天了,我就敢怼他! 你们不只要有写代码的能力,更重要的事要有解决问题的能力……”

其实有些事情你们都心领神会,小小白喝了点啤酒,酒量差,又哭了一回。

3. 小小白对性能优化感兴趣

“故障”事件过去之后,小小白不是缠着小白就是缠着我,目的是想知道如何解决那个抢红包卡顿的问题。

咱们来分析一下。卡顿现象,通常能够经过用户反馈或性能监控来发现。好比咱们接到用户投诉说活动页卡致使抢不了红包了,而后在性能平台上查看卡顿指标后,发现页面出现连续 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单线程先计算金额,用户的行为暂时放一边,你说用户感受不到卡顿就有鬼了。这类问题解决方法有两种。

    • 延迟js计算,等待优先级高的任务所有执行完了再作js计算。
    • 若是必定要及时计算结果,那就尽可能在浏览器的空闲时间去计算。

这里有个知识点提一下。

页面是一帧一帧绘制出来的,当每秒绘制的帧数达到 60 时,页面是流畅的,小于这个值时,用户会感受到卡顿。

1s 60帧,每一帧分到的时间是 1000/60 ≈ 16 ms,若是每一帧执行的时间小于16ms,就说明浏览器有空余时间。

若是任务在剩余的时间内没有完成则会中止任务执行,继续优先执行主任务,也就是说 requestIdleCallback 老是利用浏览器的空余时间执行任务。

某个周末,上午11点。我还在睡梦中(太累了),小小白打来电话。

“喂,老大你还在睡觉呐!小白说要打羽毛球,把你们都叫上了,就差你了。”

啪!我把电话扔了。 还没过一下子,手机开始夺命连环call模式。好,我输了,我错了,我捡回来……

“喂,最好给我一个不喷你的理由。不然拉黑!”

“&%@!¥%*(·”

“说人话!”

“我说你个死渣男,昨晚又在哪鬼混了?以前答应个人约会你怕不是早就忘记了吧?!”

好吧。运营老铁你好,我错了。你都知道我是个死渣男,那忘记了是个人错吗?

“老铁,我叫你老铁,你会找老铁约会吗?”

“就算不约会,吃个饭就这么难吗?”

电话那头忽然很正经的来了这么一句。我感受彷佛有点不太对头。

“我立刻要滚蛋了,你就不能请我吃顿饭吗?你抽点时间吃个饭仍是能够的吧?”

我赶紧电话挂掉,微信群已经爆炸了。组织架构调整,运营老大升VP了,运营老铁要离职了。个人睡意顿时全无,又一个很是令我疯狂的想法出如今脑海里。

这傻姑娘不会是惧怕运营老大找她的麻烦,清算上次的帐了。上次的事情已经定性了,就是个误会。但VP是有很大权限的,要是翻案……

因此,成年人作任何事情都是须要付出代价的,职场也不例外。我很庆幸我有一些关系很铁的同事,他们更像是个人朋友。

新的周一,老铁已经走了。呵,这要是没领导赞成,哪能这么快就走了。我看着我手底下这些娃,都在负责各自手上的事。我再看看领导办公室,我突然间也有了决断。我也是时候该离开了。

就跟小小白约定的那样,等半年吧。因而,我开始了疯狂的技术分享之路。

4.前端性能优化总结

其实,前端性能最重要的指标是“”,实在快不了的再选择“做弊或者欺诈”的手段。

方面:

  • 请求资源

    Html、js、css、图片等静态资源本质上都从服务器上获取。可服务器响应客户端请求是须要时间的,返回的资源体积越大,耗时越长。因此想要快,有三方面考虑。

    1. 减小资源体积大小。
      • 优化 webpack 打包, treeShakingcodeSplit等,尽可能保证打包后的体积不要太大。
      • 较大、较多的图片等资源放CDN,这也是变相的减少了包体积。代码里直接引用CDN的连接。
    2. 加载资源的优先级
      • 例如首屏,只须要优先渲染可视区的内容,非关键的延后加载便可。
      • 关键数据可让native进行预请求,再将关键数据直接交给h5便可
    3. 缓存
      • http缓存
      • 本地缓存。例如native对图片资源进行缓存
      • 接口缓存。
  • 加载方式

    1. 懒加载
      • 骨架屏 了解一下。
    2. 预加载
      • NSR 了解一下。
    3. 缓存加载
      • CSR 了解一下
    4. 离线包
  • webview 优化

    1. 并行初始化
      • 所谓并行初始化,是指用户在进入 App 时,系统就建立 WebView 和加载模板,这样 WebView 初始化和 App 启动就能够并行进行了,这大大减小了用户等待时间。
    2. 资源预加载。资源预加载,是指提早在初始化的 WebView 里面放置一个静态资源列表,后续加载东西时,因为这部分资源已经被强缓存了,页面显示速度会更快。那么,要预加载的静态资源通常能够放:
      • 必定时间内不变的外链;
      • 一些基础框架,多端适配的 JS(如 adapter.js),性能统计的SDK 或者第三方库(如 react/vue.js);
      • 基础布局的 CSS 如 base.css。
    3. 其它。如何native 提供了接口请求的API,那针对接口请求也可作优化。

    ps: 别小瞧webview这些,作好了能给你减小100-200ms的时间。

小结一下

  1. APP启动阶段的优化方案

    • webview优化
  2. 页面白屏阶段的优化方案

    • 离线化设计,例如离线包
    • 骨架屏
    • SSR
    • NSR
  3. 首屏渲染阶段的优化方案

    • 优化请求数量
    • 接口预加载
  4. DOM性能优化

    • 长列表优化。 memo、usememo
    • 减小Render次数
    • js计算优化
  5. 性能平台

    想了解请看另一个故事

5. 即将结束的时光

我意识到这个时间节点到来的时候,是由于我接到了一个陌生电话,是背调公司来打听小小白状况的。

是啊,这半年也快过去了。小小白成长的速度也是跟作火箭同样。我认真看了看这群可爱的小伙伴,我发现你们都能开始独当一面了,即便我离开了你们也能混得开。

我偷偷用另一个微信加了小小白好友,以能够帮她内推的名义和小小白打得火热。唉,果真年轻就是好骗,这种稍微留个心眼就能被戳穿的伎俩忽悠得小小白舒舒服服的。

我先要来了小小白的简历,给了一些建议,并帮忙修改。

其实最干净的简历是要保证HR、面试官能迅速对你的简历进行匹配。那样,即不会造成误会,也不会浪费双方的时间。例若有些人的简历喜欢把全部了解的技术都写上,例如了解node.js ,结果面试官就往node方面往死里问,结果可想而知。

我喜欢简历上能有一份我的履历信息。

我的履历 !== 工做经历。

我的履历更像是你对本身以往工做内容的一种 述职 。举个栗子:

1. 负责toB/toC业务相关项目设计
2. 对ToB、ToC业务的前端开发和管理,把控项目进度,推动合做方达成目标有丰富经验。
3. 喜欢研究新的技术,对能提升项目性能的极其热衷,并致力于将其更新至线上产品。
复制代码

工做经历

必不可少的内容之一。除了把工做时间交待清楚,还但愿把你当前的角色写清楚。固然还有你的在公司负责过的业务内容。

项目经历

请按照如下模版书写

  1. 项目名称
  2. 你在此项目中的角色
  3. 简洁明了切中要害的说一下项目背景
  4. 你付出了什么
  5. 效果/结果 是什么 : 例如你作了什么,提高了下单转化率,赚了多少钱,完成了多少KPI。但愿用数字来扎眼。

自我评价

我但愿你的自我评价能直接将你的厉害的方面直抒胸臆。

例如;

  1. 对XXX有实践心得
  2. 研究过Vue的源码
  3. 擅长项目基建
  4. ……

不但愿看见精通、熟悉、了解等字眼。由于一千我的眼里就有一千个哈姆雷特,也许你的精通是面试官的熟悉呢? 与其如此还不如交待明白你对此的技术研究程度。

end. 写在最后

万字了哈!小小白的故事其实没讲完,剧情有些狗血。就是小小白最终去了想去的大厂,而后发现了事实真相。

不过已经不重要了,结果是好的,曲终人散后各自安好即是。

再见,小小白。

再见,渣男。

小小白最后打电话喷个人,谁让我这么“舔狗式”的作法。即便真相被戳穿了依然打死不认可😂😂😂。

(祝,君安好。但愿你能从这篇长文故事里获得自我成长! 另外,本篇没交待清楚的细节可能会在下一篇文。)

相关文章
相关标签/搜索