Интервьюер: Почему ты такой сильный и смеешь все уменьшать?

JavaScript

Что касается обхода, если у вас есть проходимая структура, вы можете использовать сокращение для ее решения, будь то массив, строка, объект, набор, карта

1. Используйте сокращение для реализации некоторых API для массивов

Добавьте API, основанный на уменьшении, к прототипу массива:

Object.assign(Array.prototype, {
  myMap(cb, _this = this) {
    return this.reduce((res, cur, index, array) => [...res, cb.call(_this, cur, index, array)], []);
  },
  myFind(cb, _this = this) {
    return this.reduce((res, cur, index, array) => res || (cb.call(_this, cur, index, array) ? cur : undefined), undefined)
  },
  myFilter(cb, _this = this) {
    return this.reduce((res, cur, index, array) => [...res, ...(cb.call(_this, cur, index, array) ? [cur] : [])], []);
  },
  myEvery(cb, _this = this) {
    return this.reduce((res, cur, index, array) => res && !!cb.call(_this, cur, index, array), true);
  },
  mySome(cb, _this = this) {
    return this.reduce((res, cur, index, array) => res || !!cb.call(_this, cur, index, array), false);
  },
});

Далее напишите тестовый пример:

// 函数用例
const tests = {
  map: [
    item => item * 2,
    function(_, index) { return this[index] } // 这this是专门测cb传入第二个参数使用的
  ],
  find: [
    item => item,
    item => item === 6,
    item => item === Symbol(),
    function(_, index) { return this[index] === 6 }
  ],
  filter: [
    item => item > 6,
    item => item,
    function(_, index) { return this[index] > 6 }
  ],
  every: [
    item => item,
    item => item > 6,
    function(_, index) { return this[index] > 6 }
  ],
  some: [
    item => item,
    item => item > 6,
    function(_, index) { return this[index] > 6 }
  ],
}

// 数据源
const example = [
  [1,2,3,4,5,6,7],
  [1,2,3,4,5],
  [11,12,13,14,15],
];

Тестовый пример рассматривает обычный случай и второй случай изменения параметра этого, и, наконец, требует метод для выполнения тестового примера:

// 简单的比较相等
function isEqual(a, b) {
  if (typeof a !== 'object' && typeof b !== 'object') {
    return a === b
  }
  // 这是测试[1, 2, 3]和[1, 2, 3]用的
  // 本文只有number和number[]没有其他数据结构
  return `${a}` === `${b}`;
}

function doTest(example, tests) {
  // 以数据源为key,数组的isEqual是通过隐式转换比较
  return example.reduce((res, cur) => {
  // 对函数用例逐个执行,把有没有相等的true和false写进去
    res[cur] = Object.entries(tests).reduce((result, [key, fns]) => {
      result[key] = fns.map(fn =>
        example.map(eg =>
          isEqual(
            eg[key](fn, [5, 6, 7]),
            eg[`my${key[0].toUpperCase()}${key.slice(1)}`](fn, [5, 6, 7])
            )
        ));
      return result;
    }, {});
    return res;
  }, {});
}

doTest(example, tests)
// 如果全部都是true,说明测试通过

2. Как уменьшить, если это не массив

В приведенном выше тесте также используется сокращение, то есть уменьшение объекта. Пока он проходит через структуру данных и выдает результат, его можно решить с помощью сокращения:

  • Обычные объекты: используйте Object.keys, Object.values, Object.entries, а затем уменьшите
  • Массивоподобные объекты: используйте [...o]
  • Строка: [].reduce.call(строка, (res, cur) => {}, результат)
  • Поддельный массив: например, {0: 'a', 1: 'b', длина: 2}, используйте Array.from(o), Array.apply(null, o)
  • Объекты с символами в качестве ключей: используйте getOwnPropertySymbols

Давайте начнем с нескольких простых примеров.Я надеюсь, что люди, которые в основном не используют сокращение в обычное время, смогут понять смысл сокращения на нескольких примерах. сокращение может упростить код и сделать мышление более ясным, вместо того, чтобы сбиваться с толку индексом цикла for

Создайте простую схему из объекта:

// value值变成对应的type,如果是对象,则递归下一级
function transformSchema(o) {
  return Object.entries(o).reduce((res, [key, value]) => {
    res[key] = typeof value !== 'object' ? typeof value : transformSchema(value);
    return res;
  }, Array.isArray(o) ? [] : {});
}

transformSchema({ a: 1, b: '2', c: { d: 1, e: [{a: 1, b:2}]} })

Подсчитайте количество тегов на странице

[...document.querySelectorAll('*')]
  .reduce((sum, node) => node.nodeName === 'A' ? sum : sum + 1, 0)

Подсчитайте количество вхождений каждого символа в строку:

;[].reduce.call('asfsdhvui3u2498rfrvh 93c  293ur0jvdf', (res, cur) => {
  res[cur] = res[cur] || 0;
  res[cur] ++;
  return res;
}, {})

Сглаживание массивов (без сглаживания и соединения)

function flattern(arr) {
  return arr.reduce((res, cur) => 
    res.concat(Array.isArray(cur) ? flattern(cur) : [cur]),
  []);
}

Дедупликация массива, совместимая с различными типами, более совершенная версия:

function isNotSimple(o) {
  return Object.prototype.toString.call(o) === '[object Object]' || Array.isArray(o) || typeof o === 'function'
}

