BFE.dev solution for JavaScript Quiz
63. in
TL;DR
in
operator checks if a property name is present in the target object(including its prototype chain),
if the property name is not Symbol nor string, it is coerced to string.
['foo']
is coerced to 'foo'
, so the result of both are true
.
const obj = {
foo: 'bar'
}
console.log('foo' in obj) // true
console.log(['foo'] in obj) // true
Below is longer version explaining the true logic behind this.
in
operator in ECMAScript spec
According to the spec, below is how in
works.
- Let lref be ? Evaluation of RelationalExpression.
- Let lval be ? GetValue(lref).
- Let rref be ? Evaluation of ShiftExpression.
- Let rval be ? GetValue(rref).
- If rval is not an Object, throw a TypeError exception.
- Return ? HasProperty(rval, ? ToPropertyKey(lval)).
We have 2 steps to run the check
- ToPropertyKey - convert the propert name to a valid property key
- HasProperty - check if the property exists
ToPropertyKey
This is straightforward from ECMAScript spec.
- Let key be ? ToPrimitive(argument, string).
- If key is a Symbol, then
- Return key.
- Return ! ToString(key).
The property name is converted to primitives. If it is Symbol, use it, otherwise convert it to string.
ToPrimitive() in ECMAScript Spec
This defines how we get primitive values from any data types, according to the ECMAScript spec:
- If input is an Object, then
- Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
- If exoticToPrim is not undefined, then
- If preferredType is not present, let hint be "default".
- Else if preferredType is string, let hint be "string".
- Else,
- Assert: preferredType is number.
- Let hint be "number".
- Let result be ? Call(exoticToPrim, input, « hint »).
- If result is not an Object, return result.
- Throw a TypeError exception.
- If preferredType is not present, let preferredType be number.
- Return ? OrdinaryToPrimitive(input, preferredType).
- Return input.
Here it says, if Symbol.toPrimitive is defined in the object, then the method will be used in ToPrimitive(). This is pretty obvious, below is an example.
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') {
return 100
}
return 'obj'
}
}
+obj // 100
If Symbol.toPrimitive is not defined, the default OrdinaryToPrimitive() is used.
OrdinaryToPrimitive() in ECMAScript spec
This is the default process before Symbol.toPrimitive is a thing. According to the ECMAScript spec:
- If hint is string, then
- Let methodNames be « "toString", "valueOf" ».
- Else,
- Let methodNames be « "valueOf", "toString" ».
- For each element name of methodNames, do
- Let method be ? Get(O, name).
- If IsCallable(method) is true, then
- Let result be ? Call(method, O).
- If result is not an Object, return result.
- Throw a TypeError exception.
What it says is basically
- if needs a string, first
toString()
thenvalueOf()
is tried and return the first primitive value; - if needs a number, first
valueOf()
thentoString()
is tried and return the first primitive value;
ToString
The ECMAScript spec is simply a mapping table.
Argument Type Result Undefined Return "undefined"
.Null Return "null"
.Boolean If argument is true, return "true"
. If argument is false, return"false"
.Number Return Number::toString(argument, 10). String Return argument. Symbol Throw a TypeError exception. BigInt Return BigInt::toString(argument, 10). Object Apply the following steps: 1. Let primValue be ? ToPrimitive(argument, string). 2. Return ? ToString(primValue).
HasProperty
HasProperty is fairly simple
The abstract operation HasProperty takes arguments O (an Object) and P (a property key) and returns either a normal completion containing a Boolean or a throw completion. It is used to determine whether an object has a property with the specified property key. The property may be either own or inherited. It performs the following steps when called:
Return ? O.[[HasProperty]](P).
So it is native implementation and it checks the prototype chain.
Step-by-step analysis.
const obj = {
foo: 'bar'
}
console.log('foo' in obj) // true
This is super easy, 'foo'
is string and in the object, so true
.
console.log(['foo'] in obj) // true
Internally following steps are how ['foo']
is converted to string
- there is no Symbol.toPrimitive defined in Array, continue.
- since we expect string,
toString()
is tried beforevalueOf()
, different from Object.prototype.toString(), Array has its own Array.prototype.toString()- we can see from the spec that
Array.prototype.join()
is used here. - in Array.prototype.join(), it tries to convert all elements to string, which recursively calls Array.prototype.toString().
- we can see from the spec that
Thus ['foo']
is converted to 'foo'
and 'foo' in object
is true
.