1. 程式人生 > >React16原始碼解讀:開篇帶你搞懂幾個面試考點

React16原始碼解讀:開篇帶你搞懂幾個面試考點

引言

如今,主流的前端框架React,Vue和Angular在前端領域已成三足鼎立之勢,基於前端技術棧的發展現狀,大大小小的公司或多或少也會使用其中某一項或者多項技術棧,那麼掌握並熟練使用其中至少一種也成為了前端人員必不可少的技能飯碗。當然,框架的部分實現細節也常成為面試中的考察要點,因此,一方面為了應付面試官的連番追問,另一方面為了提升自己的技能水平,還是有必要對框架的底層實現原理有一定的涉獵。

當然對於主攻哪門技術棧沒有嚴格的要求,挑選你自己喜歡的就好,在面試中面試官一般會先問你最熟悉的是哪門技術棧,對於你不熟悉的領域,面試官可能也不會做太多的追問。筆者在專案中一直是使用的Vue框架,其上手門檻低,也提供了比較全面和友好的官方文件可供參考。但是可能因人而異,感覺自己還是比較喜歡React,也說不出什麼好壞,可能是自己最早接觸的前端框架吧,不過很遺憾,在之前的工作中一直派不上用場,但即便如此,也阻擋不了自己對底層原理的好奇心。所以最近也是開始研究React的原始碼,並對原始碼的解讀過程做一下記錄,方便加深記憶。如果你的技術棧剛好是React,並且也對原始碼感興趣,那麼我們可以一起互相探討技術難點,讓整個閱讀原始碼的過程變得更加容易和有趣。原始碼中如果有理解錯誤的地方,還希望能夠指出。

1、準備階段

在facebook的github上,目前React的最新版本為v16.12.0,我們知道在React的v16版本之後引入了新的Fiber架構,這種架構使得任務擁有了暫停恢復機制,將一個大的更新任務拆分為一個一個執行單元,充分利用瀏覽器在每一幀的空閒時間執行任務,無空閒時間則延遲執行,從而避免了任務的長時間執行導致阻塞主執行緒同步任務的執行。為了瞭解這種Fiber架構,這裡選擇了一個比較適中的v16.10.2的版本,沒有選擇最新的版本是因為在最新版本中移除了一些舊的相容處理方案,雖說這些方案只是為了相容,但是其思想還是比較先進的,值得我們推敲學習,所以先將其保留下來,這裡選擇v16.10.2

版本的另外一個原因是React在v16.10.0的版本中涉及到兩個比較重要的優化點:


在上圖中指出,在任務排程(Scheduler)階段有兩個效能的優化點,解釋如下:

  • 將任務佇列的內部資料結構轉換成最小二叉堆的形式以提升佇列的效能(在最小堆中我們能夠以最快的速度找到最小的那個值,因為那個值一定在堆的頂部,有效減少整個資料結構的查詢時間)。
  • 使用週期更短的postMessage迴圈的方式而不是使用requestAnimationFrame這種與幀邊界對齊的方式(這種優化方案指得是在將任務進行延遲後恢復執行的階段,前後兩種方案都是巨集任務,但是巨集任務也有順序之分,postMessage的優先順序比requestAnimationFrame
    高,這也就意味著延遲任務能夠更快速地恢復並執行)。

當然現在不太理解的話沒關係,後續會有單獨的文章來介紹任務排程這一塊內容,遇到上述兩個優化點的時候會進行詳細說明,在開始閱讀原始碼之前,我們可以使用create-react-app來快速搭建一個React專案,後續的示例程式碼可以在此專案上進行編寫:

// 專案搭建完成後React預設為最新版v16.12.0
create-react-app react-learning

// 為了保證版本一致,手動將其修改為v16.10.2
npm install --save [email protected] [email protected]

// 執行專案
npm start

執行以上步驟後,不出意外的話,瀏覽器中會正常顯示出專案的預設介面。得益於在Reactv16.8版本之後推出的React Hooks功能,讓我們在原來的無狀態函式元件中也能進行狀態管理,以及使用相應的生命週期鉤子,甚至在新版的create-react-app腳手架中,根元件App已經由原來的類元件的寫法升級為了推薦的函式定義元件的方式,但是原來的類元件的寫法並沒有被廢棄掉,事實上我們專案中還是會大量充斥著類元件的寫法,因此為了瞭解這種類元件的實現原理,我們暫且將App根元件的函式定義的寫法回退到類元件的形式,並對其內容進行簡單修改:

