某司的手写题:行政区域层级控制

320 阅读5分钟

前言

  • 常网IT戳我呀!
  • 常网IT源码上线啦!
  • 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
  • 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
  • 接下来请看某司的面试题:给你行政区域的数据,根据传入的层级返回对应的数据结构。

你一生追求的东西,
其实一开始就在。
一开始我就拥有世界上最珍贵的爱。
这一年慢慢才明白,
一生所求无非是爱与自由,
只是你后知后觉而已,
兜兜转转追寻一圈后,
才发现那把钥匙早已握在手中。
人无法同时拥有青春和对于青春的感受,
有些东西要靠失去才明证明它的珍贵。

1.jpeg

一、问题剖析

现在我们有一份行政区划的数据(这是全部层级都有的);

需要根据传入的层级,如(只展示省,或者只展示省市)

来显示对应的数据结构。

二、结构

行政区划数据如下:

[
  {
    "code": "44",
    "name": "广东省",
    "children": [
      {
        "code": "4401",
        "name": "广州市",
        "children": [
          {
            "code": "440103",
            "name": "荔湾区",
            "children": [
              {
                "code": "440103001",
                "name": "沙面街道"
              },
             
            ]
          }
        ]
      },
    ]
  },
  {
    "code": "11",
    "name": "北京市",
    "children":[
      ...
    ]
  },
]

1.png

然后传入层级。

如:只需要展示省市,后面的区、街道就不需要展示了。

要求我们在这份数据结构中处理。

三、简单粗暴

于是,我们简单粗暴,可以写出实习生都会写的代码:

let provincesCitiesDistricts_tmp = data  // 行政区划数据
if (formatArea == 1) {
  // 1:省
  provincesCitiesDistricts_tmp.forEach((f) => {
    f.children = null;
  });
} else if (formatArea == 2) {
  // 2:省市
  provincesCitiesDistricts_tmp.forEach((f) => {
    Array.isArray(f.children) &&
      f.children.forEach((c) => {
      // 把市,后面的children赋为null
        c.children = null;
      });
  });
} else if (formatArea == 3) {
// 3:省市区
  provincesCitiesDistricts_tmp.forEach((f) => {
    Array.isArray(f.children) &&
      f.children.forEach((c) => {
        Array.isArray(c.children) &&
          c.children.forEach((c1) => {
            c1.children = null;
          });
      });
  });
} else if (formatArea == 4) {
// 4:省市区街道
  provincesCitiesDistricts_tmp.forEach((f) => {
    Array.isArray(f.children) &&
      f.children.forEach((c) => {
        Array.isArray(c.children) &&
          c.children.forEach((c1) => {
            Array.isArray(c1.children) &&
              c1.children.forEach((c2) => {
                c2.children = null;
              });
          });
      });
  });
}

obj["options"] = provincesCitiesDistricts_tmp;  // 这是最终的结果

面试官一看,这是摇摇头,说出你句经典的话,你回去等通知吧!

于是你,泪流满面的狂跑而去🚶。

于是你行者孙,第二天有备而来,并说道,这一次,我肯定能写出让你欣喜万分的来。

你左牵黄,右擎苍。

娓娓写来。

四:优解

我们停下来思考一下🧐,如果传入的是省市(控制到第二层),那只是将children循环到第二级的时候,将children赋空即可。

我们在回味一下之前写的这段代码。

f.children.forEach((c) => {
Array.isArray(c.children) &&
  c.children.forEach((c1) => {  // 这里
    Array.isArray(c1.children) &&
      c1.children.forEach((c2) => {  // 这里
        c2.children = null;
      });
  });
});

发现都很类似的写法。

那么,上面这部分的代码可以用递归实现。

// 设置children
const setChildren = (list, num = 0, index = 1) => {
  Array.isArray(list) &&
    list.forEach((f) => {
      // 只要这两个相等,则表示这一层的children需要赋空
      if (num == index) {
        f.children = null;
      } else {
        index++;
        setChildren(f.children, num, index);	// 递归调用
      }
    });
};

