javascript Map默认值

b5lpy0ml  于 2023-05-27  发布在  Java
关注(0)|答案(5)|浏览(303)

我正在寻找类似默认值的Map。

m = new Map();
//m.setDefVal([]); -- how to write this line???
console.log(m[whatever]);

现在结果是Undefined,但我想得到空的array []。

iyfjxgzm

iyfjxgzm1#

首先来回答关于标准Map的问题:ECMAScript 2015中提出的Javascript Map不包含默认值的setter。但是,这并不妨碍您自己实现该函数。
如果你只想打印一个列表,每当m[whatever]未定义时,你可以只:console.log(m.get('whatever') || []);正如Li357在他的评论中指出的那样。
如果你想重用这个功能,你也可以把它封装到一个函数中,比如:

function getMapValue(map, key) {
    return map.get(key) || [];
}

// And use it like:
const m = new Map();
console.log(getMapValue(m, 'whatever'));

但是,如果这不能满足您的需求,并且您确实想要一个具有默认值的map,您可以为它编写自己的Map类,如下所示:

class MapWithDefault extends Map {
  get(key) {
    if (!this.has(key)) {
      this.set(key, this.default());
    }
    return super.get(key);
  }
  
  constructor(defaultFunction, entries) {
    super(entries);
    this.default = defaultFunction;
  }
}

// And use it like:
const m = new MapWithDefault(() => []);
m.get('whatever').push('you');
m.get('whatever').push('want');
console.log(m.get('whatever')); // ['you', 'want']
y1aodyip

y1aodyip2#

截至2022年,Map.prototype.emplace已达到stage 2
正如提案页面上所说,core-js库中提供了polyfill。

pxyaymoc

pxyaymoc3#

出于我的目的,我认为使用一个DefaultMap类来扩展普通的Map并添加额外的方法会更清楚。这真的很好,因为它导致更多的声明性代码。这意味着当你声明一个新的Map时,你不仅声明了键的类型和值的类型,还声明了默认值。
举个简单的例子:

// Using a primitive as a default value:
const myMap1 = new DefaultMap<string, number>(123);
const myMap1Value = myMap1.getAndSetDefault("some_key"); // Value is 123

// Using a factory function to generate a default value:
const myMap2 = new DefaultMap<string, number, [foo: Foo]>(
  (foo) => foo.bar
);
const foo = new Foo();
const myMap2Value = myMap2.getAndSetDefault("some_key", foo); // Value is foo.bar

代码如下:

import { isFunction, isPrimitive } from "../functions/types";

/**
 * `DefaultMap` is a data structure that makes working with default values easier. It extends a
 * `Map` and adds additional methods.
 *
 * It is a common pattern to look up a value in a `Map`, and then, if the value does not exist, set
 * a default value for the key, and then return the default value. `DefaultMap` abstracts this
 * operation away by providing the `getAndSetDefault` method.
 *
 * Using a `DefaultMap` is nice because it makes code more declarative, since you specify what the
 * default value is alongside the types of the keys/values.
 *
 * When instantiating a new `DefaultMap`, you must specify default value as the first argument. (The
 * default value is the initial value that will be assigned to every new entry in the
 * `getAndSetDefault` method.) For example:
 *
 * ```ts
 * // Initializes a new empty DefaultMap with a default value of "foo".
 * const defaultMapWithString = new DefaultMap<string, string>("foo");
 *
 * const value = defaultMapWithString.getAndSetDefault("bar");
 * // value is now "foo" and an entry for "bar" is now set.
 * ```
 *
 * Sometimes, instead of having a static initial value for every entry in the map, you will want a
 * dynamic initial value that is contingent upon the key or some other variable. In these cases, you
 * can instead specify that the `DefaultMap` should run a function that will return the initial
 * value. (This is referred to as a "factory function".) For example:
 *
 * ```ts
 * // Initializes a new empty DefaultMap with a default value based on "someGlobalVariable".
 * const factoryFunction = () => someGlobalVariable ? 0 : 1;
 * const defaultMapWithFactoryFunction = new DefaultMap<string, string>(factoryFunction);
 * ```
 *
 * Note that in TypeScript and Lua, booleans, numbers, and strings are "passed by value". This means
 * that when the `DefaultMap` creates a new entry, if the default value is one of these 3 types, the
 * values will be copied. On the other hand, arrays and maps and other complex data structures are
 * "passed by reference". This means that when the `DefaultMap` creates a new entry, if the default
 * value is an array, then it would not be copied. Instead, the same shared array would be assigned
 * to every entry. Thus, to solve this problem, any variable that is passed by reference must be
 * created using a factory function to ensure that each copy is unique. For example:
 *
 * ```ts
 * // Initializes a new empty DefaultMap with a default value of a new empty array.
 * const factoryFunction = () => [];
 * const defaultMapWithArray = new DefaultMap<string, string[]>(factoryFunction);
 * ```
 *
 * In the previous two examples, the factory functions did not have any arguments. But you can also
 * specify a factory function that takes one or more arguments:
 *
 * ```ts
 * const factoryFunction = (arg: boolean) => arg ? 0 : 1;
 * const defaultMapWithArg = new DefaultMap<string, string, [arg: boolean]>(factoryFunction);
 * ```
 *
 * Similar to a normal `Map`, you can also include an initializer list in the constructor as the
 * second argument:
 *
 * ```ts
 * // Initializes a DefaultMap with a default value of "foo" and some initial values.
 * const defaultMapWithInitialValues = new DefaultMap<string, string>("foo", [
 *   ["a1", "a2"],
 *   ["b1", "b2"],
 * ], );
 * ```
 *
 * Finally, note that `DefaultMap` has the following additional utility methods:
 *
 * - `getAndSetDefault` - The method that is called inside the overridden `get` method. In most
 *   cases, you can use the overridden `get` method instead of calling this function directly.
 *   However, if a factory function was provided during instantiation, and the factory function has
 *   one or more arguments, then you must call this method instead (and provide the corresponding
 *   arguments).
 * - `getDefaultValue` - Returns the default value to be used for a new key. (If a factory function
 *   was provided during instantiation, this will execute the factory function.)
 * - `getConstructorArg` - Helper method for cloning the map. Returns either the default value or
 *   the reference to the factory function.
 */
export class DefaultMap<Key, Value, Args extends unknown[] = []> extends Map<
  Key,
  Value
> {
  private defaultValue: Value | undefined;
  private defaultValueFactory: FactoryFunction<Value, Args> | undefined;

  /**
   * See the main `DefaultMap` documentation:
   * https://isaacscript.github.io/isaacscript-common/other/classes/DefaultMap
   */
  constructor(
    defaultValueOrFactoryFunction: Value | FactoryFunction<Value, Args>,
    initializerArray?: Iterable<[Key, Value]>,
  ) {
    const argIsPrimitive = isPrimitive(defaultValueOrFactoryFunction);
    const argIsFunction = isFunction(defaultValueOrFactoryFunction);
    if (!argIsPrimitive && !argIsFunction) {
      error(
        `Failed to instantiate a DefaultMap since the provided default value was of type "${typeof defaultValueOrFactoryFunction}". This error usually means that you are trying to use an array (or some other non-primitive data structure that is passed by reference) as the default value. Instead, return the data structure in a factory function, like "() => []". See the DefaultMap documentation for more details.`,
      );
    }

    super(initializerArray);

    if (argIsFunction) {
      this.defaultValue = undefined;
      this.defaultValueFactory = defaultValueOrFactoryFunction;
    } else {
      this.defaultValue = defaultValueOrFactoryFunction;
      this.defaultValueFactory = undefined;
    }
  }

  /**
   * If the key exists, this will return the same thing as the normal `Map.get` method. Otherwise,
   * it will set a default value for the provided key, and then return the default value.
   */
  public getAndSetDefault(key: Key, ...args: Args): Value {
    const value = super.get(key);
    if (value !== undefined) {
      return value;
    }

    const defaultValue = this.getDefaultValue(...args);
    this.set(key, defaultValue);
    return defaultValue;
  }

  /**
   * Returns the default value to be used for a new key. (If a factory function was provided during
   * instantiation, this will execute the factory function.)
   */
  public getDefaultValue(...args: Args): Value {
    if (this.defaultValue !== undefined) {
      return this.defaultValue;
    }

    if (this.defaultValueFactory !== undefined) {
      return this.defaultValueFactory(...args);
    }

    error("A DefaultMap was incorrectly instantiated.");
  }

  /**
   * Helper method for cloning the map. Returns either the default value or a reference to the
   * factory function.
   */
  public getConstructorArg(): Value | FactoryFunction<Value, Args> {
    if (this.defaultValue !== undefined) {
      return this.defaultValue;
    }

    if (this.defaultValueFactory !== undefined) {
      return this.defaultValueFactory;
    }

    error("A DefaultMap was incorrectly instantiated.");
  }
}

// eslint-disable-next-line isaacscript/complete-sentences-jsdoc
/**
 * A function that creates the default value for your `DefaultMap`. For example, if it was a
 * `DefaultMap` containing maps, the factory function would be: `() => new Map()`
 */
export type FactoryFunction<V, Args extends unknown[]> = (...args: Args) => V;

/* eslint-disable @typescript-eslint/no-unused-vars */
function test() {
  // Boolean
  const myDefaultMapBoolean = new DefaultMap<string, boolean>(false);
  const myDefaultMapBooleanFactory = new DefaultMap<string, boolean>(
    () => false,
  );
  const myDefaultMapBooleanWithoutParams = new DefaultMap(false);

  // Number
  const myDefaultMapNumber = new DefaultMap<string, number>(123);
  const myDefaultMapNumberFactory = new DefaultMap<string, number>(() => 123);
  const myDefaultMapNumberWithoutParams = new DefaultMap(123);

  // String
  const myDefaultMapString = new DefaultMap<string, string>("foo");
  const myDefaultMapStringFactory = new DefaultMap<string, string>(() => "foo");
  const myDefaultMapStringWithoutParams = new DefaultMap("foo");

  // Array
  const myDefaultMapArray = new DefaultMap<string, string[]>(() => []);
  const myDefaultMapArrayWithoutParams = new DefaultMap(() => []);

  // Map
  const myDefaultMapMap = new DefaultMap<string, Map<string, string>>(
    () => new Map(),
  );
  const myDefaultMapMapWithoutParams = new DefaultMap(() => new Map());
}
/* eslint-enable @typescript-eslint/no-unused-vars */
shstlldc

shstlldc4#

最简单的实现:

下面是一个简单的MapWithDefault类,它扩展了内置的Map
这里的巧妙技巧是使用symbols来指定默认值,从而产生一个漂亮且可读的语法:
(The下面的代码段使用TypeScript来提供类型安全,但这是可选的,如果你愿意,你可以删除类型注解。
mapWithDefault.ts

export const DEFAULT = Symbol();
export class MapWithDefault<K, V> extends Map<K | typeof DEFAULT, V> {
    get(key: K): V {
        return super.has(key)
            ? super.get(key)
            : super.get(DEFAULT);
    }
}

用途:

import { MapWithDefault, DEFAULT } from './mapWithDefault';

const map = new MapWithDefault<number, string>([
    [404, 'Not found!'],
    [403, 'Forbidden!'],
    [DEFAULT, 'Unknown error'],
]);

console.log(map.get(404)); // "Not found!"
console.log(map.get(123)); // "Unknown error"
wgxvkvu9

wgxvkvu95#

我也有这样的需要,所以我创建了一个Map的小扩展(链接here),也包括在下面。

class MapWithDefaultValues extends Map {
    constructor(...args) {
        super(...args);
        this._defaultGetValue = undefined;
    }

    setDefault(value) {
        this._defaultGetValue = value;
        return this;
    }

    clearDefault() {
        return this.setDefault(undefined);
    }

    get(key, ...rest) {
        if (super.has(key)) {
            return super.get(key);
        }

        let [defaultValue] = rest;
        // Allow for `get(key, undefined)` to override `this._defaultGetValue`
        if (rest.length === 0) {
            defaultValue = this._defaultGetValue;
        }

        return typeof defaultValue === 'function' ? defaultValue(key, this) : defaultValue;
    }
}

使用 function 作为defaultValue允许您计算默认值。

const doubles = new MapWithDefaultValues([[1, 'two'], [2, 'four']]);
doubles.setDefault(key => typeof key === 'number' ? key * 2 : undefined);
doubles.get(1) // 'two'
doubles.get(5) // 10 (computed default)

传递给default的第二个参数还允许您使用整个底层Map,因此您可以在Map上执行类似获取默认值 * 和 * set()的操作。

const caps = new MapWithDefaultValues();
caps.size; // 0
caps.get('a', (key, map) => {
  const result = String(key).toUpperCase();
  map.set(key, result);
  return result;
}); // 'A'
caps.size; // 1

相关问题