<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          「面試」你不知道的 React 和 Vue 的 20 個(gè)區(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會(huì)掛載組件,不存在 render 函數(shù)時(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會(huì)跳過靜態(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)建一個(gè)?div?元素
          ??????attrs:{"id":"app"}??//div?添加屬性?id
          ??????},[
          ????????_m(0),??//靜態(tài)節(jié)點(diǎn)?header,此處對(duì)應(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(),作用是遍歷對(duì)象屬性進(jìn)行雙向綁定;

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

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

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

          1.1.4 diff 和 patch

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

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

          3.update 方法里面調(diào)用 patch()得到同級(jí)的 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)的形式對(duì)自定義組件/原生DOM/字符串進(jìn)行掛載;

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

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

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

          1.2.3 組件類型和生命周期

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

          1.2.4 data 更新 setState

          細(xì)節(jié)請(qǐng)見 3.1

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

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

          1.3 對(duì)比

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

          Vue:
          1.v-model 可以實(shí)現(xiàn)雙向數(shù)據(jù)流,但只是v-bind:value 和 v-on:input的語(yǔ)法糖;
          2.通過 this 改變值,會(huì)觸發(fā) Object.defineProperty的 set,將依賴放入隊(duì)列,下一個(gè)事件循環(huán)開始時(shí)執(zhí)行更新時(shí)才會(huì)進(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)識(shí)。如:children、refs、_isMounted等;??
          ?B.initEvents,用來存放除@hook:生命周期鉤子名稱="綁定的函數(shù)"事件的對(duì)象。如:$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 對(duì)象;

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

          3.AST的每層的element,包含自身節(jié)點(diǎn)的信息(tag,attr等),同時(shí)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)為是簡(jiǎ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)對(duì)象包含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),不會(huì)引起回流?(相當(dāng)于打包操作)
          });
          console.log(fragment)
          element.appendChild(fragment);??//?將打包好的文檔片段插入ul節(jié)點(diǎn),只做了一次操作,時(shí)間快,性能好

          方法二:
          //?用?JS?對(duì)象來模擬?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?渲染到頁(yè)面
          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 中的首個(gè)節(jié)點(diǎn)不執(zhí)行移動(dòng)操作(除非它要被移除),以該節(jié)點(diǎn)為原點(diǎn),其它節(jié)點(diǎn)都去尋找自己的新位置; 一句話就是首位是老大,不移動(dòng);

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

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

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

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

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

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

          ??//?獲取新的子元素?cái)?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)?{
          ??????//?同一個(gè)引用,說明是使用的同一個(gè)component,所以我們需要做移動(dòng)的操作
          ??????//?移動(dòng)已有的子節(jié)點(diǎn)
          ??????// NOTICE:這里根據(jù)nextIndex, lastIndex決定是否移動(dòng)
          ??????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,是借鑒開源庫(kù)snabbdom,?snabbdom地址

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

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

          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)?{???//對(duì)于vnode.key的比較,會(huì)把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時(shí)的比較
          ??????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 對(duì)比

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

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

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

          5.1 setState

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

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

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

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

          5.2 Vue 的 this 改變

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

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

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

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

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

          6.data 變化后如果 computed 或 watch 監(jiān)聽則會(huì)執(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.場(chǎng)景一不加 key:
          新老集合進(jìn)行 diff 差異化對(duì)比,發(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í)只要對(duì)這些節(jié)點(diǎn)進(jìn)行位置移動(dòng)即可;

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

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

          6.1.2 Vue

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

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

          6.2 為什么 key 不能為 index

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

          2.如果不是

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

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

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

          7.1 Redux

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

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

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

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

          源碼組成:

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

          7.2 Vuex

          1.Vuex是吸收了Redux的經(jīng)驗(yàn),放棄了一些特性并做了一些優(yōu)化,代價(jià)就是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 對(duì)比

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          //?雙向綁定計(jì)算屬性
          "message">

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

          11. Vue的nextTick原理

          11.1 使用場(chǎng)景

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

          11.2 原理

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

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

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

          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 是對(duì)象

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

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

          13.2 React 的 state 是對(duì)象

          因?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]?//?包裝成一個(gè)數(shù)組返回
          ????:?parentVal??//?如果childVal?不存在?直接返回parentVal?
          }
          //?strats中添加屬性,屬性名為生命周期各個(gè)鉤子
          config._lifecycleHooks.forEach(function?(hook)?{
          ??strats[hook]?=?mergeHook?//?設(shè)置每一個(gè)鉤子函數(shù)的合并策略
          })

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

          3.assets(components、filters、directives):合并為原型鏈?zhǔn)浇Y(jié)構(gòu),合并的策略就是返回一個(gè)合并后的新對(duì)象,新對(duì)象的自有屬性全部來自 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ù)返回的對(duì)象;

          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,路由會(huì)自動(dòng)強(qiáng)制進(jìn)入這個(gè)模式。

          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第一個(gè)子組件;

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

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

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

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

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

          18.Vue 的 set 原理

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

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

          export?function?set?(target:?Array?|?Object,?key:?any,?val:?any):?any?{
          //?如果?set?函數(shù)的第一個(gè)參數(shù)是?undefined?或?null?或者是原始類型值,那么在非生產(chǎn)環(huán)境下會(huì)打印警告信息
          //?這個(gè)api本來就是給對(duì)象與數(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ù)組的長(zhǎng)度,?避免索引>數(shù)組長(zhǎng)度導(dǎo)致splcie()執(zhí)行有誤
          ??target.length?=?Math.max(target.length,?key)
          ??//?利用數(shù)組的splice變異方法觸發(fā)響應(yīng)式,?這個(gè)前面講過
          ??target.splice(key,?1,?val)
          ??return?val
          }
          // target為對(duì)象, key在target或者target.prototype上。
          //?同時(shí)必須不能在?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)建一個(gè)全新的屬性
          //?獲取Observer實(shí)例
          const?ob?=?(target:?any).__ob__
          //?Vue?實(shí)例對(duì)象擁有?_isVue?屬性,?即不允許給Vue?實(shí)例對(duì)象添加屬性
          //?也不允許Vue.set/$set?函數(shù)為根數(shù)據(jù)對(duì)象(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.簡(jiǎn)寫 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屬性對(duì)象,返回組件
          2.context 上下文 導(dǎo)出Provider,,和 consumer
          3.Provider 一個(gè)接受store的組件,通過context api傳遞給所有子組件

          21. react16 的 fiber 理解

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

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

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

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

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

          交流討論

          》》面試官都在用的題庫(kù),快來看看《

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草一区 | 国产中文99 | 国产精品色婷婷99久久精品 | 99精品在线免费观看视频 | 91爱电影 |