План чтения исходного кода - изучайте один метод lodash в неделю (разница)

внешний интерфейс
План чтения исходного кода - изучайте один метод lodash в неделю (разница)

предисловие

Всегда слушайте, что люди говорят, читая исходный код Vue, React, ...... XXX, но читать исходный код этих фреймворков действительно сложно, и эти превосходные фреймворки обязательно включают множество алгоритмов и структур данных Знания шаблонов дизайна. Студент, который так усердно копал исходный код, что действительно в него вчитывался? При отсутствии фундамента слепое чтение всевозможных источников фреймов только истощает силу воли. Учитель сказал, что ребенок пошел в школу, знания должны быть постепенными, легкими и трудными па после первого теста, когда вопрос. Ну, прочитайте исходный код этой штуки, все об этом. Мы также должны быть простыми в сложных, шаг за шагом. Wall lodash был услышан, но, честно говоря, я не ходил в исходный код lodash, недавно я пошел посмотреть и нашел исходный код lodash, написанный напуганным до небес! Втягивайте в мельчайшие детали, принимая во внимание различные типы границ, производительность наших соображений стоит изучить. Итак, я решил, что лодаш исходников внимательно прочитает, а для написания эссе возьмет несколько кусочков записи процесса обучения.

Никакой ерунды, первый метод lodash, который я прочитал, был метод разницы

анализ разностной функции

Создает массив с уникальными значениями массива, каждое из которых не содержится в других заданных массивах. (Примечание: это создает новый массив со значениями в этом массиве, исключая значения в данном массиве для первого числа (параметр массива).) Этот метод использует SameValueZero для сравнения на равенство. Порядок результирующих значений определяется порядком в первом массиве.

исходный код

function difference(array, ...values) {
  return isArrayLikeObject(array)
    ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
    : []
}

Код lodash очень самокомментарен, и мы можем примерно понять замысел автора, основываясь на названии его функции.differenceСама функция означает:

Массив похож на массив?
Если это массив, разверните значение ... value и сравните baseDifference с массивом.
Если это не массив, подобный массиву, верните пустой массив напрямую.

Исходный код lodash очень лаконичен, а методы, предоставляемые lodash, несложны.isArrayLike, baseDifference, baseFlattenЭтот тип базовой функции состоит из того, что с одной стороны видно, что внутренняя часть lodash также имеет высокий уровень повторного использования и абстракции. Далее мы его разденем.

зачистки коконов

isArrayLikeObject

Буквально означает «будь то类数组объект"

Подобный массиву: подобный массиву на самом деле не массив, а объект, подобный массиву. Объект, подобный массиву, должен соответствовать следующим двум пунктам:

  1. Используйте числа в качестве имен свойств
  2. Требуется атрибут длины
function isArrayLikeObject(value) {
  return isObjectLike(value) && isArrayLike(value)
}

Функция также проста, если значение является объектом и类数组тогда значение类数组объект. Давайте просто посмотримisObjectLikeа такжеisArrayLikeисходный код

function isObjectLike(value) {
  return typeof value === 'object' && value !== null
}

function isArrayLike(value) {
  return value != null && typeof value !== 'function' && isLength(value.length)
}

isObjectLikeЭто связано с древней ошибкой в ​​​​JavaScript,typeof nullценностьobject(Почему значение typeof(null) в JavaScript является «объектом»?)

isArrayLikeЗначение удаляется изfunctionситуации следует отметить, чтоisLengthметод.

const MAX_SAFE_INTEGER = 9007199254740991
function isLength(value) {
  return typeof value === 'number' &&
    value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER
}

isLengthЗдесь ограничено, что значение должно быть целым числом и меньше наибольшего безопасного целого числа. В это время мое внимание успешно привлекло магическое число 9007199254740991. Откуда взялось это число? 9007199254740991 ровно 2**53 - 1. Почему оно равно этому числу? Проще говоря, это потому, что стандарт IEEE754 используется для унифицированного представления чисел с плавающей запятой и целых чисел в js. js не полностью использует все биты 64-битных чисел с плавающей запятой для представления целых чисел.