// src -> App.js
import React, {Component} from 'react';

function List({data}) {
    return (
        <ul className="data-list">
            {
                data.map(item => {
                    return <li className="data-item" key={item}>{item}</li>
                })
            }
        </ul>
    );
}

export default class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: [1, 2, 3]
        };
    }

    render() {
        return (
            <div className="container">
                <h1 className="title">React learning</h1>
                <List data={this.state.data} />
            </div>
        );
    }
}

經過以上簡單修改後,然後我們通過呼叫

// src -> index.js
ReactDOM.render(<App />, document.getElementById('root'));

來將元件掛載到DOM容器中,最終得到App元件的DOM結構如下所示:

<div class="container">
    <h1 class="title">React learning</h1>
    <ul class="data-list">
        <li class="data-item">1</li>
        <li class="data-item">2</li>
        <li class="data-item">3</li>
    </ul>
</div>

因此我們分析React原始碼的入口也將會是從ReactDOM.render方法開始一步一步分析元件渲染的整個流程,但是在此之前,我們有必要先了解幾個重要的前置知識點,這幾個知識點將會更好地幫助我們理解原始碼的函式呼叫棧中的引數意義和其他的一些細節。

2、前置知識

首先我們需要明確的是,在上述示例中,App元件的render方法返回的是一段HTML結構,在普通的函式中這種寫法是不支援的,所以我們一般需要相應的外掛來在背後支撐,在React中為了支援這種jsx語法提供了一個Babel預置工具包@babel/preset-react,其中這個preset又包含了兩個比較核心的外掛:

  • @babel/plugin-syntax-jsx:這個外掛的作用就是為了讓Babel編譯器能夠正確解析出jsx語法。
  • @babel/plugin-transform-react-jsx:在解析完jsx語法後,因為其本質上是一段HTML結構,因此為了讓JS引擎能夠正確識別,我們就需要通過該外掛將jsx語法編譯轉換為另外一種形式。在預設情況下,會使用React.createElement來進行轉換,當然我們也可以在.babelrc檔案中來進行手動設定。
// .babelrc
{
    "plugins": [
        ["@babel/plugin-transform-react-jsx", {
            "pragma": "Preact.h", // default pragma is React.createElement
            "pragmaFrag": "Preact.Fragment", // default is React.Fragment
            "throwIfNamespace": false // defaults to true
        }]
    ]
}

這裡為了方便起見,我們可以直接使用Babel官方實驗室來檢視轉換後的結果,對應上述示例,轉換後的結果如下所示:

// 轉換前
render() {
    return (
        <div className="container">
            <h1 className="title">React learning</h1>
            <List data={this.state.data} />
        </div>
    );
}

// 轉換後
render() {
    return React.createElement("div", {
        className: "content"
    }, 
    React.createElement("header", null, "React learning"), 
    React.createElement(List, { data: this.state.data }));
}

可以看到jsx語法最終被轉換成由React.createElement方法組成的巢狀呼叫鏈,可能你之前已經瞭解過這個API,或者接觸過一些虛擬碼實現,這裡我們就基於原始碼,深入原始碼內部來看看其背後為我們做了哪些事情。

2.1 createElement & ReactElement

為了保證原始碼的一致性,也建議你將React版本和筆者保持一致,採用v16.10.2版本,可以通過facebook的github官方渠道進行獲取,下載下來之後我們通過如下路徑來開啟我們需要檢視的檔案:

// react-16.10.2 -> packages -> react -> src -> React.js 

React.js檔案中,我們直接跳轉到第63行,可以看到React變數作為一個物件字面量,包含了很多我們所熟知的方法,包括在v16.8版本之後推出的React Hooks方法:

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  // 一些有用的React Hooks方法
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,

  Fragment: REACT_FRAGMENT_TYPE,
  Profiler: REACT_PROFILER_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,
  unstable_SuspenseList: REACT_SUSPENSE_LIST_TYPE,

  // 重點先關注這裡,生產模式下使用後者
  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  unstable_withSuspenseConfig: withSuspenseConfig,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,

這裡我們暫且先關注createElement方法,在生產模式下它來自於與React.js同級別的ReactElement.js檔案,我們開啟該檔案,並直接跳轉到第312行,可以看到createElement方法的函式定義(去除了一些__DEV__環境才會執行的程式碼):

