react原始碼ReactTreeTraversal.js之資料結構與演算法

weixin_33806914發表於2019-02-26

面試中,經常遇到的一個簡單演算法題:查詢兩個單連結串列的公共節點
最近在讀react原始碼的時候發現一個react樹中對該演算法的運用(見getLowestCommonAncestor函式),在此做簡單的記錄。
git地址

getParent

在react樹中獲取當前例項節點的父節點例項

//HostComponent元件對應的DOM,比如App的tag=3, 表示為類元件,其child為tag=5對應div元素。
function getParent(inst) {
  do {
    inst = inst.return;
    // TODO: If this is a HostRoot we might want to bail out.
    // That is depending on if we want nested subtrees (layers) to bubble
    // events to their parent. We could also go through parentNode on the
    // host node but that wouldn't work for React Native and doesn't let us
    // do the portal feature.
  } while (inst && inst.tag !== HostComponent);
  if (inst) {
    return inst;
  }
  return null;
}

getLowestCommonAncestor

獲取節點A與B的最近的公共祖先節點

演算法題:找到兩個連結串列的公共節點

export function getLowestCommonAncestor(instA, instB) {
  //獲取子節點A在樹中的深度
  let depthA = 0;
  for (let tempA = instA; tempA; tempA = getParent(tempA)) {
    depthA++;
  }
    //獲取子節點B在樹中的深度
  let depthB = 0;
  for (let tempB = instB; tempB; tempB = getParent(tempB)) {
    depthB++;
  }

  // If A is deeper, crawl up.
  // 如果A的高度高,那麼A節點先往上走depthA - depthB個節點,最後同時走,直到父節點是同一個
  while (depthA - depthB > 0) {
    instA = getParent(instA);
    depthA--;
  }

    // 如果B的高度高,那麼B節點先往上走depthB - depthB個節點,最後同時走,直到父節點是同一個
  // If B is deeper, crawl up.
  while (depthB - depthA > 0) {
    instB = getParent(instB);
    depthB--;
  }

  // Walk in lockstep until we find a match.
  // 現在,指標所處的位置的高度一致,可以同時往上查詢,直到找到公共的節點
  let depth = depthA;
  while (depth--) {
    if (instA === instB || instA === instB.alternate) {
      return instA;
    }
    instA = getParent(instA);
    instB = getParent(instB);
  }
  return null;
}

isAncestor

判斷A節點是否是B節點的祖先節點

export function isAncestor(instA, instB) {
  while (instB) {
    if (instA === instB || instA === instB.alternate) {
      return true;
    }
    instB = getParent(instB);
  }
  return false;
}

getParentInstance

對getParent的export封裝:

export function getParentInstance(inst) {
  return getParent(inst);
}

traverseTwoPhase

對inst及其以上的樹執行冒泡捕獲的操作,執行fn。類似事件的冒泡捕獲

export function traverseTwoPhase(inst, fn, arg) {
  const path = [];
  //將inst的父節點入棧,陣列最後的為最遠的祖先
  while (inst) {
    path.push(inst);
    inst = getParent(inst);
  }
  let i;
  //從最遠的祖先開始向inst節點捕獲執行fn
  for (i = path.length; i-- > 0; ) {
    fn(path[i], 'captured', arg);
  }
    //從inst節點開始向最遠的祖先節點冒泡執行fn
  for (i = 0; i < path.length; i++) {
    fn(path[i], 'bubbled', arg);
  }
}

traverseEnterLeave

當關注點從from節點移出然後移入to節點的時候,在from執行執行類似移入移出的操作,from節點

export function traverseEnterLeave(from, to, fn, argFrom, argTo) {
  const common = from && to ? getLowestCommonAncestor(from, to) : null;
  const pathFrom = [];
  while (true) {
    if (!from) {
      break;
    }
    if (from === common) {
      break;
    }
    const alternate = from.alternate;
    if (alternate !== null && alternate === common) {
      break;
    }
    pathFrom.push(from);
    from = getParent(from);
  }
  const pathTo = [];
  while (true) {
    if (!to) {
      break;
    }
    if (to === common) {
      break;
    }
    const alternate = to.alternate;
    if (alternate !== null && alternate === common) {
      break;
    }
    pathTo.push(to);
    to = getParent(to);
  }
  //以上程式碼將from節點到from與to節點的最近公共祖先節點(不包括公共祖先節點)push到pathFrom陣列
  //以上程式碼將to節點到from與to節點的最近公共祖先節點(不包括公共祖先節點)push到pathTo陣列

  // 以下程式碼用於對pathFrom冒泡,執行fn
  for (let i = 0; i < pathFrom.length; i++) {
    fn(pathFrom[i], 'bubbled', argFrom);
  }
    // 以下程式碼用於對pathTo捕獲,執行fn
  for (let i = pathTo.length; i-- > 0; ) {
    fn(pathTo[i], 'captured', argTo);
  }
}

相關文章