Для получения подробной информации вы можете обратиться к:Откуда берутся MAX_VALUE и MAX_SAFE_INTEGER в JS

Хорошо, поехалиisArrayLikeObjectИсходный код lodash был почти проанализирован, и простое чтение этого небольшого кода должно заставить людей вздохнуть, что lodash действительно полон деталей.

baseFlatten

Буквально это функция, которая расширяет многомерный объект/массив.

function baseFlatten(array, depth, predicate, isStrict, result) {
  predicate || (predicate = isFlattenable)
  result || (result = [])

  if (array == null) {
    return result
  }

  for (const value of array) {
    if (depth > 0 && predicate(value)) {
      if (depth > 1) {
      	// 有栈溢出的风险
        // Recursively flatten arrays (susceptible to call stack limits).
        baseFlatten(value, depth - 1, predicate, isStrict, result)
      } else {
        result.push(...value)
      }
    } else if (!isStrict) {
      result[result.length] = value
    }
  }
  return result
}

Сначала объясните, что означают параметры функции.

  • array: расширяемый массив
  • глубина: Сколько слоев вам нужно для расширения?
  • предикат: функция, которая "проверяет" каждый элемент в массиве
  • isStrict: строго ли требуется, чтобы каждый элемент проходил проверку функции предиката
  • результат: массив, в который нужно поместить развернутый результат

Вышеприведенный код просто обходит массив.Если уровень расширения больше 1, выполняется рекурсия, в противном случае он расширяется напрямую с помощью оператора расширения массива .... Также следует отметить, что если в «строгом режиме», значения, не прошедшие проверку функции предиката, не будут помещены в массив результатов.

baseDifference

Наконец, давайте проанализируемbaseDifferenceэта функция

import SetCache from './SetCache.js'
import arrayIncludes from './arrayIncludes.js'
import arrayIncludesWith from './arrayIncludesWith.js'
import map from '../map.js'
import cacheHas from './cacheHas.js'
const LARGE_ARRAY_SIZE = 200

function baseDifference(array, values, iteratee, comparator) {

  // includes 会根据不同的使用情况而发生改变
  let includes = arrayIncludes
  let isCommon = true
  const result = []
  const valuesLength = values.length

  if (!array.length) {
    return result
  }
  if (iteratee) {
    values = map(values, (value) => iteratee(value))
  }
  // 如果有comparator则变为arrayIncludesWith
  if (comparator) {
    includes = arrayIncludesWith
    isCommon = false
  }
  else if (values.length >= LARGE_ARRAY_SIZE) {
    includes = cacheHas
    isCommon = false
    values = new SetCache(values)
  }
  // for循环以上的部分是针对不同的情况而对其中使用的一些函数进行调整和改变一些标识符
  
  outer:
  for (let value of array) {
    const computed = iteratee == null ? value : iteratee(value)

    value = (comparator || value !== 0) ? value : 0
    if (isCommon && computed === computed) {
      let valuesIndex = valuesLength
      while (valuesIndex--) {
        if (values[valuesIndex] === computed) {
          continue outer
        }
      }
      result.push(value)
    }
    else if (!includes(values, computed, comparator)) {
      result.push(value)
    }
  }
  return result
}

Давайте сначала объясним список параметров функции:

  • массив: массив для проверки
  • значения: массив для сравнения с массивом
  • iteratee: функция iteratee вызывается для каждого элемента при переборе массива
  • компаратор: функция, используемая для определения равенства двух значений

Давайте сначала прочитаем логику, а потом углубимся в детали.

Вышеупомянутая часть цикла for предназначена для настройки некоторых используемых в нем функций и изменения некоторых идентификаторов для различных ситуаций.
Часть цикла for выполняет итерацию по массиву массивов, а затем использует цикл для сравнения значений в массиве значений, и, если они разные, помещают их в массив результатов.

