Функция JS для инициализации свойств

У меня есть код JavaScript для управления 2D-картой в игре. У меня есть функция для создания новой комнаты на карте, которая находится в map[x][y]. Мне нужно создать это свойство, а также инициализировать monsters свойство. Написанный мной код выглядит так, и он отлично работает.

'use strict';

let map = {};

function newRoom(x, y) {
    if (map[x] === undefined)
        map[x] = {};
    map[x][y] = {};
    map[x][y].monsters = {};
}

newRoom(0, 0);

Есть ли более лаконичный способ написать такой код?

3 ответа
3

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

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

Существуют библиотеки, которые выполняют вложенное создание за вас, но в конечном итоге они реализуют ту же логику под капотом:

// Just for demonstration, I do not recommend using a library for this:

let map = {};
function newRoom(x, y) {
  _.setWith(map, [x, y, 'monsters'], {}, Object);
}
newRoom(0, 0);
console.log(map);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>

Один ярлык, который вы можете сделать в исходном коде, — это просто проверить map[x] вместо сравнения с undefined. Вы также можете объявить monsters объект встроен при назначении комнаты. Вы также должны объявить объект карты с помощью const если вы не переназначаете его позже.

Теперь, когда логическое нулевое присвоение существует, вы также можете использовать его, хотя, учитывая его новизну, вам (или другим, кто может читать код) он может не понравиться, и он может выглядеть немного запутанным независимо от того:

const map = {};

function newRoom(x, y) {
    map[x] ??= {};
    map[x][y] = {
        monsters: {}
    };
}

/*
or
function newRoom(x, y) {
    if (!map[x]) {
      map[x] = {};
    }
    map[x][y] = {
        monsters: {}
    };
}
*/

newRoom(0, 0);
console.log(map);

Я рекомендую использовать скобки для вашего if блоки. Они упростят чтение и уберегут вас от случайного написания неправильного уровня отступа, если предположить, что что-то находится внутри if блок, когда на самом деле это не так.

Обратите внимание, что и приведенный выше код, и ваш исходный код перезапишут содержимое комнаты, если оно уже существует в map. Надеюсь, это желательно.

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

const map = {};

function newRoom(m,n){map[m]||(map[m]={}),map[m][n]={monsters:{}}}

newRoom(0, 0);
console.log(map);

Другое дело — по состоянию на ES2015, Maps больше подходят для динамических свойств, чем объекты. Если вам нужна карта, используйте карту вместо объекта.

function newRoom(x, y) {
    if (!map.has(x)) {
        map.set(x, new Map());
    }
    map.get(x).set('y', { monsters: {} });
}

  • 1

    $ begingroup $
    Хороший ответ. Я просто хотел добавить тот факт, что, хотя могут быть способы достичь того, о чем просил OP, ничто из вышеперечисленного не так читаемо и легче для понимания, как OP
    $ endgroup $
    — Грайдяну Алекс

  • $ begingroup $
    Даже не or раздел? Я не продана ??= либо, но я думаю, используя только правдивый тест для if, с помощью const вместо let, и объявив monsters подобъект встроен в все улучшения по сравнению с оригиналом?
    $ endgroup $
    — CertainPerformance

  • $ begingroup $
    Не поймите меня неправильно, все они определенно улучшения! Я строго говорил о читабельности кода: p
    $ endgroup $
    — Грайдяну Алекс

TL; DR

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


Несколько мелких моментов …

Для начинающих:

'use strict';

Хорошее начало!


let map = {};

Использовать const. С помощью let говорит: «Я собираюсь переназначить это значение позже», чего вы почти никогда не хотите делать, кроме счетчиков циклов.


    if (map[x] === undefined)
        map[x] = {};

Всегда используйте блочные скобки {} на условиях.

Если твой «контракт» позади map в том, что у него всегда только два значения, undefined и {} тогда if (map[x]) кажется мне наиболее понятным и устойчивым к изменениям, таким как замена undefined из за null. Это второстепенный момент — === undefined неплохо.


Уважайте область видимости и учитывайте инкапсуляцию

newRoom имеет серьезную проблему: он нарушает область видимости и полагается на жестко запрограммированный глобальный, map. Его нельзя повторно использовать с произвольными картами. Если вы измените имя глобального map к bigMap или что-то в этом роде, функция ломается без уважительной причины. В лучшем случае звонок newRoom не имеет очевидной связи с map.

Имея это в виду, лучший заголовок функции — newRoom(map, x, y). Это явно сообщает, что map инициализируется, работает на любом map-type, который мы передаем, и он не сломается, если вы сделаете что-то простое, например, измените имя переменной в другом месте вашего приложения.

Теперь, даже если вы пройдете map в качестве параметра функция все еще остается нечистой, потому что она мутирует объект. Это похоже на ООП в стиле C с объектом, переданным в качестве первого аргумента функции. Если вам нужно многократно изменять свойство объекта, например, это, или иметь много функций, например foo(map, ...), bar(map, ...), baz(map, ...), рассмотрите возможность присоединения к нему функции-прототипа (или использования class синтаксический сахар), поэтому вы можете использовать map.newRoom(x, y). Преимущество в том, что функция newRoom «принадлежит» к map объект (или его прототип / класс), а не уходить в глобальную область видимости без очевидной связи ни с чем. когда map.newRoom(x, y) изменяет данные о this, легко понять, что происходит, потому что состояние инкапсулировано в экземпляр.

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

Выполнение этого вызова дизайна зависит от контекста, который здесь недоступен, и я не призываю сразу перейти к ООП или 100% чистому программированию без хорошей мотивации для этого.

Важно получить map в newRoomобъем.


Больше дизайнерских мыслей

