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:
- Return ? InitializeCollator(
collator,locales,options).
The documentation for InitializeCollator includes the step:
- Let
requestedLocalesbe ? 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:
- If
localesisundefined, then
a. Return a new emptyList. - [...]
- If Type(
locales) isStringor Type(locales) isObjectandlocaleshas an[[InitializedLocale]]internal slot, then
a. LetObe ! CreateArrayFromList(«locales»). - Else
a. LetObe ? ToObject(locales). - [...]
- [...]
- [...]
- [...]
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.
