Home > Blockchain >  JavaScript string method localeCompare() accepts 'undefined' and refuse 'null' a
JavaScript string method localeCompare() accepts 'undefined' and refuse 'null' a

Time:04-19

I found some approach to sort string in natural way

const rows = ['37-SK', '4-ML', '41-NP', '2-YZ', '21', '26-BF'];
console.log(rows.sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })));

it outputs following:

0: "2-YZ"
1: "4-ML"
2: "21"
3: "26-BF"
4: "37-SK"
5: "41-NP"
length: 6
[[Prototype]]: Array(0)

but when i replace undefined with null

console.log(rows.sort((a, b) => a.localeCompare(b, null, { numeric: true, sensitivity: 'base' })));

it outputs:

Uncaught TypeError: Cannot convert undefined or null to object
    at String.localeCompare (<anonymous>)
    at natSort.js:2:35
    at Array.sort (<anonymous>)
    at natSort.js:2:18

Why?

CodePudding user response:

It's how the spec is written. If the second argument (the locales string) is undefined, then it's assumed to be whatever is the system default. If it is anything other than undefined, then it must be a string with proper locale information, or else that's an error.

edit — as noted in a comment, the locales argument can also be an already-initialized locale list object. However, it's still true that undefined will work, while null will not.

CodePudding user response:

String#localeCompare takes three parameters:

Parameter name Expected value
compareString The string against which the referenceStr is compared.
locales and options These arguments customize the behavior of the function and let applications specify the language whose formatting conventions should be used. In implementations which ignore the locales and options arguments, the locale used and the form of the string returned are entirely implementation-dependent.

See the Intl.Collator() constructor for details on these parameters and how to use them.

When you pass a value for the locales parameter, it's passed to the Intl.Collator constructor listed above whose documentation is available here. The steps taken to construct an Intl.Collator include:

  1. Return ? InitializeCollator(collator, locales, options).

The documentation for InitializeCollator includes the step:

  1. Let requestedLocales be ? CanonicalizeLocaleList(locales).

Here's the actual answer, then. The documentation for CanonicalizeLocaleList includes the steps for handling the value you passed to locales all the way back in String#localeCompare:

  1. If locales is undefined, then
    a. Return a new empty List.
  2. [...]
  3. If Type(locales) is String or Type(locales) is Object and locales has an [[InitializedLocale]] internal slot, then
    a. Let O be ! CreateArrayFromListlocales »).
  4. Else
    a. Let O be ? ToObject(locales).
  5. [...]
  6. [...]
  7. [...]
  8. [...]

Looking at the documentation for ToObject(argument):

Argument Type Result
Undefined Throw a TypeError exception.
Null Throw a TypeError exception.
Boolean Return a new Boolean object whose [[BooleanData]] internal slot is set to argument. See 20.3 for a description of Boolean objects.
Number Return a new Number object whose [[NumberData]] internal slot is set to argument. See 21.1 for a description of Number objects.
String Return a new String object whose [[StringData]] internal slot is set to argument. See 22.1 for a description of String objects.
Symbol Return a new Symbol object whose [[SymbolData]] internal slot is set to argument. See 20.4 for a description of Symbol objects.
BigInt Return a new BigInt object whose [[BigIntData]] internal slot is set to argument. See 21.2 for a description of BigInt objects.
Object Return argument.

To summarize, if you pass:

undefined

localeCompare passes to Intl.Collator().constructor which passes to InitializeCollator which passes to CanonicalizeLocaleList which has an explicit step that returns a new empty List.

null

localeCompare passes to Intl.Collator().constructor which passes to InitializeCollator which passes to CanonicalizeLocaleList which calls ToObject which throws a TypeError if it's passed null.


You asked about two other cases in the comments:

{} (empty object)

localeCompare passes to Intl.Collator().constructor which passes to InitializeCollator which passes to CanonicalizeLocaleList which calls ToObject which returns the object it is passed.

Note that CanonicalizeLocaleList would have handled the object if it had an [[InitializedLocale]] internal slot.

'' (empty string)

localeCompare passes to Intl.Collator().constructor which passes to InitializeCollator which passes to CanonicalizeLocaleList which calls ToObject which returns a new String object which a length of zero so the rest of CanonicalizeLocaleList has no effect and an empty List is returned.

I'll admit, I don't know why the empty string case doesn't work. There's probably something deeper in the spec that handles that case.

  • Related