They're used to avoid conflicts for "generic method" hooks (similar to dunder methods in Python): the conflict issues is why environments have broken when new methods were added, and why e.g. Object.keys and their ilk are "static" functions rather than methods.
If you don't want JSON.stringify or Object.assign to see your properties just make them non-enumerable e.g.:
> const obj = {};
> Object.defineProperty(obj, 'prop', {'value': 42});
> obj
< {prop: 42}
> JSON.stringify(obj)
< "{}"
> Object.assign({}, obj)
< {}
Object.assign will copy symbol-keyed properties. In fact Symbol-keyed properties are enumerable by default.They live in an odd half-way world because many constructs only accept/handle string-keyed properties: Object.keys/entries/values and JSON.stringify use EnumerableOwnProperties and (for..in) uses EnumerateObjectProperties, both of which are specified to ignore non-string-keyed properties (before even checking for enumerability).
> I've always been confused by why Ruby uses symbols so much.
Mutable non-interned strings.
Good: Other people mainly covered it. Also note that when writing extensions using the C API, accepting options as symbols is generally much more convenient.
Less good:
val = "something"
val = :something
# Saves one character and no need for an end token.
{ "key" => val }
{ key: val }
# Saves four characters.I like the separation of strings for external data and symbols for internal data.
So symbols will usually be used for enums or things which logic or control flow depend on. Symbols then have the benefit of feeling like syntax and the scheme zen of code is data is code...