/**
 * 該方法接收包括但不限於三個引數,與上述示例中的jsx語法經過轉換之後的實參進行對應
 * @param type 表示當前節點的型別,可以是原生的DOM標籤字串,也可以是函式定義元件或者其它型別
 * @param config 表示當前節點的屬性配置資訊
 * @param children 表示當前節點的子節點,可以不傳,也可以傳入原始的字串文字,甚至可以傳入多個子節點
 * @returns 返回的是一個ReactElement物件
 */
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  // 用於存放config中的屬性,但是過濾了一些內部受保護的屬性名
  const props = {};

  // 將config中的key和ref屬性使用變數進行單獨儲存
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  // config為null表示節點沒有設定任何相關屬性
  if (config != null) {

    // 有效性判斷,判斷 config.ref !== undefined
    if (hasValidRef(config)) {
      ref = config.ref;
    }

    // 有效性判斷,判斷 config.key !== undefined
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    // Remaining properties are added to a new props object
    // 用於將config中的所有屬性在過濾掉內部受保護的屬性名後,將剩餘的屬性全部拷貝到props物件中儲存
    // const RESERVED_PROPS = {
    //   key: true,
    //   ref: true,
    //   __self: true,
    //   __source: true,
    // };
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }

  // Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  // 由於子節點的數量不限,因此從第三個引數開始,判斷剩餘引數的長度
  // 具有多個子節點則props.children屬性儲存為一個數組
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    
    // 單節點的情況下props.children屬性直接儲存對應的節點
    props.children = children;
  } else if (childrenLength > 1) {
    
    // 多節點的情況下則根據子節點數量建立一個數組
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  // 此處用於解析靜態屬性defaultProps
  // 針對於類元件或函式定義元件的情況,可以單獨設定靜態屬性defaultProps
  // 如果有設定defaultProps,則遍歷每個屬性並將其賦值到props物件中(前提是該屬性在props物件中對應的值為undefined)
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  
  // 最終返回一個ReactElement物件
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

經過上述分析我們可以得出,在類元件的render方法中最終返回的是由多個ReactElement物件組成的多層巢狀結構,所有的子節點資訊均存放在父節點的props.children屬性中。我們將原始碼定位到ReactElement.js的第111行,可以看到ReactElement函式的完整實現:

/**
 * 為一個工廠函式,每次執行都會建立並返回一個ReactElement物件
 * @param type 表示節點所對應的型別,與React.createElement方法的第一個引數保持一致
 * @param key 表示節點所對應的唯一標識,一般在列表渲染中我們需要為每個節點設定key屬性
 * @param ref 表示對節點的引用,可以通過React.createRef()或者useRef()來建立引用
 * @param self 該屬性只有在開發環境才存在
 * @param source 該屬性只有在開發環境才存在
 * @param owner 一個內部屬性,指向ReactCurrentOwner.current,表示一個Fiber節點
 * @param props 表示該節點的屬性資訊,在React.createElement中通過config,children引數和defaultProps靜態屬性得到
 * @returns 返回一個ReactElement物件
 */
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    // 這裡僅僅加了一個$$typeof屬性,用於標識這是一個React Element
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };
  
  ...
  
  return element;
};

一個ReactElement物件的結構相對而言還是比較簡單,主要是增加了一個$$typeof屬性用於標識該物件是一個React Element型別。REACT_ELEMENT_TYPE在支援Symbol型別的環境中為symbol型別,否則為number型別的數值。與REACT_ELEMENT_TYPE對應的還有很多其他的型別,均存放在shared/ReactSymbols目錄中,這裡我們可以暫且只關心這一種,後面遇到其他型別再來細看。

2.2 Component & PureComponent

瞭解完ReactElement物件的結構之後,我們再回到之前的示例,通過繼承React.Component我們將App元件修改為了一個類元件,我們不妨先來研究下React.Component的底層實現。React.Component的原始碼存放在packages/react/src/ReactBaseClasses.js檔案中,我們將原始碼定位到第21行,可以看到Component建構函式的完整實現:

