1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        「面試」你不知道的 React 和 Vue 的 20 個區(qū)別

        共 19360字,需瀏覽 39分鐘

         ·

        2020-09-14 21:24

        作者:火狼1

        原文地址:https://juejin.im/post/5ef55acde51d4534bf67a878

        1.Vue和React源碼區(qū)別

        1.1 Vue源碼

        1.1.1 掛載

        初始化$mounted會掛載組件,不存在 render 函數(shù)時需要編譯(compile);

        1.1.2 compile

        1.compile 分為 parse,optimize 和 generate,最終得到 render 函數(shù);

        2.parse 調(diào)用 parseHtml 方法,方法核心是利用正則解析 template 的指令,class 和 stype,得到 AST;

        3.optimize 作用標(biāo)記 static 靜態(tài)節(jié)點(diǎn),后面 patch,diff會跳過靜態(tài)節(jié)點(diǎn);

        4.generate 是將 AST 轉(zhuǎn)化為 render 函數(shù)表達(dá)式,執(zhí)行 vm._render 方法將 render 表達(dá)式轉(zhuǎn)化為VNode,得到 render 和 staticRenderFns 字符串;

        5.vm._render 方法調(diào)用了 VNode 創(chuàng)建的方法createElement

        //?render函數(shù)表達(dá)式
        (function()?{
        ??with(this){
        ????return?_c('div',{???//創(chuàng)建一個?div?元素
        ??????attrs:{"id":"app"}??//div?添加屬性?id
        ??????},[
        ????????_m(0),??//靜態(tài)節(jié)點(diǎn)?header,此處對應(yīng)?staticRenderFns?數(shù)組索引為?0?的?render?function
        ????????_v("?"),?//空的文本節(jié)點(diǎn)
        ????????(message)?//判斷?message?是否存在
        ?????????//如果存在,創(chuàng)建?p?元素,元素里面有文本,值為?toString(message)
        ?????????_c('p',[_v("\n????"+_s(message)+"\n??")])
        ????????//如果不存在,創(chuàng)建?p?元素,元素里面有文本,值為?No?message.
        ????????:_c('p',[_v("\n????No?message.\n??")])
        ??????]
        ????)
        ??}
        })

        1.1.3 依賴收集與監(jiān)聽

        這部分是數(shù)據(jù)響應(yīng)式系統(tǒng)
        1.調(diào)用 observer(),作用是遍歷對象屬性進(jìn)行雙向綁定;

        2.在 observer 過程中會注冊O(shè)bject.defineProperty的 get 方法進(jìn)行依賴收集,依賴收集是將Watcher 對象的實(shí)例放入 Dep 中;

        3.Object.defineProperty的 set 會調(diào)用Dep 對象的 notify 方法通知它內(nèi)部所有的 Watcher 對象調(diào)用對應(yīng)的 update()進(jìn)行視圖更新;

        4.本質(zhì)是發(fā)布者訂閱模式的應(yīng)用

        1.1.4 diff 和 patch

        diff 算法對比差異和調(diào)用 update更新視圖:
        1.patch 的 differ 是將同層的樹節(jié)點(diǎn)進(jìn)行比較,通過唯一的 key 進(jìn)行區(qū)分,時間復(fù)雜度只有 O(n);

        2.上面將到 set 被觸發(fā)會調(diào)用 watcher 的 update()修改視圖;

        3.update 方法里面調(diào)用 patch()得到同級的 VNode 變化;

        4.update 方法里面調(diào)用createElm通過虛擬節(jié)點(diǎn)創(chuàng)建真實(shí)的 DOM 并插入到它的父節(jié)點(diǎn)中;

        5.createElm實(shí)質(zhì)是遍歷虛擬 dom,逆向解析成真實(shí) dom;

        1.2 React 源碼

        1.2.1 React.Component

        1.原型上掛載了setState和forceUpdate方法;
        2.提供props,context,refs 等屬性;
        3.組件定義通過 extends 關(guān)鍵字繼承 Component;

        1.2.2 掛載

        1.render 方法調(diào)用了React.createElement方法(實(shí)際是ReactElement方法);

        2.ReactDOM.render(component,mountNode)的形式對自定義組件/原生DOM/字符串進(jìn)行掛載;

        3.調(diào)用了內(nèi)部的ReactMount.render,進(jìn)而執(zhí)行ReactMount._renderSubtreeIntoContainer,就是將子DOM插入容器;

        4.ReactDOM.render()根據(jù)傳入不同參數(shù)會創(chuàng)建四大類組件,返回一個 VNode;

        5.四大類組件封裝的過程中,調(diào)用了mountComponet方法,觸發(fā)生命周期,解析出 HTML;

        1.2.3 組件類型和生命周期

        1.ReactEmptyComponent,ReactTextComponent,ReactDOMComponent組件沒有觸發(fā)生命周期;
        2.ReactCompositeComponent類型調(diào)用mountComponent方法,會觸發(fā)生命周期,處理 state 執(zhí)行componentWillMount鉤子,執(zhí)行 render,獲得 html,執(zhí)行componentDidMounted

        1.2.4 data 更新 setState

        細(xì)節(jié)請見 3.1

        1.2.5 數(shù)據(jù)綁定

        1.setState 更新 data 后,shouldComponentUpdate為 true會生成 VNode,為 false 會結(jié)束;2.VNode會調(diào)用 DOM diff,為 true 更新組件;

        1.3 對比

        React:
        1.單向數(shù)據(jù)流;
        2.setSate 更新data 值后,組件自己處理;3.differ 是首位是除刪除外是固定不動的,然后依次遍歷對比;

        Vue:
        1.v-model 可以實(shí)現(xiàn)雙向數(shù)據(jù)流,但只是v-bind:value 和 v-on:input的語法糖;
        2.通過 this 改變值,會觸發(fā) Object.defineProperty的 set,將依賴放入隊(duì)列,下一個事件循環(huán)開始時執(zhí)行更新時才會進(jìn)行必要的DOM更新,是外部監(jiān)聽處理更新;
        3.differcompile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù),而且是采用雙向遍歷方法;

        2.React 和 Vue 渲染過程區(qū)別

        2.1 React

        1.生成期(掛載):參照 1.2.12.更新: 參照1.1.3和 1.1.43.卸載:銷毀掛載的組件

        2.2 Vue

        1.new Vue()初始化后initLifecycle(vm),initEvents(vm),initRender(vm),callHook(vm,beforeCreate),initState(vm),callHook(vm,created);

        ?A.initLifecycle, 建立父子組件關(guān)系,在當(dāng)前實(shí)例上添加一些屬性和生命周期標(biāo)識。如:children、refs、_isMounted等;??
        ?B.initEvents,用來存放除@hook:生命周期鉤子名稱="綁定的函數(shù)"事件的對象。如:$on、$emit等;??
        ?C.initRender,用于初始化$slots、$attrs、$listeners;?
        ?D.initState,是很多選項(xiàng)初始化的匯總,包括:props、methods、data、computed 和 watch 等;??
        ?E.callHook(vm,created)后才掛載實(shí)例?

        2.compileToFunction:就是將 template 編譯成 render 函數(shù);
        3.watcher: 就是執(zhí)行1.2.3;
        4.patch:就是執(zhí)行 1.2.4

        3.AST 和 VNode 的異同

        1.都是 JSON 對象;

        2.AST 是HTML,JS,Java或其他語言的語法的映射對象,VNode 只是 DOM 的映射對象,AST 范圍更廣;

        3.AST的每層的element,包含自身節(jié)點(diǎn)的信息(tag,attr等),同時parent,children分別指向其父element和子element,層層嵌套,形成一棵樹

        "app">
        ??

          ????"item?in?items">
          ??????itemid:{{item.id}}
          ????
          ??



        //轉(zhuǎn)化為?AST?格式為
        {
        ????"type":?1,
        ????"tag":?"div",
        ????"attrsList":?[
        ????????{
        ????????????"name":?"id",
        ????????????"value":?"app"
        ????????}
        ????],
        ????"attrsMap":?{
        ????????"id":?"app"
        ????},
        ????"children":?[
        ????????{
        ????????????"type":?1,
        ????????????"tag":?"ul",
        ????????????"attrsList":?[],
        ????????????"attrsMap":?{},
        ????????????"parent":?{
        ????????????????"$ref":?"$"
        ????????????},
        ????????????"children":?[
        ????????????????{
        ????????????????????"type":?1,
        ????????????????????"tag":?"li",
        ????????????????????//?children省略了很多屬性,表示格式即可
        ????????????????}
        ????????????],
        ????????????"plain":?true
        ????????}
        ????],
        ????"plain":?false,
        ????"attrs":?[
        ????????{
        ????????????"name":?"id",
        ????????????"value":?"\"app\""
        ????????}
        ????]
        }

        4.vnode就是一系列關(guān)鍵屬性如標(biāo)簽名、數(shù)據(jù)、子節(jié)點(diǎn)的集合,可以認(rèn)為是簡化了的dom:

        {
        ??tag:?string?|?void;
        ??data:?VNodeData?|?void;
        ??children:??Array;
        ??text:?string?|?void;
        ??elm:?Node?|?void;
        ??ns:?string?|?void;
        ??context:?Component?|?void;
        ??...
        }

        5.VNode 的基本分類:EmptyVNode,TextVNode,ComponentVNNode,ElementVNNode,CloneVNode

        6.創(chuàng)建 VNode

        方法一:
        //?利用createDocumentFragment()創(chuàng)建虛擬?dom?片段
        //?節(jié)點(diǎn)對象包含dom所有屬性和方法

        //?html
        "ul">
        //?js
        const?element??=?document.getElementById('ul');
        const?fragment?=?document.createDocumentFragment();
        const?browsers?=?['Firefox',?'Chrome',?'Opera',?'Safari',?'Internet?Explorer'];

        browsers.forEach(function(browser)?{
        ????const?li?=?document.createElement('li');
        ????li.textContent?=?browser;
        ????fragment.appendChild(li);??//?此處往文檔片段插入子節(jié)點(diǎn),不會引起回流?(相當(dāng)于打包操作)
        });
        console.log(fragment)
        element.appendChild(fragment);??//?將打包好的文檔片段插入ul節(jié)點(diǎn),只做了一次操作,時間快,性能好

        方法二:
        //?用?JS?對象來模擬?VNode
        function?Element?(tagName,?props,?children)?{
        ??console.log('this',this)
        ??this.tagName?=?tagName
        ??this.props?=?props
        ??this.children?=?children
        }

        let?ElementO?=new?Element('ul',?{id:?'list'},?[
        ??new?Element('li',?{class:?'item'},?['Item?1']),
        ??new?Element('li',?{class:?'item'},?['Item?2']),
        ??new?Element('li',?{class:?'item'},?['Item?3'])
        ])

        //?利用?render?渲染到頁面
        Element.prototype.render?=?function?()?{
        ??const?el?=?document.createElement(this.tagName)?//?根據(jù)tagName構(gòu)建
        ??const?props?=?this.props

        ??for?(const?propName?in?props)?{?//?設(shè)置節(jié)點(diǎn)的DOM屬性
        ????const?propValue?=?props[propName]
        ????el.setAttribute(propName,?propValue)
        ??}

        ??const?children?=?this.children?||?[]

        ??children.forEach(function?(child)?{
        ????const?childEl?=?(child?instanceof?Element)
        ????????child.render()?//?如果子節(jié)點(diǎn)也是虛擬DOM,遞歸構(gòu)建DOM節(jié)點(diǎn)
        ??????:?document.createTextNode(child)?//?如果字符串,只構(gòu)建文本節(jié)點(diǎn)
        ????el.appendChild(childEl)
        ??})

        ??return?el
        }
        console.log('ElementO',ElementO)
        var?ulRoot?=?ElementO.render()
        console.log('ulRoot',ulRoot)
        document.body.appendChild(ulRoot)

        4.React 和Vue 的 differ 算法區(qū)

        4.1 React

        1.Virtual DOM 中的首個節(jié)點(diǎn)不執(zhí)行移動操作(除非它要被移除),以該節(jié)點(diǎn)為原點(diǎn),其它節(jié)點(diǎn)都去尋找自己的新位置; 一句話就是首位是老大,不移動;

        2.在 Virtual DOM 的順序中,每一個節(jié)點(diǎn)與前一個節(jié)點(diǎn)的先后順序與在 Real DOM 中的順序進(jìn)行比較,如果順序相同,則不必移動,否則就移動到前一個節(jié)點(diǎn)的前面或后面;

        3.tree diff:只會同級比較,如果是跨級的移動,會先刪除節(jié)點(diǎn) A,再創(chuàng)建對應(yīng)的 A;將 O(n3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 復(fù)雜度;

        4.component diff:
        根據(jù)batchingStrategy.isBatchingUpdates值是否為 true;如果true 同一類型組件,按照 tree differ 對比;如果 false將組件放入 dirtyComponent,下面子節(jié)點(diǎn)全部替換,具體邏輯看 3.1 setSate

        5.element differ:
        tree differ 下面有三種節(jié)點(diǎn)操作:INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和 REMOVE_NODE(刪除)?請戳

        6.代碼實(shí)現(xiàn)

        _updateChildren:?function(nextNestedChildrenElements,?transaction,?context)?{
        ???var?prevChildren?=?this._renderedChildren
        ??var?removedNodes?=?{}
        ??var?mountImages?=?[]

        ??//?獲取新的子元素數(shù)組
        ??var?nextChildren?=?this._reconcilerUpdateChildren(
        ????prevChildren,
        ????nextNestedChildrenElements,
        ????mountImages,
        ????removedNodes,
        ????transaction,
        ????context
        ??)

        ??if?(!nextChildren?&&?!prevChildren)?{
        ????return
        ??}

        ??var?updates?=?null
        ??var?name
        ??var?nextIndex?=?0
        ??var?lastIndex?=?0
        ??var?nextMountIndex?=?0
        ??var?lastPlacedNode?=?null

        ??for?(name?in?nextChildren)?{
        ????if?(!nextChildren.hasOwnProperty(name))?{
        ??????continue
        ????}
        ????var?prevChild?=?prevChildren?&&?prevChildren[name]
        ????var?nextChild?=?nextChildren[name]
        ????if?(prevChild?===?nextChild)?{
        ??????//?同一個引用,說明是使用的同一個component,所以我們需要做移動的操作
        ??????//?移動已有的子節(jié)點(diǎn)
        ??????// NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動
        ??????updates?=?enqueue(
        ????????updates,
        ????????this.moveChild(prevChild,?lastPlacedNode,?nextIndex,?lastIndex)
        ??????)

        ??????//?更新lastIndex
        ??????lastIndex?=?Math.max(prevChild._mountIndex,?lastIndex)
        ??????//?更新component的.mountIndex屬性
        ??????prevChild._mountIndex?=?nextIndex

        ????}?else?{
        ??????if?(prevChild)?{
        ????????//?更新lastIndex
        ????????lastIndex?=?Math.max(prevChild._mountIndex,?lastIndex)
        ??????}

        ??????//?添加新的子節(jié)點(diǎn)在指定的位置上
        ??????updates?=?enqueue(
        ????????updates,
        ????????this._mountChildAtIndex(
        ??????????nextChild,
        ??????????mountImages[nextMountIndex],
        ??????????lastPlacedNode,
        ??????????nextIndex,
        ??????????transaction,
        ??????????context
        ????????)
        ??????)


        ??????nextMountIndex++
        ????}

        ????//?更新nextIndex
        ????nextIndex++
        ????lastPlacedNode?=?ReactReconciler.getHostNode(nextChild)
        ??}

        ??//?移除掉不存在的舊子節(jié)點(diǎn),和舊子節(jié)點(diǎn)和新子節(jié)點(diǎn)不同的舊子節(jié)點(diǎn)
        ??for?(name?in?removedNodes)?{
        ????if?(removedNodes.hasOwnProperty(name))?{
        ??????updates?=?enqueue(
        ????????updates,
        ????????this._unmountChild(prevChildren[name],?removedNodes[name])
        ??????)
        ????}
        ??}
        ??}

        4.2 Vue

        1.自主研發(fā)了一套Virtual DOM,是借鑒開源庫snabbdom,?snabbdom地址

        2.也是同級比較,因?yàn)樵?compile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù);

        3.Vue 的這個 DOM Diff 過程就是一個查找排序的過程,遍歷 Virtual DOM 的節(jié)點(diǎn),在 Real DOM 中找到對應(yīng)的節(jié)點(diǎn),并移動到新的位置上。不過這套算法使用了雙向遍歷的方式,加速了遍歷的速度,?更多請戳;

        4.代碼實(shí)現(xiàn):

        updateChildren?(parentElm,?oldCh,?newCh)?{
        ????let?oldStartIdx?=?0,?newStartIdx?=?0
        ??let?oldEndIdx?=?oldCh.length?-?1
        ??let?oldStartVnode?=?oldCh[0]
        ??let?oldEndVnode?=?oldCh[oldEndIdx]
        ??let?newEndIdx?=?newCh.length?-?1
        ??let?newStartVnode?=?newCh[0]
        ??let?newEndVnode?=?newCh[newEndIdx]
        ??let?oldKeyToIdx
        ??let?idxInOld
        ??let?elmToMove
        ??let?before
        ??while?(oldStartIdx?<=?oldEndIdx?&&?newStartIdx?<=?newEndIdx)?{
        ????if?(oldStartVnode?==?null)?{???//對于vnode.key的比較,會把oldVnode?=?null
        ??????oldStartVnode?=?oldCh[++oldStartIdx]
        ????}else?if?(oldEndVnode?==?null)?{
        ??????oldEndVnode?=?oldCh[--oldEndIdx]
        ????}else?if?(newStartVnode?==?null)?{
        ??????newStartVnode?=?newCh[++newStartIdx]
        ????}else?if?(newEndVnode?==?null)?{
        ??????newEndVnode?=?newCh[--newEndIdx]
        ????}else?if?(sameVnode(oldStartVnode,?newStartVnode))?{
        ??????patchVnode(oldStartVnode,?newStartVnode)
        ??????oldStartVnode?=?oldCh[++oldStartIdx]
        ??????newStartVnode?=?newCh[++newStartIdx]
        ????}else?if?(sameVnode(oldEndVnode,?newEndVnode))?{
        ??????patchVnode(oldEndVnode,?newEndVnode)
        ??????oldEndVnode?=?oldCh[--oldEndIdx]
        ??????newEndVnode?=?newCh[--newEndIdx]
        ????}else?if?(sameVnode(oldStartVnode,?newEndVnode))?{
        ??????patchVnode(oldStartVnode,?newEndVnode)
        ??????api.insertBefore(parentElm,?oldStartVnode.el,?api.nextSibling(oldEndVnode.el))
        ??????oldStartVnode?=?oldCh[++oldStartIdx]
        ??????newEndVnode?=?newCh[--newEndIdx]
        ????}else?if?(sameVnode(oldEndVnode,?newStartVnode))?{
        ??????patchVnode(oldEndVnode,?newStartVnode)
        ??????api.insertBefore(parentElm,?oldEndVnode.el,?oldStartVnode.el)
        ??????oldEndVnode?=?oldCh[--oldEndIdx]
        ??????newStartVnode?=?newCh[++newStartIdx]
        ????}else?{
        ??????//?使用key時的比較
        ??????if?(oldKeyToIdx?===?undefined)?{
        ????????oldKeyToIdx?=?createKeyToOldIdx(oldCh,?oldStartIdx,?oldEndIdx)?//?有key生成index表
        ??????}
        ??????idxInOld?=?oldKeyToIdx[newStartVnode.key]
        ??????if?(!idxInOld)?{
        ????????api.insertBefore(parentElm,?createEle(newStartVnode).el,?oldStartVnode.el)
        ????????newStartVnode?=?newCh[++newStartIdx]
        ??????}
        ??????else?{
        ????????elmToMove?=?oldCh[idxInOld]
        ????????if?(elmToMove.sel?!==?newStartVnode.sel)?{
        ??????????api.insertBefore(parentElm,?createEle(newStartVnode).el,?oldStartVnode.el)
        ????????}else?{
        ??????????patchVnode(elmToMove,?newStartVnode)
        ??????????oldCh[idxInOld]?=?null
        ??????????api.insertBefore(parentElm,?elmToMove.el,?oldStartVnode.el)
        ????????}
        ????????newStartVnode?=?newCh[++newStartIdx]
        ??????}
        ????}
        ??}
        ??if?(oldStartIdx?>?oldEndIdx)?{
        ????before?=?newCh[newEndIdx?+?1]?==?null???null?:?newCh[newEndIdx?+?1].el
        ????addVnodes(parentElm,?before,?newCh,?newStartIdx,?newEndIdx)
        ??}else?if?(newStartIdx?>?newEndIdx)?{
        ????removeVnodes(parentElm,?oldCh,?oldStartIdx,?oldEndIdx)
        ??}
        }

        4.3 對比

        相同點(diǎn):
        都是同層 differ,復(fù)雜度都為 O(n);

        不同點(diǎn):
        1.React 首位是除刪除外是固定不動的,然后依次遍歷對比;
        2.Vue 的compile 階段的optimize標(biāo)記了static 點(diǎn),可以減少 differ 次數(shù),而且是采用雙向遍歷方法;

        5.React 的 setState和 Vue 改變值的區(qū)別

        5.1 setState

        1.setState 通過一個隊(duì)列機(jī)制來實(shí)現(xiàn) state 更新,當(dāng)執(zhí)行 setState() 時,會將需要更新的 state 淺合并后,根據(jù)變量 isBatchingUpdates(默認(rèn)為 false)判斷是直接更新還是放入狀態(tài)隊(duì)列;

        2.通過js的事件綁定程序 addEventListener 和使用setTimeout/setInterval 等 React 無法掌控的 API情況下isBatchingUpdates 為 false,同步更新。除了這幾種情況外batchedUpdates函數(shù)將isBatchingUpdates修改為 true;

        3.放入隊(duì)列的不會立即更新 state,隊(duì)列機(jī)制可以高效的批量更新 state。而如果不通過setState,直接修改this.state 的值,則不會放入狀態(tài)隊(duì)列;

        4.setState 依次直接設(shè)置 state 值會被合并,但是傳入 function 不會被合并;
        讓setState接受一個函數(shù)的API的設(shè)計是相當(dāng)棒的!不僅符合函數(shù)式編程的思想,讓開發(fā)者寫出沒有副作用的函數(shù),而且我們并不去修改組件狀態(tài),只是把要改變的狀態(tài)和結(jié)果返回給React,維護(hù)狀態(tài)的活完全交給React去做。正是把流程的控制權(quán)交給了React,所以React才能協(xié)調(diào)多個setState調(diào)用的關(guān)系

        //?情況一
        state={
        ??count:0
        }
        handleClick()?{
        ??this.setState({
        ????count:?this.state.count?+?1
        ??})
        ??this.setState({
        ????count:?this.state.count?+?1
        ??})
        ??this.setState({
        ????count:?this.state.count?+?1
        ??})
        }
        //?count?值依舊為1

        //?情況二
        increment(state,?props)?{
        ??return?{
        ????count:?state.count?+?1
        ??}
        }

        handleClick()?{
        ??this.setState(this.increment)
        ??this.setState(this.increment)
        ??this.setState(this.increment)
        }
        //?count?值為?3

        5.更新后執(zhí)行四個鉤子:shouleComponentUpdate,componentWillUpdate,render,componentDidUpdate

        5.2 Vue 的 this 改變

        1.vue 自身維護(hù) 一個 更新隊(duì)列,當(dāng)你設(shè)置 this.a = 'new value',DOM 并不會馬上更新;

        2.在更新 DOM 時是異步執(zhí)行的。只要偵聽到數(shù)據(jù)變化,Vue 將開啟一個隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更;

        3.如果同一個 watcher 被多次觸發(fā),只會被推入到隊(duì)列中一次;

        4.也就是下一個事件循環(huán)開始時執(zhí)行更新時才會進(jìn)行必要的DOM更新和去重;

        5.所以 for 循環(huán) 10000次 this.a = i vue只會更新一次,而不會更新10000次;

        6.data 變化后如果 computed 或 watch 監(jiān)聽則會執(zhí)行;

        6. Vue的v-for 或 ?React 的map 中為什么不要用 index作為 key

        6.1 為什么要加 key

        6.1.1 React

        1.上面的 5.1 講到 React 的 differ 中 element differ 有三種節(jié)點(diǎn)操作;

        2.場景一不加 key:
        新老集合進(jìn)行 diff 差異化對比,發(fā)現(xiàn) B != A,則創(chuàng)建并插入 B 至新集合,刪除老集合 A;以此類推,創(chuàng)建并插入 A、D 和 C,刪除 B、C 和 D;
        都是相同的節(jié)點(diǎn),但由于位置發(fā)生變化,導(dǎo)致需要進(jìn)行繁雜低效的刪除、創(chuàng)建操作,其實(shí)只要對這些節(jié)點(diǎn)進(jìn)行位置移動即可;

        3.場景二加 key:
        新建:從新集合中取得 E,判斷老集合中不存在相同節(jié)點(diǎn) E,則創(chuàng)建新節(jié)點(diǎn) ElastIndex不做處理E的位置更新為新集合中的位置,nextIndex++;
        刪除:當(dāng)完成新集合中所有節(jié)點(diǎn) diff 時,最后還需要對老集合進(jìn)行循環(huán)遍歷,判斷是否存在新集合中沒有但老集合中仍存在的節(jié)點(diǎn),發(fā)現(xiàn)存在這樣的節(jié)點(diǎn) D,因此刪除節(jié)點(diǎn) D;

        4.總結(jié):
        顯然加了 key 后操作步驟要少很多,性能更好;
        但是都會存在一個問題,上面場景二只需要移動首位,位置就可對應(yīng),但是由于首位是老大不能動,所以應(yīng)該盡量減少將最后一個節(jié)點(diǎn)移動到首位,?更多請戳。

        6.1.2 Vue

        Vue 不加 key 場景分析:
        1.場景一不加 key:
        也會將使用了雙向遍歷的方式查找,發(fā)現(xiàn) A,B,C,D都不等,先刪除再創(chuàng)建;

        2.場景二加 key:雙向遍歷的方式查找只需要創(chuàng)建E,刪除D,改變 B、C、A的位置

        6.2 為什么 key 不能為 index

        這個問題分為兩個方面:
        1.如果列表是純靜態(tài)展示,不會 CRUD,這樣用 index 作為 key 沒得啥問題;

        2.如果不是

        const?list?=?[1,2,3,4];
        //?list?刪除?4?不會有問題,但是如果刪除了非?4?就會有問題
        //?如果刪除?2
        const?listN=?[1,3,4]
        //?這樣index對應(yīng)的值就變化了,整個?list?會重新渲染

        3.所以 list 最好不要用 index 作為 key

        7. Redux和 Vuex 設(shè)計思想

        7.1 Redux

        API:
        1.Redux則是一個純粹的狀態(tài)管理系統(tǒng),React利用React-Redux將它與React框架結(jié)合起來;

        2.只有一個用createStore方法創(chuàng)建一個 store;

        3.action接收 view 發(fā)出的通知,告訴 Store State 要改變,有一個 type 屬性;

        4.reducer:純函數(shù)來處理事件,純函數(shù)指一個函數(shù)的返回結(jié)果只依賴于它的參數(shù),并且在執(zhí)行過程里面沒有副作用,得到一個新的 state;

        源碼組成:

        1.createStore?創(chuàng)建倉庫,接受reducer作為參數(shù)??
        2.bindActionCreator?綁定store.dispatch和action?的關(guān)系??
        3.combineReducers?合并多個reducers??
        4.applyMiddleware?洋蔥模型的中間件,介于dispatch和action之間,重寫dispatch
        5.compose?整合多個中間件
        6.單一數(shù)據(jù)流;state?是可讀的,必須通過?action?改變;reducer設(shè)計成純函數(shù);

        7.2 Vuex

        1.Vuex是吸收了Redux的經(jīng)驗(yàn),放棄了一些特性并做了一些優(yōu)化,代價就是VUEX只能和VUE配合;

        2.store:通過 new Vuex.store創(chuàng)建 store,輔助函數(shù)mapState;

        3.getters:獲取state,有輔助函數(shù) mapGetters;

        4.action:異步改變 state,像ajax,輔助函數(shù)mapActions;

        5.mutation:同步改變 state,輔助函數(shù)mapMutations;

        7.3 對比

        1.Redux:view——>actions——>reducer——>state變化——>view變化(同步異步一樣)
        2.Vuex:view——>commit——>mutations——>state變化——>view變化(同步操作)?
        ??view——>dispatch——>actions——>mutations——>state變化——>view變化(異步操作)

        8.redux 為什么要把 reducer 設(shè)計成純函數(shù)

        1.純函數(shù)概念:一個函數(shù)的返回結(jié)果只依賴于它的參數(shù)(外面的變量不會改變自己),并且在執(zhí)行過程里面沒有副作用(自己不會改變外面的變量);

        2.主要就是為了減小副作用,避免影響 state 值,造成錯誤的渲染;

        3.把reducer設(shè)計成純函數(shù),便于調(diào)試追蹤改變記錄;

        9.Vuex的mutation和Redux的reducer中為什么不能做異步操作

        1.在 vuex 里面 actions 只是一個架構(gòu)性的概念,并不是必須的,說到底只是一個函數(shù),你在里面想干嘛都可以,只要最后觸發(fā) mutation 就行;

        2.vuex 真正限制你的只有 mutation 必須是同步的這一點(diǎn)(在 redux 里面就好像 reducer 必須同步返回下一個狀態(tài)一樣);

        3.每一個 mutation 執(zhí)行完成后都可以對應(yīng)到一個新的狀態(tài)(和 reducer 一樣),這樣 devtools 就可以打個 snapshot 存下來,然后就可以隨便 time-travel 了。如果你開著 devtool 調(diào)用一個異步的 action,你可以清楚地看到它所調(diào)用的 mutation 是何時被記錄下來的,并且可以立刻查看它們對應(yīng)的狀態(tài);

        4.其實(shí)就是框架是這么設(shè)計的,便于調(diào)試追蹤改變記錄

        10.雙向綁定和 vuex 是否沖突

        1.在嚴(yán)格模式中使用Vuex,當(dāng)用戶輸入時,v-model會試圖直接修改屬性值,但這個修改不是在mutation中修改的,所以會拋出一個錯誤;

        2.當(dāng)需要在組件中使用vuex中的state時,有2種解決方案:

        在input中綁定value(vuex中的state),然后監(jiān)聽input的change或者input事件,在事件回調(diào)中調(diào)用mutation修改state的值;??

        //?雙向綁定計算屬性
        "message">

        computed:?{
        ??message:?{
        ????get?()?{
        ??????return?this.$store.state.obj.message
        ????},
        ????set?(value)?{
        ??????this.$store.commit('updateMessage',?value)
        ????}
        ??}
        }

        11. Vue的nextTick原理

        11.1 使用場景

        什么時候會用到?
        nextTick的使用原則主要就是解決單一事件更新數(shù)據(jù)后立即操作dom的場景。

        11.2 原理

        1.vue 用異步隊(duì)列的方式來控制 DOM 更新和 nextTick 回調(diào)先后執(zhí)行;

        2.microtask 因?yàn)槠涓邇?yōu)先級特性,能確保隊(duì)列中的微任務(wù)在一次事件循環(huán)前被執(zhí)行完畢;

        3.考慮兼容問題,vue 做了 microtask 向 macrotask 的降級方案;

        4.代碼實(shí)現(xiàn):

        const?simpleNextTick?=?function?queueNextTick?(cb)?{???
        ????return?Promise.resolve().then(()?=>?{
        ??????cb()
        ????})
        }

        simpleNextTick(()?=>?{
        ??console.log(this.$refs.test.innerText)
        })

        13. Vue 的data 必須是函數(shù)而 React 的 state 是對象

        13.1 Vue 的 data 必須是函數(shù)

        對象是引用類型,內(nèi)存是存貯引用地址,那么子組件中的 data 屬性值會互相污染,產(chǎn)生副作用;
        如果是函數(shù),函數(shù)的{}構(gòu)成作用域,每個實(shí)例相互獨(dú)立,不會相互影響;

        13.2 React 的 state 是對象

        因?yàn)?state 是定義在函數(shù)里面,作用域已經(jīng)獨(dú)立

        14.Vue 的合并策略

        1.生命周期鉤子:合并為數(shù)組

        function?mergeHook?(
        ??parentVal,
        ??childVal?
        )?{
        ??return?childVal
        ??????parentVal?//?如果?childVal存在
        ????????parentVal.concat(childVal)?//?如果parentVal存在,直接合并
        ??????:?Array.isArray(childVal)?//?如果parentVal不存在
        ??????????childVal??//?如果chilidVal是數(shù)組,直接返回
        ????????:?[childVal]?//?包裝成一個數(shù)組返回
        ????:?parentVal??//?如果childVal?不存在?直接返回parentVal?
        }
        //?strats中添加屬性,屬性名為生命周期各個鉤子
        config._lifecycleHooks.forEach(function?(hook)?{
        ??strats[hook]?=?mergeHook?//?設(shè)置每一個鉤子函數(shù)的合并策略
        })

        2.watch:合并為數(shù)組,執(zhí)行有先后順序;

        3.assets(components、filters、directives):合并為原型鏈?zhǔn)浇Y(jié)構(gòu),合并的策略就是返回一個合并后的新對象,新對象的自有屬性全部來自 childVal, 但是通過原型鏈委托在了 parentVal 上

        function?mergeAssets?(parentVal,?childVal)?{?//?parentVal:?Object?childVal:?Object
        ??var?res?=?Object.create(parentVal?||?null)?//?原型委托
        ??return?childVal
        ??????extend(res,?childVal)
        ????:?res
        }

        config._assetTypes.forEach(function?(type)?{
        ??strats[type?+?'s']?=?mergeAssets
        })

        4.data為function,需要合并執(zhí)行后的結(jié)果,就是執(zhí)行 parentVal 和 childVal 的函數(shù),然后再合并函數(shù)返回的對象;

        5.自定義合并策略:

        Vue.config.optionMergeStrategies.watch?=?function?(toVal,?fromVal)?{
        ??//?return?mergedVal
        }

        15.Vue-router 的路由模式

        1.三種:"hash" | "history" | "abstract";

        2.hash(默認(rèn)),history 是瀏覽器環(huán)境,abstract是 node 環(huán)境;

        3.hash: 使用 URL hash 值來作路由,是利用哈希值實(shí)現(xiàn)push、replace、go 等方法;

        4.history:依賴 HTML5 History API新增的 pushState() 和 replaceState(),需要服務(wù)器配置;

        5.abstract:如果發(fā)現(xiàn)沒有瀏覽器的 API,路由會自動強(qiáng)制進(jìn)入這個模式。

        16.Vue 的事件機(jī)制

        class?Vue?{??
        ??constructor()?{????
        ????//??事件通道調(diào)度中心????
        ????this._events?=?Object.create(null);??
        ??}??
        ??$on(event,?fn)?{????
        ????if?(Array.isArray(event))?{??????
        ??????event.map(item?=>?{????????
        ????????this.$on(item,?fn);??????
        ????});????
        ??}?else?{??????
        ????(this._events[event]?||?(this._events[event]?=?[])).push(fn);????}????
        ????return?this;?
        ?}??
        $once(event,?fn)?{????
        ??function?on()?{??????
        ????this.$off(event,?on);??????
        ????fn.apply(this,?arguments);????
        ????}????
        ????on.fn?=?fn;????
        ????this.$on(event,?on);????
        ????return?this;??
        }??
        $off(event,?fn)?{????
        ??if?(!arguments.length)?{??????
        ????this._events?=?Object.create(null);??????
        ????return?this;????
        ??}????
        ??if?(Array.isArray(event))?{??????
        ????event.map(item?=>?{????????
        ??????this.$off(item,?fn);??????
        ??});??????
        ??return?this;????
        ??}????
        const?cbs?=?this._events[event];????
        if?(!cbs)?{??????
        ??return?this;
        }????
        if?(!fn)?{??????
        ??this._events[event]?=?null;
        ??return?this;????
        }????
        let?cb;????
        let?i?=?cbs.length;????
        while?(i--)?{??????
        ??cb?=?cbs[i];??????
        ??if?(cb?===?fn?||?cb.fn?===?fn)?{????????
        ????cbs.splice(i,?1);????????
        ????break;??????
        }????
        }????
        return?this;??
        }??
        $emit(event)?{????
        ??let?cbs?=?this._events[event];????
        ??if?(cbs)?{??????
        ????const?args?=?[].slice.call(arguments,?1);??????
        ????cbs.map(item?=>?{????????
        ??????args???item.apply(this,?args)?:?item.call(this);??????
        });????
        }????
        return?this;??
        }}

        17.keep-alive 的實(shí)現(xiàn)原理和緩存策略

        1.獲取keep-alive第一個子組件;

        2.根據(jù)include exclude名單進(jìn)行匹配,決定是否緩存。如果不匹配,直接返回組件實(shí)例,如果匹配,到第3步;

        3.根據(jù)組件id和tag生成緩存組件的key,再去判斷cache中是否存在這個key,即是否命中緩存,如果命中,用緩存中的實(shí)例替代vnode實(shí)例,然后更新key在keys中的位置,(LRU置換策略)。如果沒有命中,就緩存下來,如果超出緩存最大數(shù)量max,刪除cache中的第一項(xiàng)。

        4.keep-alive是一個抽象組件:它自身不會渲染一個 DOM 元素,也不會出現(xiàn)在父組件鏈中;

        5.LRU算法:根據(jù)數(shù)據(jù)的歷史訪問記錄來進(jìn)行淘汰數(shù)據(jù),其實(shí)就是訪問過的,以后訪問概率會高;

        6.LRU 實(shí)現(xiàn):新數(shù)據(jù)插入到鏈表頭部;每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問),則將數(shù)據(jù)移到鏈表頭部;當(dāng)鏈表滿的時候,將鏈表尾部的數(shù)據(jù)丟棄。

        18.Vue 的 set 原理

        1.由于 Object.observe()方法廢棄了,所以Vue 無法檢測到對象屬性的添加或刪除;

        2.原理實(shí)現(xiàn):
        判斷是否是數(shù)組,是利用 splice 處理值;
        判斷是否是對象的屬性,直接賦值;
        不是數(shù)組,且不是對象屬性,創(chuàng)建一個新屬性,不是響應(yīng)數(shù)據(jù)直接賦值,是響應(yīng)數(shù)據(jù)調(diào)用defineReactive;

        export?function?set?(target:?Array?|?Object,?key:?any,?val:?any):?any?{
        //?如果?set?函數(shù)的第一個參數(shù)是?undefined?或?null?或者是原始類型值,那么在非生產(chǎn)環(huán)境下會打印警告信息
        //?這個api本來就是給對象與數(shù)組使用的
        if?(process.env.NODE_ENV?!==?'production'?&&
        ??(isUndef(target)?||?isPrimitive(target))
        )?{
        ??warn(`Cannot?set?reactive?property?on?undefined,?null,?or?primitive?value:?${(target:?any)}`)
        }
        if?(Array.isArray(target)?&&?isValidArrayIndex(key))?{
        ??//?類似$vm.set(vm.$data.arr,?0,?3)
        ??//?修改數(shù)組的長度,?避免索引>數(shù)組長度導(dǎo)致splcie()執(zhí)行有誤
        ??target.length?=?Math.max(target.length,?key)
        ??//?利用數(shù)組的splice變異方法觸發(fā)響應(yīng)式,?這個前面講過
        ??target.splice(key,?1,?val)
        ??return?val
        }
        // target為對象, key在target或者target.prototype上。
        //?同時必須不能在?Object.prototype?上
        //?直接修改即可,?有興趣可以看issue:?https://github.com/vuejs/vue/issues/6845
        if?(key?in?target?&&?!(key?in?Object.prototype))?{
        ??target[key]?=?val
        ??return?val
        }
        //?以上都不成立,?即開始給target創(chuàng)建一個全新的屬性
        //?獲取Observer實(shí)例
        const?ob?=?(target:?any).__ob__
        //?Vue?實(shí)例對象擁有?_isVue?屬性,?即不允許給Vue?實(shí)例對象添加屬性
        //?也不允許Vue.set/$set?函數(shù)為根數(shù)據(jù)對象(vm.$data)添加屬性
        if?(target._isVue?||?(ob?&&?ob.vmCount))?{
        ??process.env.NODE_ENV?!==?'production'?&&?warn(
        ????'Avoid?adding?reactive?properties?to?a?Vue?instance?or?its?root?$data?'?+
        ????'at?runtime?-?declare?it?upfront?in?the?data?option.'
        ??)
        ??return?val
        }
        //?target本身就不是響應(yīng)式數(shù)據(jù),?直接賦值
        if?(!ob)?{
        ??target[key]?=?val
        ??return?val
        }
        //?進(jìn)行響應(yīng)式處理
        defineReactive(ob.value,?key,?val)
        ob.dep.notify()
        return?val
        }
        https://juejin.im/post/5e04411f6fb9a0166049a073#heading-18

        19.簡寫 Redux

        function?createStore(reducer)?{
        ????let?state;
        ????let?listeners=[];
        ????function?getState()?{
        ????????return?state;
        ????}

        ????function?dispatch(action)?{
        ????????state=reducer(state,action);
        ????????listeners.forEach(l=>l());
        ????}

        ????function?subscribe(listener)?{
        ????????listeners.push(listener);
        ????????return?function?()?{
        ????????????const?index=listeners.indexOf(listener);
        ????????????listeners.splice(inddx,1);
        ????????}
        ????}
        ????
        ????dispatch({});
        ????
        ????return?{
        ????????getState,
        ????????dispatch,
        ????????subscribe
        ????}

        }

        20. react-redux是如何來實(shí)現(xiàn)的

        源碼組成:
        1.connect 將store和dispatch分別映射成props屬性對象,返回組件
        2.context 上下文 導(dǎo)出Provider,,和 consumer
        3.Provider 一個接受store的組件,通過context api傳遞給所有子組件

        21. react16 的 fiber 理解

        1.react 可以分為 differ 階段和 commit(操作 dom)階段;

        2.v16 之前是向下遞歸算法,會阻塞;

        3.v16 引入了代號為 fiber 的異步渲染架構(gòu);

        4.fiber 核心實(shí)現(xiàn)了一個基于優(yōu)先級和requestIdleCallback循環(huán)任務(wù)調(diào)度算法;

        5.算法可以把任務(wù)拆分成小任務(wù),可以隨時終止和恢復(fù)任務(wù),可以根據(jù)優(yōu)先級不同控制執(zhí)行順序,?更多請戳;

        交流討論

        》》面試官都在用的題庫,快來看看《

        瀏覽 115
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产大屁股视频 | 鸡巴操逼视频 | 少妇被两个黑人3p喷水视频 | 久久免费国产精品1 | 丁香五月五月丁香 | 日本无码熟妇五十路视频 | 九九成人在线视频 | 张柏芝裸体视频 | 99久久精品无免国产免费 | 嗯h粗大喷水噗呲h调教bl |