Comprensión de la especificación ECMAScript, Parte 1



¡Buen dia amigos!

En este artículo, tomamos una función de la especificación y analizamos su explicación. Vamos.

Prefacio


Incluso si conoce bien JavaScript, leer la especificación puede ser difícil. El siguiente código demuestra el uso de Object.prototype.hasOwnProperty:

const o = {
    foo: 1
}
o.hasOwnProperty('foo') // true
o.hasOwnProperty('bar') // false

En el ejemplo, el objeto "o" no tiene el método "hasOwnProperty", por lo que nos referimos a su prototipo - "Object.prototype" (cadena de prototipo).

Para describir cómo funciona Object.hasOwnProperty, la especificación utiliza el siguiente pseudocódigo:

Object.prototype.hasOwnProperty (V)

Cuando se llama a hasOwnProperty con el argumento V, se realizan los siguientes pasos: ... y HasOwnProperty (O, P) La operación abstracta HasOwnProperty se usa para determinar si el objeto tiene su propia propiedad con una clave específica. Se devuelve el valor booleano. La operación se llama con los argumentos O y P. Esta operación consta de los siguientes pasos: ¿Qué es una "operación abstracta"? Qué [[ ]]? ¿Por qué una función tiene un signo de interrogación? ¿Qué significa "afirmación"? Vamos a averiguar.

  1. P ? ToPropertyKey(V)
  2. O ? ToObject( this)
  3. ? HasOwnProperty(O, P).







  1. (assert): Type(O) Object.
  2. : IsPropertyKey(P) true.
  3. desc ? O.[[GetOwnProperty]](P).
  4. desc undefined, false.
  5. true.





Tipos en el idioma y tipos en la especificación.


Comencemos con algo familiar. En la especificación, existen valores como indefinido, verdadero y falso, que JS conoce. Todos ellos son "valores de lenguaje" , "valores de tipos de lenguaje" , que también están definidos por la especificación.

La especificación utiliza valores de lenguaje integrados, por ejemplo, un tipo de datos interno puede contener un campo con un valor de verdadero o falso. Los motores JS generalmente no usan significados lingüísticos en sus mecanismos internos. Por ejemplo, si el motor JS está escrito en C ++, lo más probable es que use verdadero y falso de C ++, en lugar de la representación interna de los valores booleanos de JS.

Además de los tipos de lenguaje, la especificación usa tipos especiales (tipos de especificación), tipos que se usan solo en la especificación. Los motores JS no están obligados a ejecutarlos (pero pueden). En este artículo nos familiarizaremos con el tipo especial "Registro" (registro) y su subtipo "Registro de finalización" (registro de finalización).

Operaciones abstractas


Las operaciones abstractas son funciones definidas en la especificación; se definen para reducir la especificación. Los motores JS no están obligados a realizarlos como funciones independientes. En JS, no se pueden llamar directamente.

Ranuras internas y métodos internos.


Las ranuras internas y los métodos internos se indican mediante nombres encerrados en [[]].

Las ranuras internas son elementos de datos (conjuntos) de un objeto JS o tipo especial. Se utilizan para almacenar información sobre el estado del objeto. Los métodos internos son funciones miembro de un objeto JS.

Por ejemplo, cada objeto JS tiene una ranura interna [[Prototype]] y un método interno [[GetOwnProperty]].

Las ranuras y métodos internos no están disponibles en JS. Por ejemplo, no podemos acceder a o. [[Prototype]] o llamar a o. [[GetOwnProperty]] (). El motor JS puede ejecutarlos para sus propias necesidades (internas), pero no es necesario que lo haga.

A veces, los métodos internos se convierten en operaciones abstractas del mismo nombre, como es el caso de [[GetOwnProperty]]:

[[GetOwnProperty]] (P)

Cuando se llama al método interno [[GetOwnProperty]] del objeto “O” con la tecla “P”, se realizan las siguientes acciones: OrdinaryGetOwnProperty no es un método interno, ya que no está asociado con ningún objeto; el objeto con el que trabaja se le pasa como parámetro. OrdinaryGetOwnProperty se llama "ordinario" porque opera en objetos ordinarios. Los objetos en ECMAScript son ordinarios e inusuales (exóticos). Un objeto es ordinario si se comporta de manera predecible en respuesta a un conjunto de métodos llamados métodos internos esenciales. De lo contrario (cuando el objeto se comporta de manera impredecible; no como se esperaba; cuando el comportamiento del objeto se desvía de lo normal, es desviado), se considera inusual.

  1. ! OrdinaryGetOwnProperty(O, P)





El objeto inusual más famoso es la matriz, porque su propiedad de "longitud" se comporta de manera no estándar: establecer esta propiedad puede eliminar elementos de la matriz.

Puede encontrar una lista de métodos internos básicos aquí .

Registro de finalización


¿Qué pasa con las preguntas y los signos de exclamación? Para comprender esto, debe comprender qué es un registro de finalización .

Un registro de finalización es un tipo especial (definido únicamente para fines de especificación). No se requiere que el motor JS tenga el mismo tipo de datos internos.

Un registro de finalización es un tipo de datos que tiene un conjunto fijo de campos con nombre. El registro de finalización tiene tres campos:
[[Tipo]]normal, break, continue, return throw. , normal, « () » (abrupt comlpetions)
[[Value]], , , , ,
[[Target]]( )

Cada operación abstracta devuelve implícitamente un registro de finalización. Incluso si el resultado de la operación abstracta es un valor lógico simple, está envuelto en un registro de finalización de tipo normal (consulte Valores de finalización implícitos ).

Nota 1: la especificación no es muy consistente en esta parte; Hay varias funciones auxiliares que devuelven valores desnudos que se utilizan tal cual, sin ser recuperados del registro de finalización.

Nota 2: los autores de las especificaciones buscan hacer que el procesamiento de registros de finalización sea más explícito.

Si el algoritmo arroja una excepción, esto significa que se recibirá un registro de finalización con el tipo ([[Tipo]]) y el valor ([[Valor]]) como un objeto de excepción. No consideraremos otros tipos (pausa, continuar y regresar) por ahora.

ReturnIfAbrupt (argumento) significa realizar las siguientes operaciones: Esto es lo que es el registro de finalización; Si termina abruptamente, regrese de inmediato. De lo contrario, extraemos el valor del registro de finalización. ReturnIfAbrupt parece una llamada de función, pero no lo es. Llamamos a una función que devuelve ReturnIfAbrupt (), y no ReturnIfAbrupt en sí. Su comportamiento es más como una macro en lenguajes de programación tipo C. ReturnIfAbrupt se puede usar de la siguiente manera: aquí entra en juego

  1. argument - (abrupt), argument.
  2. argument argument.[[Value]].







  1. obj Foo() (obj - ).
  2. ReturnIfAbrupt(obj).
  3. Bar(obj) ( , , obj - , ).

Signo de interrogación : ¿ registro  ? Foo () es equivalente a ReturnIfAbrupt (Foo ()). El uso de esta abreviatura tiene un valor práctico: no necesitamos escribir código de controlador de errores cada vez.

Por analogía, ¡ que val sea la entrada ! Foo () es equivalente a lo siguiente: usando este conocimiento, podemos reescribir Object.prototype.hasOwnProperty de la siguiente manera: Object.prototype.hasOwnProperty (P) ... HasOwnProperty se puede reescribir así: HasOwnProperty (O, P) También podemos reescribir el método interno [ [GetOwnProperty]] sin un signo de exclamación: O. [[GetOWnProperty]] Suponemos que temp es una nueva variable temporal que no interactúa con nada.

  1. val Foo().
  2. : val .
  3. val val.[[Value]].





  1. P ToProperty(V).
  2. P , P.
  3. P P.[[Value]].
  4. O ToObject( this).
  5. O , O.
  6. O O.[[Value]].
  7. temp HasOwnProperty(O, P).
  8. temp , temp.
  9. temp temp.[[Value]].
  10. NormalCompletion(temp).





  1. : Type(O) Object.
  2. : IsPropertyKey(P) true.
  3. desc O.[[GetOWnProperty]](P).
  4. desc , desc.
  5. desc desc.[[Value]].
  6. desc undefined, NormalCompletion(false).
  7. NormalCompletion(true).





  1. temp OrdinaryGetOwnProperty(O, P).
  2. : temp .
  3. temp temp.[[Value]].
  4. NormalCompletion(temp).



También sabemos que en el caso de que la instrucción return devuelva algo distinto de un registro de finalización, este algo se envuelve implícitamente en NormalCompletion.

Respaldo: ¿Regreso? Foo ()


¿La especificación utiliza Devolución? Foo (): ¿por qué hay un signo de interrogación aquí? Regreso de

registro ? Foo () se puede expandir de la siguiente manera: ¿Comportamiento de retorno? Foo () es igual para la terminación normal y repentina. Regreso de registro? Foo () le permite indicar más claramente que Foo devuelve un registro de finalización.

  1. temp Foo().
  2. temp , temp.
  3. temp temp.[[Value]].
  4. NormalCompletion.





Declaraciones


Las declaraciones en la especificación "afirman" las condiciones invariantes de los algoritmos. Se agregan a la especificación para mayor claridad, pero no contienen ningún requisito de implementación; por lo tanto, no necesitan ser verificados por una implementación específica.

¿Que sigue?


Aprendimos a leer la especificación de métodos simples como Object.prototype.hasOwnProperty y operaciones abstractas como HasOwnProperty. Teniendo este conocimiento, podemos entender lo que hacen otras operaciones abstractas, que se discutirán en la siguiente parte. También en el próximo artículo, veremos los Descriptores de propiedades, que son otro tipo especial.


Gracias por su atención. ¡Feliz codificación!

All Articles