前端项目常见数据清洗总结

108 阅读6分钟

常规数据扁平化

方案一:基于ES10中新增的flat实现扁平化处理

let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, 14]]], 10];
let flatArr = arr.flat(Infinity);
console.log(flatArr); //[1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]

方案二:基于concat方式进行逐级降维

while (arr.some((item) => Array.isArray(item))) {
  // arr = [].concat(...arr);
  arr = [].concat.apply([], arr); // 兼容IE
}
console.log(arr);

方案三:执行toString变为字符串的时候就已经扁平化了「不适合某一项是对象,因为对象会变为“[object Object]”」

arr = arr.toString().split(',').map(Number);
console.log(arr);

方案四:基于递归实现

Object.defineProperty(Array.prototype, 'myFlat', {
  writable: true,
  enumerable: false,
  configurable: true,
  value: function myFlat() {
    let result = [];
    let next = function next(arr) {
      arr.forEach((item) => {
        if (Array.isArray(item)) {
          next(item);
        } else {
          result.push(item);
        }
      });
    };
    next(this);
    return result;
  },
});
let flatArr = arr.myFlat();
console.log(flatArr);

特殊数组扁平化

方案一:递归查找「深度优先原则」

let address = [
  {
    code: '110000',
    name: '北京市',
    children: [
      {
        code: '110100',
        name: '北京市',
        children: [
          {
            code: '110101',
            name: '东城区',
          },
          {
            code: '110102',
            name: '西城区',
          },
          {
            code: '110105',
            name: '朝阳区',
          },
        ],
      },
    ],
  },
  {
    code: '310000',
    name: '上海市',
    children: [
      {
        code: '310100',
        name: '上海市',
        children: [
          {
            code: '310101',
            name: '黄浦区',
          },
          {
            code: '310104',
            name: '徐汇区',
          },
        ],
      },
    ],
  },
  {
    code: '440000',
    name: '广东省',
    children: [
      {
        code: '440100',
        name: '广州市',
        children: [
          {
            code: '440103',
            name: '荔湾区',
          },
          {
            code: '440104',
            name: '越秀区',
          },
        ],
      },
    ],
  },
  {
    code: '330000',
    name: '浙江省',
    children: [
      {
        code: '330100',
        name: '杭州市',
        children: [
          {
            code: '330102',
            name: '上城区',
          },
          {
            code: '330103',
            name: '下城区',
          },
        ],
      },
    ],
  },
];
const getNameByCode = (address, code) => {
  let result = '';
  const next = (arr) => {
    for (let i = 0; i < arr.length; i++) {
      let { code: codeItor, name, children } = arr[i];
      if (codeItor === code) {
        result = name;
        break;
      }
      if (children && children.length > 0) {
        next(children);
      }
    }
  };
  next(address);
  return result;
};
console.log(getNameByCode(address, '440103')); // 荔湾区

方案二:先把数组扁平化,后期查找的时候按照扁平化的数组查找即可

const getNameByCode = (function (address) {
  // 先扁平化处理「广度优先原则」
  let result = [];
  const next = (arr) => {
    // 先把本级处理好
    let temp = arr.map((item) => {
      return {
        code: item.code,
        name: item.name,
      };
    });
    result = result.concat(temp);
    // 再看是否有children,如果存在,再处理下一级
    arr.forEach((item) => {
      if (item.children && item.children.length > 0) next(item.children);
    });
  };
  next(address);
  // 返回按照code查找的方法 「去扁平话后的数据中查找」
  return function getNameByCode(code) {
    let item = result.find((item) => item.code === code);
    return item ? item.name : '';
  };
})(address);
console.log(getNameByCode('440103')); // 荔湾区

对象扁平化处理

const obj = {
  a: {
    b: 1,
    c: 2,
    d: {
      e: 3,
      2: 200,
    },
  },
  b: [1, 2, { a: 3, b: 4 }],
  c: 1,
  1: 100,
  x: {},
};
Object.defineProperty(Object.prototype, 'flatten', {
  enumerable: false,
  writable: true,
  configurable: true,
  value: function flatten(obj) {
    let result = {};
    const next = (obj, attr) => {
      // isPlainObject判断是否是纯粹对象
      let isObject = _.isPlainObject(obj),
        isArray = Array.isArray(obj);
      if (isObject || isArray) {
          // isEmptyObject判断是否为空对象
        if (isObject && _.isEmptyObject(obj)) {
          result[attr] = obj;
          return;
        }
        if (isArray && obj.length === 0) {
          result[attr] = obj;
          return;
        }
        // 不为空的情况则迭代处理 each迭代方法的封装
        _.each(obj, (value, key) => {
          let temp = isNaN(key) ? `.${key}` : `[${key}]`;
          next(value, attr ? attr + temp : key);
        });
        return;
      }
      result[attr] = obj;
    };
    next(obj, '');
    return result;
  },
});
console.log(Object.flatten(obj));