/**
 * 建構函式,用於建立一個類元件的例項
 * @param props 表示所擁有的屬性資訊
 * @param context 表示所處的上下文資訊
 * @param updater 表示一個updater物件,這個物件非常重要,用於處理後續的更新排程任務
 */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  // 該屬性用於儲存類元件例項的引用資訊
  // 在React中我們可以有多種方式來建立引用
  // 通過字串的方式,如:<input type="text" ref="inputRef" />
  // 通過回撥函式的方式,如:<input type="text" ref={(input) => this.inputRef = input;} />
  // 通過React.createRef()的方式,如:this.inputRef = React.createRef(null); <input type="text" ref={this.inputRef} />
  // 通過useRef()的方式,如:this.inputRef = useRef(null); <input type="text" ref={this.inputRef} />
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  // 當state發生變化的時候,需要updater物件去處理後續的更新排程任務
  // 這部分涉及到任務排程的內容,在後續分析到任務排程階段的時候再來細看
  this.updater = updater || ReactNoopUpdateQueue;
}

// 在原型上新增了一個isReactComponent屬性用於標識該例項是一個類元件的例項
// 這個地方曾經有面試官考過,問如何區分函式定義元件和類元件
// 函式定義元件是沒有這個屬性的,所以可以通過判斷原型上是否擁有這個屬性來進行區分
Component.prototype.isReactComponent = {};

/**
 * 用於更新狀態
 * @param partialState 表示下次需要更新的狀態
 * @param callback 在元件更新之後需要執行的回撥
 */
Component.prototype.setState = function(partialState, callback) {
  ...
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

/**
 * 用於強制重新渲染
 * @param callback 在元件重新渲染之後需要執行的回撥
 */
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

上述內容中涉及到任務排程的會在後續講解到排程階段的時候再來細講,現在我們知道可以通過原型上的isReactComponent屬性來區分函式定義元件和類元件。事實上,在原始碼中就是通過這個屬性來區分Class ComponentFunction Component的,可以找到以下方法:

// 返回true則表示類元件,否則表示函式定義元件
function shouldConstruct(Component) {
  return !!(Component.prototype && Component.prototype.isReactComponent);
}

Component建構函式對應的,還有一個PureComponent建構函式,這個我們應該還是比較熟悉的,通過淺比較判斷元件前後傳遞的屬性是否發生修改來決定是否需要重新渲染元件,在一定程度上避免元件重渲染導致的效能問題。同樣的,在ReactBaseClasses.js檔案中,我們來看看PureComponent的底層實現:

// 通過借用建構函式,實現典型的寄生組合式繼承,避免原型汙染
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

// 將PureComponent的原型指向借用建構函式的例項
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());

// 重新設定建構函式的指向
pureComponentPrototype.constructor = PureComponent;

// Avoid an extra prototype jump for these methods.
// 將Component.prototype和PureComponent.prototype進行合併,減少原型鏈查詢所浪費的時間(原型鏈越長所耗費的時間越久)
Object.assign(pureComponentPrototype, Component.prototype);

// 這裡是與Component的區別之處,PureComponent的原型上擁有一個isPureReactComponent屬性
pureComponentPrototype.isPureReactComponent = true;

通過以上分析,我們就可以初步得出ComponentPureComponent之間的差異,可以通過判斷原型上是否擁有isPureReactComponent屬性來進行區分,當然更細粒度的區分,還需要在閱讀後續的原始碼內容之後才能見分曉。

3、面試考點

看完以上內容,按道理來說以下幾個可能的面試考點應該就不成問題了,或者說至少也不會遇到一個字也回答不了的尷尬局面,試試看吧:

  • 在React中為何能夠支援jsx語法
  • 類元件的render方法執行後最終返回的結果是什麼
  • 手寫程式碼實現一個createElement方法
  • 如何判斷一個物件是不是React Element
  • 如何區分類元件和函式定義元件
  • ComponentPureComponent之間的關係
  • 如何區分ComponentPureComponent

4、總結

本文作為React16原始碼解讀的開篇,先講解了幾個比較基礎的前置知識點,這些知識點有助於我們在後續分析元件的任務排程和渲染過程時能夠更好地去理解原始碼。閱讀原始碼的過程是痛苦的,一個原因是原始碼量巨大,檔案依賴關係複雜容易讓人產生恐懼退縮心理,另一個是閱讀原始碼是個漫長的過程,期間可能會佔用你學習其他新技術的時間,讓你無法完全靜下心來。但是其實我們要明白的是,學習原始碼不只是為了應付面試,原始碼中其實有很多我們可以借鑑的設計模式或者使用技巧,如果我們可以學習並應用到我們正在做的專案中,也不失為一件有意義的事情。後續文章就從ReactDOM.render方法開始,一步一步分析元件渲染的整個流程,我們也不需要去搞懂每一行程式碼,畢竟每個人的思路不太一樣,但是關鍵步驟我們還是需要去多花時間理解的。

