Интервьюер: Что такого особенного в массивах JavaScript?

JavaScript

Массивы являются наиболее часто используемыми структурами данных для фронтенд-разработчиков. Мы постоянно работаем с массивами в наших проектах. Например, мы храним данные компонентов списка в массивах и данные, которые необходимо отобразить в гистограммы. Хотя мы часто используют массивы, многие люди не понимают сути массивов JavaScript.

В этом разделе мы объясним использование массивов JavaScript и модели памяти.Мы надеемся, что благодаря этому разделу вы получите более глубокое понимание массивов JavaScript.

Прежде чем начать этот раздел, подумайте над вопросом, что особенного в массивах в JavaScript?

использование массивов

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

Создание и инициализация массива

Если вы ранее изучали другие языки, такие как c++/java и т. д., вы можете создать и инициализировать массив с помощью:

const appleMac = new Array('Mac Book Air', 'iMac', 'Mac Book Pro', 'Mac pro')

Конечно, это возможно в JavaScript, но это не общепринятый способ, обычно люди создают и инициализируют массивы буквально:

const appleMac = ['Mac Book Air', 'iMac', 'Mac Book Pro', 'Mac pro']

В es6 были введены два новых метода, которые также создают массивы:

  • Array.of() возвращает массив всех аргументов, независимо от количества или типов аргументов, или пустой массив, если аргументов нет.
  • Array.from() создает новый массив из массивоподобного или итерируемого объекта.

Эти два метода решают две проблемы соответственно,Array.of()Исправлено странное поведение, вызванное одним числом, когда метод конструктора создает массив.

const a = new Array(3);   // (3) [empty × 3] 构造函数方法单个数组会被用于数组长度
const b = Array.of(3);    // [3]

Array.from()Решена проблема преобразования типа «массив», раньше мы обычно использовали метод преобразования типа массива в массив.Array.prototype.slice.call(arguments)Этот метод частичного взлома,Array.from()по-видимому, нормализует его, и в будущих преобразованиях лучше следовать стандартуArray.from()метод преобразования.

операции с массивами

Существуют десятки операций с массивами, и мы не можем говорить о них по отдельности. Вы также можете увидеть MDN для конкретного использования. Мы говорим только о двух API, которые важны для этого раздела.

вставить элемент в голову

Операция unshift является наиболее распространенной операцией добавления элементов в начало массива.

const arr = [1, 2, 3]

arr.unshift(0) // arr = [0, 1, 2, 3,]

вставить элемент в конец

Операция push — наиболее распространенная операция добавления элемента в конец массива.

const arr = [1, 2, 3]

arr.push(4) // arr = [1, 2, 3, 4]

модель памяти

Память языка программирования обычно проходит три этапа.

  1. Выделить память
  2. Чтение и запись памяти
  3. свободная память (сборка мусора)

Создание массива соответствует первому этапу, а работа с массивом — второму этапу.

Итак, теперь возникает вопрос, мы используем push и unshift для добавления элементов в хвост и голову массива соответственно, что быстрее?

непрерывная память

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

2019-06-18-10-21-29

Затем, когда мы добавляем к массиву в концеpushКогда элемент равен 6, вам нужно только выделить следующую часть памяти для 6.

Unshift отличается тем, что добавляет элементы в начало массива.Чтобы обеспечить непрерывность массива, элементы после головы необходимо последовательно перемещать назад.

Суть unshift аналогична следующему коду:

for (var i=numbers.length; i>=0; i--){
      numbers[i] = numbers[i-1];
    }
    numbers[0] = -1;

2019-06-18-10-30-59

Поскольку unshift начинает перемещать все элементы обратно в память, производительность намного хуже, чем push.

Я провел эксперимент под версией node10.x:

function unshiftFn() {
    const a = []

    console.time('unshift')
    for (var i=0;i<100000;i++) {
        a.unshift(1);
    }

    console.timeEnd('unshift')
}

function pushFn() {
    const a = []

    console.time('push')
    for (var i=0;i<100000;i++) {
        a.push(1);
    }

    console.timeEnd('push')
}

unshiftFn() // unshift: 2297.383ms
pushFn() // push: 3.760ms

Мы видим, что разница в скорости между ними очень велика, и если вы продолжите корректировать количество циклов for, вы обнаружите, что чем больше количество раз, тем медленнее операция unshift, потому что больше элементов нужно сдвинуть. назад.

Эта разница вызвана тем, что массив хранится как непрерывный участок памяти, что приводит к плохой производительности «вставки» и «удаления» массива, потому что, как только мы удаляем или вставляем элемент, другие элементы для того, чтобы поддерживать блок Непрерывная память должна генерировать большое смещение элементов, что снижает производительность.

несмежная память

Мы начали с вопроса: что такого особенного в массивах JavaScript?

Конечно же, мы будем говорить о многих особенностях JavaScript, о том, что поддерживает создание литеральных объявлений, поддерживает хранение разных типов данных, динамизм и так далее.

По сути, особенность массивов JavaScript заключается в том, что массивы JavaScript не обязательно представляют собой непрерывную память.

И определение массива в Википедии:

В информатике структура данных массива (английский язык: структура данных массива), называемая массивом (английский язык: массив), представляет собой структуру данных, состоящую из набора элементов одного типа, выделяющих непрерывный кусок памяти для хранения .

Если это так, массивы JavaScript не кажутся строго массивами, так почему же в предыдущем подразделе говорится, что массивам выделяется непрерывная память? Разве это не противоречит самому себе?

Выделяет ли массив JavaScript непрерывную память, зависит от типа элементов массива. Если это массив одного типа, он будет выделять непрерывную память. Если массив включает множество различных типов, это несмежная память.

Массивы несмежной памяти существуют аналогично хэш-карте.Например, если массив объявлен, он распределяется по четырем несмежным адресам памяти 1001, 2011, 1088 и 1077, которые связаны указателями. чтобы сформировать линейную структуру. , то когда мы запрашиваем элемент, нам действительно нужно пройти по этой линейной структуре связанного списка, что потребляет много производительности.

2019-06-18-11-08-47

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

a[k]_address = base_address + k * type_size

Давайте проведем простой эксперимент, мы продолжаем вставлять элементы в массив, но две стороны сравнения — это массивы с нелинейным хранением и однородные массивы с линейным хранением:

const total = 1000000

function unshiftContinuity() {
    const arr = new Array(total)
    arr.push({name: 'xiaomuzhu'});
    console.time('unshiftContinuity')
    for(let i=0;i<total; i++){
        arr[i]=i
    }
    console.timeEnd('unshiftContinuity')
}

function unshiftUncontinuity() {
    const arr = new Array(total)
    console.time('unshiftUncontinuity')
    for (let i=0;i<total;i++) {
        arr[i]=i
    }

    console.timeEnd('unshiftUncontinuity')
}

unshiftContinuity() // unshiftContinuity: 71.050ms
unshiftUncontinuity() // unshiftUncontinuity: 1.691ms

Мы видели, что массивы с нелинейным хранением работают намного медленнее, чем массивы с линейным хранением.


Так как автор не читал исходный код движка JavaScript, то это информация не из первых рук, если есть ошибки, просьба указать на них, и я их вовремя исправлю.


Ссылаться на:

How are JavaScript arrays represented in physical memory?


2019-07-12-15-10-54