/* // 输出结果:
let objFlatten = {
  1: 100,
  'a.b': 1,
  'a.c': 2,
  'a.d.e': 3,
  'a.d[2]': 200,
  'b[0]': 1,
  'b[1]': 2,
  'b[2].a': 3,
  'b[2].b': 4,
  x: {},
}; */

常规方法

  /* 函数数据类型检测 */
  var getProto = Object.getPrototypeOf;
  var class2type = {};
  var toString = class2type.toString; // Object.prototype.toString 检测数据类型的
  var hasOwn = class2type.hasOwnProperty; // Object.prototype.hasOwnProperty 检测是否是对象自身属性
  var fnToString = hasOwn.toString; // Function.prototype.toString 把函数转换为字符串
  var ObjectFunctionString = fnToString.call(Object); // 把Object函数转换为字符串 Object.toString() => "function Object() { [native code] }"

  // 检测是否为空对象
  var isEmptyObject = function isEmptyObject(obj) {
    let keys = Object.getOwnPropertyNames(obj);
    if (typeof Symbol !== 'undefined')
      keys = keys.concat(Object.getOwnPropertySymbols(obj));
    return keys.length === 0;
  };
  
   // 检测是否为纯对象/标准普通对象 : obj.__proto__ === Object.prototype
  var isPlainObject = function isPlainObject(obj) {
    let proto, Ctor;

    if (!obj || toString.call(obj) !== '[object Object]') {
      return false;
    }

    proto = getProto(obj);
    // 匹配 Object.create(null) 创建的对象
    if (!proto) {
      return true;
    }

    Ctor = hasOwn.call(proto, 'constructor') && proto.constructor; // 获取对象原型上的constructor属性
    return (
      typeof Ctor === 'function' &&
      fnToString.call(Ctor) === ObjectFunctionString
    );
  };
  
   // 迭代数组、类数组、纯粹对象,支持迭代的结束
  const each = function each(obj, callback) {
    let isArray = isArrayLike(obj),
      isObject = isPlainObject(obj);
    if (!isArray && !isObject)
      throw new TypeError('obj must be an array or object');
    if (!isFunction(callback))
      throw new TypeError('callback must be a function');

    if (isArray) {
      // 数组和类数组的迭代
      for (let i = 0, len = obj.length; i < len; i++) {
        let item = obj[i],
          index = i;
        if (callback.call(item, item, index) === false) break;
      }
      return obj;
    }
    // 对象的迭代
    let keys = Object.getOwnPropertyNames(obj);
    if (typeof Symbol !== 'undefined')
      keys = keys.concat(Object.getOwnPropertySymbols(obj));
    for (let i = 0, len = keys.length; i < len; i++) {
      let key = keys[i],
        value = obj[key];
      if (callback.call(value, value, key) === false) break;
    }
    return obj;
  };
let obj = {
  a: {
    b: {
      c: {
        d: 1,
      },
    },
  },
};
// 可选链处理
const getValue = (objName, keyPath) => {
  keyPath = keyPath.replace(/\./g, '?.');
  return eval(`${objName}?.${keyPath}`);
};
console.log(getValue('obj', 'a.b.c.d')); // 1
console.log(getValue('obj', 'a.b')); // { c: { d: 1 } }

// reduce
const getValue = (obj, keyPath) => {
  let keys = keyPath.split('.');
  return keys.reduce((pre, cur) => {
    return pre ? pre[cur] : undefined;
  }, obj);
};
console.log(getValue(obj, 'a.b.c.d')); // 1
console.log(getValue(obj, 'a.b')); // { c: { d: 1 } }

// 递归
const getValue = (obj, keyPath) => {
  let keys = keyPath.split('.'),
    index = 0,
    result;
  const next = (x) => {
    if (index >= keys.length) return x;
    let key = keys[index++];
    result = x[key];
    if (result == null || typeof result !== 'object') return;
    next(result);
  };
  next(obj);
  return result;
};
console.log(getValue(obj, 'a.b.c.d')); // 1
console.log(getValue(obj, 'a.b')); // { c: { d: 1 } }

一维数据转数结构

方案一只能处理一级结构

let data = [
  { id: 0, parentId: null, text: '北京市' },
  { id: 1, parentId: 0, text: '海淀区' },
  { id: 4, parentId: 0, text: '朝阳区' },
  { id: 7, parentId: null, text: '上海市' },
  { id: 8, parentId: 7, text: '浦东新区' },
  { id: 11, parentId: null, text: '广州市' },
  { id: 12, parentId: 11, text: '天河区' },
  { id: 15, parentId: null, text: '深圳市' },
];
/* 方案一 (O(N^2)) */
const list2Tree = (data) => {
  // 先找出第一级列表
  let arr = data.filter((item) => item.parentId === null);
  // 递归第一级的数据,筛选出其子集数据
  arr.forEach((item) => {
    let children = data.filter((cur) => cur.parentId === item.id);
    if (children.length > 0) {
      item.children = children;
    }
  });
  return arr;
};
console.log(list2Tree(data));

方案二:利用map数据结构数据 0(N) 可以处理多级结构

/* 方案二:利用map数据结构数据 */
const list2Tree = (data) => {
  // 把data变为Map数据结构
  let map = new Map();
  data.forEach((item) => {
    map.set(item.id, item);
  });
  let arr = [];
  // 迭代数组中的每一项,根据parentId做不同的处理
  data.forEach((item) => {
    // parentId为null的,就是第一级,直接加入到arr中即可
    if (item.parentId === null) {
      arr.push(item);
    } else {
      // parentId不为null的,就是子集,需要找到其父级,把当前项加入到父级的children中
      let parent = map.get(item.parentId);
      parent.children ? parent.children.push(item) : (parent.children = [item]);
    }
  });
  return arr;
};
console.log(list2Tree(data));

数据组合问题

const obj = {
  data: [
    ['xiaoming', 'male', '18', 'beijing', '2020-01-02'],
    ['xiaohong', 'female', '20', 'shanghai', '2020-01-03'],
  ],
  columns: [
    { name: 'name', note: '' },
    { name: 'gender', note: '' },
    { name: 'age', note: '' },
    { name: 'address', note: '' },
    { name: 'date', note: '' },
  ],
};
const combine = (obj) => {
  const { data, columns } = obj,
    columnKeys = {};
  // 先把columns变为{name:0,gender:1...} 这种格式
  columns.forEach((item, index) => {
    columnKeys[item.name] = index;
  });
  // 外层迭代数据data
  return data.map((item) => {
    // item:['xiaoming', 'male', '18', 'beijing', '2020-01-02']
    // columnKeys:{name:0,gender:1, age:2,address:3,date:4}
    let obj = {};
    _.each(columnKeys, (index, key) => {
      obj[key] = item[index];
    });
    return obj;
  });
};
console.log(combine(obj));

/* // 输出下面结果:
[
  {
    name: 'xiaoming',
    gender: 'male',
    age: '18',
    address: 'beijing',
    date: '2020-01-02',
  },
  {
    name: 'xiaohong',
    gender: 'female',
    age: '20',
    address: 'shanghai',
    date: '2020-01-03',
  },
]; */

const combine = (obj) => {
  let { data, columns } = obj;
  // 把column按照列的字段名扁平化
  columns = columns.map((item) => item.name); // ['name', 'gender', 'age', 'address', 'date']
  return data.map((item) => {
    let obj = {};
    columns.forEach((key, index) => {
      obj[key] = item[index];
    });
    return obj;
  });
};
console.log(combine(obj));
const obj = {
  a: {
    b: 1,
    c: 2,
    d: {
      e: 3,
      2: 200,
    },
  },
  b: [1, 2, { a: 3, b: 4 }],
  c: 1,
  1: 100,
  x: {},
};

// 转换为JSON字符串
// console.log(JSON.stringify(obj));

// 转换为URLENCODED格式字符串「有的后台要求AJAX请求主体传递这样的格式」
// 利用qs库
// 'a[b]=1&a[c]=2&a[d][e]=3&a[d][2]=200&b[0]=1&b[1]=2&b[2][a]=3&b[2][b]=4&c=1&1=100&x='
console.log(qs.stringify(obj)); //1=100&a[b]=1&a[c]=2&a[d][2]=200&a[d][e]=3&b[0]=1&b[1]=2&b[2][a]=3&b[2][b]=4&c=1