По теме, почему нет map предварительно выделены пустыми объектами для комнат? Проверка undefined кажется мне запахом кода — если остальная часть кодовой базы имеет функции, которые постоянно проверяют undefined прежде чем принимать меры map, вероятно, есть лучший способ спроектировать это, учитывая более подробный контекст приложения.

Немного странно, что функция должна действовать как операция объединения для инициализации и повторной инициализации комнаты. Кажется перегруженным. Может быть лучше иметь newRoom функция, которая обрабатывает установку map[x] = {}; и initializeMonstersInRoom функция, которая устанавливает map[x][y] = {monsters: {}}. Это все еще довольно необычно, но, по крайней мере, нет условия, и вызывающий может использовать newRoom и initializeMonstersInRoom в отдельных точках приложения, а не в основном с помощью ветки в newRoom разобраться, что делать. Если вы вызываете эту функцию в цикле, который инициализирует комнаты для многих x и y значений, может быть более чистый способ упорядочить, который решает проблему объединения.


Предпочитайте массивы для числовых последовательных индексов

Меня немного удивляет то, что map представляет собой вложенный объект со строковыми целочисленными ключами и поиском по хешу. Единственная причина, по которой я это сделаю, — если x/y координаты редкие или пронумерованы непоследовательно.

Я также предпочитаю использовать порядок строк: map[y][x] скорее, чем map[x][y]. Стандартный подход — перебирать строки, а затем столбцы. Обычно так распределяется память, и компиляторы часто перестраивают доступ к матрице со старшим столбцом в строковый, чтобы улучшить локальность. Я не уверен, делают ли JS JIT-компиляторы это или нет, но это только вишенка на торте следующих стандартов. Есть некоторые заметные исключения, такие как FORTRAN и R, которые используют основной столбец, но эти языки также используют 1-индексацию, поэтому ни один из них не имеет большого доверия в качестве справочника для написания современного кода на таком языке, как JS.


Предложение переписать оригинал за чистую монету

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

Сказав это, я бы написал это как

'use strict';

const newRoom = (map, x, y) => {
  map[x] = map[x] || {};
  map[x][y] = {monsters: {}};
};

const map = {};
newRoom(map, 0, 0);
console.log(map);

Или, с массивами, если в ваших комнатах используются последовательные целые числа:

'use strict';

const newRoom = (map, x, y) => {
  map[x] = map[x] || [];
  map[x][y] = {monsters: {}};
};

const map = [];
newRoom(map, 0, 0);
console.log(map);

    Намерение

    Чтение только кода

    Код дает подсказки для более высокого уровня использования, но неясно, что это могло быть. Я воспользуюсь намерением заполнить пробелы

    Хороший код дает больше, чем просто понимание логики, но передает намерение.

    Самое сильное руководство по намерениям — это именование, я считаю, что это более верно для менее опытных программистов.

    Выведение намерения

    От наименования map, x, y, monsters, newRoom

    • map намекает на пространственный макет, хотя также распространено (и плохая практика) использовать его для представления хранилища ключей / значений. В этом случае я думаю, что вы строите географическую карту.

    • x, y Это дополнительно гарантирует, что ваше намерение — это географические координаты на map

    • «монстры». Это термин, который редко используют вне игры. Это решающий аргумент, который убеждает меня в том, что up создает некую карту уровней. Добавление комнаты в координатах x, y, которая содержит monsters. В виде monsters во множественном числе предполагает множество монстров

    • newRoom это имя не соответствует логике кода. Код условно создает столбец на карте, затем создает комнату, непосредственно добавляется на карту, а затем добавляет monsters Я подозреваю, что ваше намерение больше соответствует имени newRoom Что вы вынуждены добавить столбец из-за того, что карта неполная.

    Исходя из предположения, которое я только что сделал

    Используйте 2D-массив

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

    Я также предлагаю вам поменять местами координаты, координаты адреса соглашения, x, затем y, поэтому лучше поддерживать это, когда вы когда-либо получаете доступ к координате. Это избавит вас от головной боли.

    Для заполнения массива потребуется некоторая информация о максимальном размере карты.

    Следующий пример — это один из методов создания 2D-массива, заполненного undefined чтобы обеспечить максимально быструю индексацию в массиве.

    2D-массив — это массив столбцов.

    const WIDTH = 10;
    const HEIGHT = 10;
    const map = new Array(WIDTH).fill().map(()=>new Array(HEIGHT).fill());
    

    Создать комнату

    Комната не зависит от местоположения на карте. Я предлагаю вам создать комнату, а затем добавить ее на карту

    В monsters во множественном числе также будет лучше в виде массива, если вы не дадите каждому монстру уникальное имя. Но я думаю, вы не знакомы с массивами и назвали бы монстров 1, 2, 3

    Таким образом, создание комнаты было бы

    const createRoom = (x, y) => ({monsters: [], x, y});
    

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

    Добавление комнаты на карту

    Поскольку комната теперь создается самостоятельно, newRoomToMap функция может получить лучшее название. addRoomToMap это, как следует из названия, добавляет комнату на карту.

    Поскольку комната хранит свои координаты, вам нужно только передать комнату функции.

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

    // with map already defined as 2D array
    const addRoomToMap = (room, map) => { map[room.x][room.y] = room }
    

    Собираем все вместе.

    Таким образом, со всеми допущениями, которые вы кодируете, несколько преобразуются

    "use strict";
    const WIDTH = 10, HEIGHT = 10;
    const map = new Array(WIDTH).fill().map(()=>new Array(HEIGHT).fill());
    
    const createRoom = (x, y) => ({monsters: [], x, y});
    const addRoomToMap = (room, map) => { map[room.x][room.y] = room }
    
    addRoomToMap(createRoom(5, 0), map); 
    

    Предположения.

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

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

    Массивы

    Если вы не знакомы с массивами Массив MDN поможет вам начать

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *