Why is 0.. _ is undefined

preface

Void 0 void 0 void 0

Void 0 is shorter and saves more space than undefined.

The main reason, of course, is that undefined can be overridden in local scopes

0.. _ shorter length, also undefined. It will be explained that it is equivalent to 0[‘_’], but there is no further discussion.

Several questions arose in my mind at that time:

  1. 0.. _How is it implicitly converted toundefined
  2. Why (almost) no one0.. _Instead ofvoid 0

0.. _Implicit conversion of

Lexical analysis

For decimal numbers, it is followed by the. Operator, which the JS engine does not know about. Is a decimal point or a. That accesses attributes, so it is specified as follows:

If the preceding digit is in base 10 and has a decimal point, the. Is an access attribute, otherwise it is a decimal point. If not in base 10, then. Is an access attribute

0.0. _// undefined equals (0.0)._
0.. _// Equivalent to (0.)._
00._ // Start with base 8
true. _/ / output is undefined
0._ // Syntax error. Should be followed by number

'use strict';
00._ // Uncaught SyntaxError: Octal literals are not allowed in strict mode. Octal is not resolved in strict mode
Copy the code

Note: the above is the conclusion of the test, not found in the specification.

However, based on the principles of compilation, the engine will first find 0 based on the lexical-numeric literal. This number is literal quantifier, followed by grammatical analysis

Also added syntax – numeric literals mention that OctalIntegerLiteral octal literals are allowed only in strict mode for NumericLiteral

Syntax analysis

Then there is the question of why numeric literals can have property access. This is an lvalue expression.

Lvalue expression syntax, part of which is listed here

LeftHandSideExpression :
NewExpression
CallExpression

CallExpression :
MemberExpression Arguments
CallExpression Arguments
CallExpression [ Expression ]
CallExpression . IdentifierName

MemberExpression :
PrimaryExpression
FunctionExpression
MemberExpression [ Expression ]
MemberExpression . IdentifierName
new MemberExpression Arguments

Copy the code

Lvalue expression – property access can be done in two ways

  • MemberExpression . IdentifierName
  • MemberExpression [ Expression ]

The former is equivalent to MemberExpression [< identity-name-string >]

< identifiers -name-string> is a string literal that contains the same sequence of characters as Unicode encoded IdentifierName.

For MemberExpression [Expression] expressions, they are executed in the following order:

  1. Make baseReference to explain the result of executing MemberExpression.
  2. BaseValue offers GetValue (baseReference).
  3. Makes propertyNameReference explain the result of executing the Expression.
  4. Make propertyNameValue GetValue (propertyNameReference).
  5. Call CheckObjectCoercible (baseValue).
  6. Make propertyNameString for ToString (propertyNameValue).
  7. Set Strict to true if the executing syntax production is included in strict mode code, and false otherwise.
  8. Returns a value of a reference type. The reference type, whose base value is baseValue, whose referenced name is propertyNameString, and whose strict mode tag is strict.

With 0.. For example, _ is equal to 0[‘_’], that is, MemberExpression = 0,Expression = ‘_’

  1. baseReference = 0
  2. baseValue = GetValue(baseReference) = 0
  3. propertyNameReference = ‘_’
  4. propertyNameValue = GetValue(propertyNameReference) = ‘_’
  5. BaseValue = ToObject(0) = new Number(0

Number {__proto__: Number, [[PrimitiveValue]]: 0}

  1. propertyNameString = ToString(propertyNameValue) = ‘_’
  2. Strict set
  3. Generates a reference whose base value isNumber { __proto__: Number, [[PrimitiveValue]]: 0}, the reference name is_. In the base value (and prototype chain)_Attribute search. Finally not found, returnundefined

The key is that ToObject is called while performing CheckObjectCoercible(0) to return a temporary wrapper object

This specification is a bit vague, stating only that CheckObjectCoercible throws an exception in case its argument cannot be converted to an object with ToObject, but not that baseValue does ToObject conversion. How long is the trigger condition and life cycle of a temporary wrapper type object in JS’s basic data type? – Tapirs eat incense. – Someone in Zhihu answered.

Why not use0.. _Instead ofvoid 0

We analyze from three aspects: readability, performance and correctness

readability

Void 0, 0.. _ reduces only one character, but it greatly reduces readability.

For compression tools that don’t care about readability, let’s look at it from a performance perspective.

performance

var COUNT = 100000000
var tmp
console.time("test1")
for(let i=0; i<COUNT; i++){if(tmp === void 0) {}}console.timeEnd("test1")
/ / test1:61.760986328125 ms
console.time("test2")
for(let i=0; i<COUNT; i++){if(tmp === 0._) {}}console.timeEnd("test2")
/ / test2:74.657958984375 ms
Copy the code

Void 0 is a little faster, but this doesn’t matter much, with execution differences between single instructions within microseconds.

Finally, it depends on whether the value of both is correct, that is, the result is always undefined

correctness

For void 0, void is the keyword and cannot be changed externally, so the return value always returns undefined, see the void operator

For 0.. _, as we have analyzed above, when the reference name is searched in the base value, it will be searched in the prototype chain, so it changes the prototype attribute of Number, Object, etc., 0.. The _ values are different

console.log(0._)// undefined
Object.prototype._ = 0
console.log(0._)/ / 0
Number.prototype._ = 1
console.log(0._)/ / 1
Copy the code

As can be seen, 0.. The _ result is not fixed and therefore cannot be used to replace void 0

reference

  1. Es5 specification _ Chinese version
  2. Es5 specification

Ps: Some parts of the Chinese translation are not accurate enough. You can first see the Chinese version for an overview, and then go to the original version for a detailed look