前端 js 笔试题:实现一个 query 方法

2,037 阅读2分钟

目标

实现一个 query 方法,实现对数据的链式查询和处理

要求

  • query 传入参数为原始数据(数据格式,每个元素都是对象)
  • 通过进行链式调用对数据执行操作,支持的方法有
    • where(predicate) :根据参数的条件进行筛选,参数与 [].filter 的参数类似
    • orderBy(key,desc):根据key的值进行排列,默认升序排列,当第二个参数为true时降序排列
    • groupBy(key):根据key的值对数据元素进行分组,合并为二维数组
    • excuse():执行所有处理并返回最终结果
  • 执行 execute 方法时才真正执行操作并返回结果
  • 请结合下面示例理解需求
function query(data) {
  // 在这里输入你的代码
}
const data = [
  { name: "foo", age: 16, city: "shanghai" },
  { name: "bar", age: 24, city: "hangzhou" },
  { name: "fiz", age: 22, city: "shanghai" },
  { name: "baz", age: 19, city: "hanzghou" },
  { name: "bae", age: 23, city: "hangzhou" },
];

query(data)
  .where((item) => item.age > 18)
  .orderBy("name")
  .groupBy("city")
  .execute();
// 结果返回
[
  [
    { name: 'bae', age: 23, city: 'hangzhou' },
    { name: 'bar', age: 24, city: 'hangzhou' }
  ],
  [ { name: 'baz', age: 19, city: 'hanzghou' } ],
  [ { name: 'fiz', age: 22, city: 'shanghai' } ]
]

我的实现

function query(data) {
  if (!(this instanceof query)) return new query(data);
  this.wrapper = data;
  this.callbcakFnStack = [];
}

// where方法
query.prototype.where = function (predicate) {
  const _where = function (predicate) {
    const len = this.wrapper.length;
    const _newArr = [];
    if (typeof predicate !== "function") {
      throw new Error("传入的参数必须是函数");
    }
    for (let i = 0; i < len; i++) {
      if (predicate.call(this.wrapper, this.wrapper[i], i)) {
        if (typeof this.wrapper[i] === "object") {
          _newArr.push({ ...this.wrapper[i] });
        } else {
          _newArr.push(this.wrapper[i]);
        }
      }
    }
    this.wrapper = _newArr;
    return this;
  };
  this.pushCallBack(_where.bind(this, predicate));
  return this;
};

// orderBy方法
query.prototype.orderBy = function (key, desc) {
  const _orderBy = function (key, desc) {
    this.wrapper.sort((pre, next) => {
      if (typeof pre[key] === "string") {
        if (desc) return next[key].localeCompare(pre[key]);
        return pre[key].localeCompare(next[key]);
      } else if (typeof pre[key] === "number") {
        if (desc) return next[key] - pre[key];
        return pre[key] - next[key];
      } else if (typeof pre[key] === "object" && pre[key] != null) {
        throw new Error("目前暂不支持为属性为对象排序");
      }
    });
    return this;
  };
  this.pushCallBack(_orderBy.bind(this, key, desc));
  return this;
};

// groupBy方法
query.prototype.groupBy = function (key) {
  const _groupBy = function (key) {
    const res = [];
    this.wrapper.forEach((n) => {
      res[n[key]] = res[n[key]] ? [...res[n[key]], n] : [n];
    });
    this.wrapper = Object.values(res)
    return this;
  };
  this.pushCallBack(_groupBy.bind(this, key));
  return this;
};

// execute 方法
query.prototype.execute = function () {
  while(this.callbcakFnStack.length) {
    const cb = this.callbcakFnStack.shift()
    cb()
  }
  return this.wrapper;
};

query.prototype.pushCallBack = function (cb) {
  this.callbcakFnStack.push(cb);
};

const data = [
  { name: "foo", age: 16, city: "shanghai" },
  { name: "bar", age: 24, city: "hangzhou" },
  { name: "fiz", age: 22, city: "shanghai" },
  { name: "baz", age: 19, city: "hanzghou" },
  { name: "bae", age: 23, city: "hangzhou" },
];

const _ = query(data)
  .where((item) => item.age > 18)
  .orderBy("name")
  .groupBy("city")
  .execute();

console.log(_);