function deepEqual(a = {}, b = {}, cache = new Set()) {
  if (typeof a === 'function') { // 函数的情况
    return a.toString() === b.toString()
  }
  if (cache.has(a)) { // 解决环引用
    return a === b
  }
  cache.add(a)
  const keys = Object.keys(a)
  const symbolKeys = Object.getOwnPropertySymbols(a) // 考虑symbol做key
  return (keys.length === Object.keys(b).length &&
    symbolKeys.length === Object.getOwnPropertySymbols(b).length) &&
    [...keys, ...symbolKeys].every(key => !isNotSimple(a[key]) ?
      a[key] === b[key] : deepEqual(a[key], b[key], cache))
}

function unique(arr) {
  const cache = new Set() // set可以干掉NaN
  const objCache = []
  // 简单的基本类型直接来,复杂的使用deepEqual
  return arr.reduce((res, cur) => (
    !isNotSimple(cur) ? !cache.has(cur) && res.push(cur) && cache.add(cur)
      : !objCache.find(o => deepEqual(o, cur)) && objCache.push(cur) && res.push(cur),
    res
  ), []);
}

Сгенерируйте односвязный список всех входящих параметров:

function createLinkList(...init) {
  let current
  return init.reduce((res, cur) => {
    current = current || res
    current.value = cur
    current.next = current.next || {}
    current = current.next
    return res
  }, {})
}
createLinkList(1,2,4,5,6);

Создайте древовидную структуру:

const ran = () => ~~(Math.random() * 2) + 1
function createTree(dept = 0) {
  if (dept > 1) {
    return null;
  }
  // 如果每一层是数组型的树结构,用map也可以
  // reduce还可以兼容非数组的结构,还可以完成其他更复杂的需求
  return Array.apply(null, { length: ran() }).reduce((res, cur, i) => {
    res[i] = {
      value: ran(),
      nodes: createTree(dept + 1),
    }
    return res;
  }, {});
}
const tree = createTree();

Основываясь на приведенной выше древовидной структуре, найдите количество вхождений определенного значения узла:

// 如果当前节点值等于target,则+1;如果有子节点,则带上sum递归计算
function targetFromTree(tree = {}, target, sum = 0) {
  return Object.values(tree).reduce((res, node) => 
    res + ~~(node.value === target) + targetFromTree(node.nodes, target, sum)
  , sum);
}

3. Составьте идею

Для API-интерфейсов массивов часто используются связанные операции, например:

[1,2,3,4,5].filter(x => x > 3).map(x => x * 2)

Таким образом, фильтруйте каждый элемент и проходите его один раз. Для каждой карты элементов повторите еще раз. На самом деле, мы можем сделать все это, пройдя один раз, чтобы выполнить две операции.При обходе мы можем выполнить общую функциональную операцию, которая объединяет все функции для каждого элемента.

class MagicArray extends Array {
  temp = []; // 存放链式操作的方法

  FLAG = Symbol(); // filter标记

  // 如果有filter标记则直接返回
  myMap(cb, _this = this) {
    this.temp.push((cur, index, array) => cur === this.FLAG ? this.FLAG : cb.call(_this, cur, index, array));
    return this;
  }

  // 不符合要求的打上filter标记
  myFilter(cb, _this = this) {
    this.temp.push((cur, index, array) => cb.call(_this, cur, index, array) ? cur : this.FLAG);
    return this;
  }

  run() {
    // 函数compose
    const f = this.temp.reduceRight((a, b) => (cur, ...rest) => a(b(cur, ...rest), ...rest));
    const result = this.reduce((res, cur, index, arr) => {
      const ret = f(cur, index, arr);
      // filter标记的元素直接跳过
      if (ret === this.FLAG) {
        return res;
      }
      res.push(ret);
      return res;
    }, []);
    this.temp = [];
    return result;
  }
}

Мы завершили массив магией, давайте проверим, что быстрее, чем нативная операция:

const a = new MagicArray(...Array.apply(null, { length: 10000 }).map(x => Math.random() * 10));
console.time('normal')
a.map(x => x * 2).filter(x => x > 5)
console.timeEnd('normal')

console.time('compose')
a.myMap(x => x * 2).myFilter(x => x > 5).run()
console.timeEnd('compose')

После многих тестов соотношение времени составного массива к обычному массиву составляет около 3: 5.

дляthis.temp.reduceRight((a, b) => (cur, ...rest) => a(b(cur, ...rest), ...rest));Как понять этот код?

Подобно реализации промежуточного программного обеспечения различных фреймворков, наша реализация здесь заключается в том, что входящие параметры согласуются с элементом, индексом и массивом массива, но наш элемент здесь является результатом предыдущей операции, поэтому естьb(cur, ...rest), ...rest)операция

Короче говоря, в случае обхода структуры данных и, наконец, генерации одного или нескольких результатов (несколько результатов res представлены несколькими атрибутами объекта), тогдадиск с уменьшениемВот и все

【Ad】Генератор аннотаций типа TS

Воспользовавшись сокращением несколько раз, вы обнаружите, что оно обеспечивает лучший опыт разработки и повышает эффективность, а также больше используется для создания колес. Недавно я написал небольшой инструмент, который преобразует известные структуры json в объявления ts. В исходном коде вы можете почувствовать, что после использования сокращения логика рекурсии и обхода становится очень ясной.

// 已知json
{
  "a": 1,
  "b": "1",
  "c": {
    "d": 1,
    "e": [
      "1",
      {
        "g": 1,
        "r": "asd",
        "gg": true
      },
      1
    ]
  }
}
// 转换结果
{
  a: number;
  b: string;
  c: {
    d: number;
    e: number | {
      g: number;
      r: string;
      gg: boolean;
    } | string [];
  };
}

адрес проекта

ts-declaration-gen: адрес пакета npm

Обратите внимание на официальный аккаунт «Другой интерфейс», изучите интерфейс с другой точки зрения, быстро растем, играйте в новейшие технологии и исследуйте различные черные технологии вместе.