我終於把你送進了大廠

本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,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. 寫在最後

萬字了哈!小小白的故事其實沒講完,劇情有些狗血。就是小小白最終去了想去的大廠,而後發現了事實真相。

不過已經不重要了,結果是好的,曲終人散後各自安好即是。

再見,小小白。

再見,渣男。

小小白最後打電話噴個人,誰讓我這麼「舔狗式」的作法。即便真相被戳穿了依然打死不認可😂😂😂。

(祝,君安好。但願你能從這篇長文故事裏獲得自我成長! 另外,本篇沒交待清楚的細節可能會在下一篇文。)

相關文章
相關標籤/搜索