let provincesCitiesDistricts_tmp = data  // 行政区划数据
if (formatArea == 1) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    f.children = null;
  });
} else if (formatArea == 2) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    setChildren(f.children, 1);
  });
} else if (formatArea == 3) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    setChildren(f.children, 2);
  });
} else if (formatArea == 4) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    setChildren(f.children, 3);
  });
} else if (formatArea == 5) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    setChildren(f.children, 4);
  });
}

obj["optionsString"] = provincesCitiesDistricts_tmp;

别急,一步一步来😁。

到这里,我们巧用递归优化了很多代码,我们看到if...else也是很恶心,接着优化代码:

const setChildren = (list, num = 0, index = 1) => {
  Array.isArray(list) &&
    list.forEach((f) => {
      if (num == index) {
        f.children = null;
      } else {
        index++;
        setChildren(f.children, num, index);
      }
    });
};

let provincesCitiesDistricts_tmp = data  // 行政区划数据
if (formatArea == 1) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    f.children = null;
  });
} else if (formatArea) {
  provincesCitiesDistricts_tmp.forEach((f) => {
    setChildren(f.children, formatArea - 1);
  });
}

obj["optionsString"] = provincesCitiesDistricts_tmp;

根据formatArea是多少层,控制行政区域到多少层显示。

五:深拷贝递归

🙋面试官:你小紫,对递归很熟?那你用递归实现一下深拷贝吧。

🙋🏻‍♂️这不是信手拈来的事情嘛!

/**
* @description 提问:
* @description 1. 为什么使用WeakMap?
* @description 因为WeakMap是弱引用,可以防止递归进入死循环
* @description 2. 为什么使用obj.constructor()创建空对象?
* @description 构造函数新建一个空的对象,而不是使用{}或者[],这样可以保持原形链的继承
* @description 3. 有必要加上obj.hasOwnProperty(key)判断
* @description 判断属性是否来自原型链上,因为for..in..也会遍历其原型链上的可枚举属性
*
* @description 深拷贝(递归拷贝)
*
* @param {Array} obj 目标数组
* @param {WeakMap} [cache=new WeakMap()]
* @return {*}
* @memberof ArrayTool
*/
deepCopy1(obj, cache = new WeakMap()) {
    if (obj === null || typeof obj !== "object") return obj
    if (obj instanceof Date) return new Date(obj)
    if (obj instanceof RegExp) return new RegExp(obj)

    if (cache.has(obj)) return cache.get(obj) // 如果出现循环引用,则返回缓存的对象,防止递归进入死循环(Node的循环引用也返回缓存对象)
    let cloneObj = new obj.constructor() // 使用对象所属的构造函数创建一个新对象
    cache.set(obj, cloneObj) // 缓存对象,用于循环引用的情况

    for (let key in obj) {
      if (obj.hasOwnProperty(key)) {
        cloneObj[key] = this.deepCopy1(obj[key], cache) // 递归拷贝
      }
    }

    return cloneObj
}

这可以直接用在自己项目中的哦~

面试官摸了摸胡子,顺便摸了摸我那八块腹肌,明天来报到。

后记

其实这道题在实际项目中,行政区域很常见,行政区域数据无非是接口按需返回。

或者是在数据不变的情况下,数据结构放在前段项目中,由前段管理。

如果有其他更好的方法也欢迎评论区见,这里提供的只是诸多方法之一。

最后,祝君能拿下满意的offer。

我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

以往推荐

靓仔,说一下keep-alive缓存组件后怎么更新及原理?

面试官问我watch和computed的区别以及选择?

面试官问我new Vue阶段做了什么?

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

项目中你不知道的Axios骚操作(手写核心原理、兼容性)

VuePress搭建项目组件文档

原文链接

juejin.cn/post/733758…