5、交流

如果你覺得這篇文章的內容對你有幫助,能否幫個忙關注一下筆者的公眾號[前端之境],每週都會努力原創一些前端技術乾貨,關注公眾號後可以邀你加入前端技術交流群,我們可以一起互相交流,共同進步。

文章已同步更新至Github部落格,若覺文章尚可,歡迎前往star!

你的一個點贊,值得讓我付出更多的努力!

逆境中成長,只有不斷地學習,才能成為更好的自己,與君共勉!

相關推薦

React16原始碼解讀開篇面試考點

引言 如今,主流的前端框架React,Vue和Angular在前端領域已成三足鼎立之勢,基於前端技術棧的發展現狀,大大小小的公司或多或少也會使用其中某一項或者多項技術棧,那麼掌握並熟練使用其中至少一種也成為了前端人員必不可少的技能飯碗。當然,框架的部分實現細節也常成為面試中的考察要點,因此,一方面為了應付面試

React16原始碼解讀揭祕ReactDOM.render

引言 在上一篇文章中我們通過create-react-app腳手架快速搭建了一個簡單的示例,並基於該示例講解了在類元件中React.Component和React.PureComponent背後的實現原理。同時我們也瞭解到,通過使用Babel預置工具包@babel/preset-react可以將類元件中ren

五分鐘學Java一篇文章spring全家桶套餐

原創宣告 本文首發於微信公眾號【程式設計師黃小斜】 本文作者:黃小斜 轉載請務必在文章開頭註明出處和作者。 本文思維導圖 什麼是Spring,為什麼你要學習spring? 你第一次接觸spring框架是在什麼時候?相信很多人和我一樣,第一次瞭解spring都不是做專案的時候用到,而是在網上看到或者是聽到過

區塊鏈入門-區塊鏈-熊麗兵-專題視訊課程

區塊鏈入門-帶你搞懂區塊鏈—221人已學習 課程介紹         區塊鏈已火遍全球,很多人都想要能夠清晰的瞭解什麼是區塊鏈以及區塊鏈的價值在哪裡,本課程將從入門開始,為你講解區塊鏈技術核心概念與

支援向量機SVM演算法原理

一、原理 1. 線性可分支援向量機 問題的輸入輸出 X = {x1,x2,...,xnx1,x2,...,xn} Y = {+1, -1} 模型: 感知機的目的是找到一個可以正確分類資料的超平面S:ω⋅x+b=0ω⋅x+b=0, 得到感知機

樸素貝葉斯分類演算法

帶你搞懂樸素貝葉斯分類算 貝葉斯分類是一類分類演算法的總稱,這類演算法均以貝葉斯定理為基礎,故統稱為貝葉斯分類。而樸素樸素貝葉斯分類是貝葉斯分類中最簡單,也是常見的一種分類方法。這篇文章我儘可能用直白的話語總結一下我們學習會上講到的樸素貝葉斯分類演算法,希望有利於他人理解。 1  分類問題綜述

樸素貝葉斯演算法原理

一、樸素貝葉斯是什麼,怎麼用? 貝葉斯定理:樸素貝葉斯定理體現了後驗概率 P(y|x)P(y|x) 、先驗概率 P(y)P(y) 、條件概率 P(x|y)P(x|y) 之間的關係: P(y|x)=P(x,y)P(x)=P(x|y)⋅P(y)

機器學習-樸素貝葉斯分類演算法

帶你搞懂樸素貝葉斯分類演算法 你搞懂樸素貝葉斯分類算 貝葉斯分類是一類分類演算法的總稱,這類演算法均以貝葉斯定理為基礎,故統稱為貝葉斯分類。而樸素樸素貝葉斯分類是貝葉斯分類中最簡單,也是常見的一種分類方法。這篇文章我儘可能用直白的話語總結一下我們學習會上講到的樸素貝葉斯分

感知機演算法原理

很多人可能聽過大名鼎鼎的SVM,這裡介紹的正是SVM演算法的基礎——感知機,感知機是一種適用於二類線性分類問題的演算法 原理 問題的輸入與輸出: X = {x1,x2,...,xnx1,x2,...,xn} Y = {+1, -1} 模型

決策樹演算法原理

一、決策樹是什麼?   顧名思義,決策樹是由一個個“決策”組成的樹,學過資料結構的同學對樹一定不陌生。決策樹中,結點分為兩種,放“決策依據”的是非葉結點,放“決策結果”的是葉結點。   那麼決策是什麼呢?很好理解,和人一樣,決策就是對於一個問題,有多個答案,

一篇文章JS對象的自我銷毀

log 還要 很快 實例化 webp dom操作 angular listener css 在日常的JS組件開發中,往往會有一些較為復雜的DOM操作及事件監聽,尤其是在處理UI層面的widgets時候更為明顯。常常會花很多精力在對象的init上,而當組件需要被移除時則僅僅是

曹工雜談手把手 JVM 的 gc 日誌

 一、前言 今天下午本來在划水,突然看到微信聯絡人那一個紅點點,看了下,應該是部落格園的朋友。加了後,這位朋友問了我一個問題:   問我,這兩塊有什麼關係? 看到這段 gc 日誌,一瞬間腦子還有點懵,嗯,這個可能要翻下書了,周志明的 Java 虛擬機器那本神書裡面有講,我果斷地打

一文什麼是測試開發!

01  開始前說點什麼 需要說明的是,原文發表於作者的公眾號中,文章篇幅雖長,但內容樸實、且能幫助讀者進一步理解測試開發工作,請讀者耐心品完~    1. 自我反省       公眾號開通了也有兩年多了,除了剛開通的那段時間發文比

【乾貨!!】十分鐘 Java AQS 核心設計與實現!!!

前言 這篇文章寫完放著也蠻久的了,今天終於釋出了,對於拖延症患者來說也真是不容易~哈哈哈。 言歸正傳,其實吧。。我覺得對於大部分想了解 AQS 的朋友來說,明白 AQS 是個啥玩意兒以及為啥需要 AQS,其實是最重要的。就像我一開始去看 AQS 的時候,抱著程式碼就啃,看不懂就去網上搜。。但是網上文章千篇一律

從定義到AST及其遍歷方式,一文Antlr4

摘要:本文將首先介紹Antlr4 grammer的定義方式,如何通過Antlr4 grammer生成對應的AST,以及Antlr4 的兩種AST遍歷方式:Visitor方式和Listener方式。 1. Antlr4簡單介紹 Antlr4(Another Tool for Language Recogniti

一文cookie,面試前端不用愁

本文由雲+社群發表 在前端面試中,有一個必問的問題:請你談談cookie和localStorage有什麼區別啊? localStorage是H5中的一種瀏覽器本地儲存方式,而實際上,cookie本身並不是用來做伺服器儲存的。但在 localStorage 出現之前,cookie被濫用當做了儲存工具,什麼資

如何進行高效的原始碼閱讀以Spring Cache擴充套件為例清楚

摘要 日常開發中,需要用到各種各樣的框架來實現API、系統的構建。作為程式設計師,除了會使用框架還必須要了解框架工作的原理。這樣可以便於我們排查問題,和自定義的擴充套件。那麼如何去學習框架呢。通常我們通過閱讀文件、檢視原始碼,然後又很快忘記。始終不能融匯貫通。本文主要基於Spring Cache擴充套件為例

Android手把手 深入讀 Retrofit 2.0 原始碼

前言 在Android開發中,網路請求十分常用 而在Android網路請求庫中,Retrofit是當下最熱的一個網路請求庫 Github截圖 今天,我將手把手帶你深入剖析Retrofit v2.0的原始碼,希望你們會喜歡 請儘量在PC端

Android手把手分析 Protocol Buffer使用 原始碼

前言 習慣用 Json、XML 資料儲存格式的你們,相信大多都沒聽過Protocol Buffer Protocol Buffer 其實 是 Google出品的一種輕量 & 高效的結構化資料儲存格式,效能比 Json、XML 真的強!太!多!

Android手把手深入剖析 Retrofit 2.0 原始碼

前言 在Andrroid開發中,網路請求十分常用 而在Android網路請求庫中,Retrofit是當下最熱的一個網路請求庫 今天,我將手把手帶你深入剖析Retrofit v2.0的原始碼,希望你們會喜歡 目錄 1. 簡介