Общая логика в общем-то относительно проста, то есть двойной обход цикла. Стоит отметить, чтоcomparatorа такжеLARGE_SIZEчасть. когда мы прошли вcomparatorилиvaluesКогда размер массива превышает 200,isCommon === false, на этот раз будут некоторые отличия.

comparator

если мы предоставимcomparator,ноincludesфункция станетarrayIncludesWith

arrayВключает исходный код

function arrayIncludes(array, value) {
  const length = array == null ? 0 : array.length
  return !!length && baseIndexOf(array, value, 0) > -1
}

function baseIndexOf(array, value, fromIndex) {
  // NaN !== NaN
  return value === value
    ? strictIndexOf(array, value, fromIndex)
    : baseFindIndex(array, baseIsNaN, fromIndex)
}

function strictIndexOf(array, value, fromIndex) {
  let index = fromIndex - 1
  const { length } = array

  while (++index < length) {
    if (array[index] === value) {
      return index
    }
  }
  return -1
}

function baseFindIndex(array, predicate, fromIndex, fromRight) {
  const { length } = array
  let index = fromIndex + (fromRight ? 1 : -1)

  while ((fromRight ? index-- : ++index < length)) {
    if (predicate(array[index], index, array)) {
      return index
    }
  }
  return -1
}


function baseIsNaN(value) {
  return value !== value
}
код arrayIncludesWithSource
function arrayIncludesWith(array, target, comparator) {
  if (array == null) {
    return false
  }

  for (const value of array) {
    if (comparator(target, value)) {
      return true
    }
  }
  return false
}

arrayIncludesWithа такжеarrayIncludesФункция та же самая, обе для определения того, появляется ли значение в массиве, ноarrayIncludesWithНам нужно предоставить один из наших собственныхcomparatorФункция используется для сравнения двух значений на равенство.
предоставлено в ЛодашеarrayIncludesМожно сказать, что функция снова полна деталей, полностью учитывая граничные условия, такие как пустые массивы и значения NaN.

LARGE_SIZE

И когда мыvaluesРазмер массива превышаетLARGE_SIZEназад,includesстанетcacheHas, valuesМассив также станетSetHashобъект. Теперь давайте посмотримcacheHasа такжеSetHashЧто не так с объектом.

cacheHas

function cacheHas(cache, key) {
  return cache.has(key)
}

Его исходный код очень прост,cacheЭтот объект имеет метод has, который возвращает, существует ли этот объект.keyстоимость.

SetCache

Исходный код SetCache относительно больше, вот еще одна статья, посвященная объекту SetCache. Здесь вы можете понять это, как предусмотрено в ES6Setобъект.

Это так много сказало, почему, когдаvaluesИспользуйте, когда он большеSetА как насчет такого объекта? Давайте рассмотрим, как мы можем сказать, что в двух массивах есть разные значения? Мы используем двойной цикл, а временная сложность — O(n^2). а такжеSetВременная сложность запроса данных для этого типа объекта составляет O (1), а с самым внешним циклом наша временная сложность становится O (n).

Если наш массив небольшой, создайтеSetCacheНакладные расходы объекта могут быть больше, чем ухудшение производительности, вызванное двойным циклом, но если массив относительно велик, проблема производительности, вызванная двойным циклом, намного больше, чем проблема производительности, которую мы создаем.SetCacheрасходы.

Суммировать

До сих пор весьdifferenceЧтение исходного кода метода в основном подошло к концу. Давайте подытожим некоторые из основных моментов и деталей, которые мы узнали:

  1. typeof null === 'object'Это ошибка в дизайне JS. (233333333)
  2. NaN !== NaNЭтот момент необходимо отметить, это также необходимо для определения того, является ли значениеNaNМетоды.
  3. JS использует стандарт IEEE-754 для унифицированного представления целых чисел и чисел с плавающей запятой, поэтому самое большое безопасное число в JS — 2^53 — 1.
  4. В случае больших массивов запрос массива с использованием хеш-таблицы выполняется быстрее, чем двойной цикл, поскольку временная сложность запроса хеш-таблицы составляет O (1).

Если вы считаете, что статья полезна для вас, вы можете поставить лайк и перейти~