Capítulo 4. Expresiones y operadores

Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com

Este capítulo de documenta las expresiones de JavaScript y los operadores con los que se construyen muchas de esas expresiones. Una expresión es una frase de JavaScript que puede evaluarse para producir un valor. Una constante incrustada literalmente en tu programa es un tipo de expresión muy simple. Un nombre de variable también es una expresión simple que se evalúa a cualquier valor que se haya asignado a esa variable. Las expresiones complejas se construyen a partir de expresiones más sencillas. Una expresión de acceso a una matriz, por ejemplo, consiste en una expresión que se evalúa como una matriz, seguida de un corchete abierto, una expresión que se evalúa como un número entero y un corchete cerrado. Esta nueva expresión, más compleja, se evalúa al valor almacenado en el índice especificado de la matriz especificada. Del mismo modo, una expresión de invocación a una función consiste en una expresión que se evalúa como un objeto de función y cero o más expresiones adicionales que se utilizan como argumentos de la función.

La forma más habitual de construir una expresión compleja a partir de expresiones más sencillas es con un operador. Un operador combina los valores de sus operandos (normalmente dos de ellos) de alguna manera y se evalúa a un nuevo valor. El operador de multiplicación * es un ejemplo sencillo. La expresión x * y se evalúa como el producto de los valores de las expresiones x y y. Para simplificar, a veces decimos que un operador devuelve un valor en lugar de "evalúa a" un valor.

Este capítulo documenta todos los operadores de JavaScript, y también explica expresiones (como la indexación de matrices y la invocación de funciones) que no utilizan operadores. Si ya conoces otro lenguaje de programación que utilice sintaxis de tipo C, verás que la sintaxis de la mayoría de las expresiones y operadores de JavaScript ya te resulta familiar.

4.1 Expresiones primarias

Las expresiones más sencillas, conocidas como expresiones primarias, son las que se mantienen solas: no incluyen ninguna expresión más sencilla. Las expresiones primarias en JavaScript son valores constantes o literales, determinadas palabras clave del lenguaje y referencias a variables.

Los literales son valores constantes que se incrustan directamente en tu programa. Tienen el siguiente aspecto

1.23         // A number literal
"hello"      // A string literal
/pattern/    // A regular expression literal

La sintaxis de JavaScript para los literales numéricos se trató en §3 .2. Los literales de cadena se documentaron en §3.3. La sintaxis de los literales de expresiones regulares se introdujo en §3.3.5 y se documentará en detalle en §11.3.

Algunas de las palabras reservadas de JavaScript son expresiones primarias:

true       // Evalutes to the boolean true value
false      // Evaluates to the boolean false value
null       // Evaluates to the null value
this       // Evaluates to the "current" object

Aprendimos sobre true, false y null en§3 .4 y§3.5. A diferencia de y las demás palabras clave, this no es una constante, sino que se evalúa con valores diferentes en distintos lugares del programa. La palabra clave this se utiliza en la programación orientada a objetos. Dentro del cuerpo de un método, this se evalúa al objeto sobre el que se invocó el método. Consulta §4.5, el Capítulo 8(especialmente §8.2.2) y el Capítulo 9 para saber más sobre this.

Por último, el tercer tipo de expresión primaria es una referencia a una variable, constante o propiedad del objeto global:

i             // Evaluates to the value of the variable i.
sum           // Evaluates to the value of the variable sum.
undefined     // The value of the "undefined" property of the global object

Cuando cualquier identificador aparece solo en un programa, JavaScript asume que es una variable o constante o propiedad del objeto global y busca su valor. Si no existe ninguna variable con ese nombre, al intentar evaluar una variable inexistente se produce un error de referencia (ReferenceError).

4.2 Inicializadores de objetos y matrices

Los inicializadores deobjetos y de matrices son expresiones cuyo valor es un objeto o una matriz recién creados. Estas expresiones inicializadoras se denominan a veces literales de objeto y literales de matriz. Sin embargo, a diferencia de los literales verdaderos, no son expresiones primarias, porque incluyen una serie de subexpresiones que especifican valores de propiedades y elementos. Los inicializadores de matrices tienen una sintaxis algo más sencilla, por lo que empezaremos por ellos.

Un inicializador de matriz es una lista de expresiones separadas por comas y contenidas entre corchetes. El valor de un inicializador de matriz es una matriz recién creada. Los elementos de esta nueva matriz se inicializan con los valores de las expresiones separadas por comas:

[]         // An empty array: no expressions inside brackets means no elements
[1+2,3+4]  // A 2-element array.  First element is 3, second is 7

Las expresiones de elementos de un inicializador de matrices pueden ser a su vez inicializadores de matrices, lo que significa que estas expresiones pueden crear matrices anidadas:

let matrix = [[1,2,3], [4,5,6], [7,8,9]];

Las expresiones de elemento de un inicializador de matriz se evalúan cada vez que se evalúa el inicializador de matriz. Esto significa que el valor de la expresión de un inicializador de matriz puede ser diferente cada vez que se evalúa.

Los elementos indefinidos pueden incluirse en un literal de matriz simplemente omitiendo un valor entre comas. Por ejemplo, la siguiente matriz contiene cinco elementos, incluidos tres indefinidos:

let sparseArray = [1,,,,5];

Se permite una sola coma final después de la última expresión de un inicializador de matriz y no crea un elemento indefinido. Sin embargo, cualquier expresión de acceso a la matriz para un índice posterior al de la última expresión se evaluará necesariamente como indefinido.

Las expresiones inicializadoras de objetos son como las expresiones inicializadoras de matrices, pero los corchetes se sustituyen por llaves, y cada subexpresión va precedida de un nombre de propiedad y dos puntos:

let p = { x: 2.3, y: -1.2 };  // An object with 2 properties
let q = {};                   // An empty object with no properties
q.x = 2.3; q.y = -1.2;        // Now q has the same properties as p

En ES6, los literales de objeto tienen una sintaxis mucho más rica en funciones (encontrarás más detalles en §6.10). Los literales de objeto pueden anidarse. Por ejemplo

let rectangle = {
    upperLeft: { x: 2, y: 2 },
    lowerRight: { x: 4, y: 5 }
};

Volveremos a ver inicializadores de objetos y matrices en los capítulos 6 y 7.

4.3 Expresiones de definición de funciones

Una expresión de definición de función define una función JavaScript, y el valor de dicha expresión es la función recién definida. En cierto sentido, una expresión de definición de función es un "literal de función" del mismo modo que un inicializador de objeto es un "literal de objeto". Una expresión de definición de función suele consistir en la palabra clave functionseguida de una lista separada por comas de cero o más identificadores (los nombres de los parámetros) entre paréntesis y un bloque de código JavaScript (el cuerpo de la función) entre llaves. Por ejemplo

// This function returns the square of the value passed to it.
let square = function(x) { return x * x; };

Una expresión de definición de función también puede incluir un nombre para la función. Las funciones también pueden definirse utilizando una declaración de función en lugar de una expresión de función. Y en ES6 y posteriores, las expresiones de función pueden utilizar una nueva y compacta sintaxis de "función flecha". Los detalles completos sobre la definición de funciones se encuentran en el Capítulo 8.

4.4 Expresiones de acceso a propiedades

Una expresión de acceso a propiedades evalúa el valor de una propiedad de un objeto o de un elemento de una matriz. JavaScript define dos sintaxis para el acceso a propiedades:

expression . identifier
expression [ expression ]

El primer estilo de acceso a propiedades es una expresión seguida de un punto y un identificador. La expresión especifica el objeto, y el identificador especifica el nombre de la propiedad deseada. El segundo estilo de acceso a propiedades sigue a la primera expresión (el objeto o la matriz) con otra expresión entre corchetes. Esta segunda expresión especifica el nombre de la propiedad deseada o el índice del elemento de la matriz deseado. He aquí algunos ejemplos concretos:

let o = {x: 1, y: {z: 3}}; // An example object
let a = [o, 4, [5, 6]];    // An example array that contains the object
o.x                        // => 1: property x of expression o
o.y.z                      // => 3: property z of expression o.y
o["x"]                     // => 1: property x of object o
a[1]                       // => 4: element at index 1 of expression a
a[2]["1"]                  // => 6: element at index 1 of expression a[2]
a[0].x                     // => 1: property x of expression a[0]

Con cualquiera de los dos tipos de expresión de acceso a propiedades, primero se evalúa la expresión anterior a . o [. Si el valor es null o undefined, la expresión lanza un TypeError, ya que son los dos valores de JavaScript que no pueden tener propiedades. Si la expresión de objeto va seguida de un punto y un identificador, se busca el valor de la propiedad nombrada por ese identificador y se convierte en el valor global de la expresión. Si la expresión objeto va seguida de otra expresión entre corchetes, esa segunda expresión se evalúa y se convierte en una cadena. El valor global de la expresión es entonces el valor de la propiedad nombrada por esa cadena. En cualquier caso, si la propiedad nombrada no existe, el valor de la expresión de acceso a la propiedad es undefined.

La sintaxis .identificador es la más sencilla de las dos opciones de acceso a propiedades, pero ten en cuenta que sólo se puede utilizar cuando la propiedad a la que quieres acceder tiene un nombre que es un identificador legal, y cuando conoces el nombre al escribir el programa. Si el nombre de la propiedad incluye espacios o caracteres de puntuación, o cuando es un número (para matrices), debes utilizar la notación de corchetes. Los corchetes también se utilizan cuando el nombre de la propiedad no es estático, sino que es el resultado de un cálculo (consulta un ejemplo en §6.3.1 ).

Los objetos y sus propiedades se tratan con detalle en el Capítulo 6, y las matrices y sus elementos, en el Capítulo 7.

4.4.1 Acceso condicional a la propiedad

ES2020 añade dos nuevos tipos de expresiones de acceso a propiedades:

expression ?. identifier
expression ?.[ expression ]

En JavaScript, los valores null y undefined son los dos únicos valores que no tienen propiedades. En una expresión regular de acceso a propiedades que utilice . o [], obtendrás un TypeError si la expresión de la izquierda se evalúa como null o undefined. Puedes utilizar la sintaxis ?.y ?.[] para protegerte de errores de este tipo.

Considera la expresión a?.b. Si a es null o undefined, entonces la expresión se evalúa como undefined sin intentar acceder a la propiedad b. Si a es algún otro valor, entonces a?.b se evalúa como a.b (y si a no tiene una propiedad llamada b, entonces el valor volverá a ser undefined).

Esta forma de expresión de acceso a propiedades a veces se denomina "encadenamiento opcional" porque también funciona para expresiones de acceso a propiedades "encadenadas" más largas, como ésta:

let a = { b: null };
a.b?.c.d   // => undefined

a es un objeto, por lo que a.b es una expresión de acceso a propiedades válida. Pero el valor de a.b es null, por lo que a.b.c lanzaría un TypeError. Al utilizar ?. en lugar de . evitamos el error de tipo, y a.b?.cse evalúa como undefined. Esto significa que (a.b?.c).d provocará un error de tipo, porque esa expresión intenta acceder a una propiedad con el valor undefined. Pero -y ésta es una parte muy importante del "encadenamiento opcional"-a.b?.c.d (sin los paréntesis) simplemente se evalúa como undefined y no arroja ningún error. Esto se debe a que el acceso a propiedades con ?. es un "cortocircuito": si la subexpresión a la izquierda de ?. se evalúa como null o undefined, entonces toda la expresión se evalúa inmediatamente como undefined sin más intentos de acceso a propiedades.

Por supuesto, si a.b es un objeto, y si ese objeto no tiene una propiedad llamada c, entonces a.b?.c.d volverá a lanzar un TypeError, y querremos utilizar otro acceso condicional a la propiedad:

let a = { b: {} };
a.b?.c?.d  // => undefined

El acceso condicional a propiedades también es posible utilizando ?.[] en lugar de[]. En la expresión a?.[b][c], si el valor de a es null oundefined, toda la expresión se evalúa inmediatamente aundefined, y las subexpresiones b y c ni siquiera se evalúan. Si alguna de esas expresiones tiene efectos secundarios, el efecto secundario no se producirá si a no está definido:

let a;          // Oops, we forgot to initialize this variable!
let index = 0;
try {
    a[index++]; // Throws TypeError
} catch(e) {
    index       // => 1: increment occurs before TypeError is thrown
}
a?.[index++]    // => undefined: because a is undefined
index           // => 1: not incremented because ?.[] short-circuits
a[index++]      // !TypeError: can't index undefined.

El acceso condicional a propiedades con ?. y ?.[] es una de las características más recientes de JavaScript. A principios de 2020, esta nueva sintaxis es compatible con las versiones actuales o beta de la mayoría de los principales navegadores.

4.5 Expresiones de invocación

Una expresión de invocación es La sintaxis de JavaScript para llamar (o ejecutar) una función o método. Comienza con una expresión de función que identifica la función a llamar. La expresión de función va seguida de un paréntesis de apertura, una lista separada por comas de cero o más expresiones de argumentos y un paréntesis de cierre. Algunos ejemplos:

f(0)            // f is the function expression; 0 is the argument expression.
Math.max(x,y,z) // Math.max is the function; x, y, and z are the arguments.
a.sort()        // a.sort is the function; there are no arguments.

Cuando se evalúa una expresión de invocación, primero se evalúa la expresión de la función y, a continuación, se evalúan las expresiones de los argumentos para obtener una lista de valores de los argumentos. Si el valor de la expresión de la función no es una función, se produce un error de tipo TypeError. A continuación, los valores de los argumentos se asignan, por orden, a los nombres de los parámetros especificados al definir la función y, después, se ejecuta el cuerpo de la función. Si la función utiliza una sentencia return para devolver un valor, entonces ese valor se convierte en el valor de la expresión de invocación. En caso contrario, el valor de la expresión de invocación es undefined. Los detalles completos sobre la invocación de funciones, incluida una explicación de lo que ocurre cuando el número de expresiones de argumentos no coincide con el número de parámetros de la definición de la función, están en el Capítulo 8.

Toda expresión de invocación incluye un par de paréntesis y una expresión antes del paréntesis abierto. Si esa expresión es una expresión de acceso a una propiedad, entonces la invocación se conoce como invocación a un método. En las invocaciones a métodos, el objeto o matriz objeto del acceso a propiedades se convierte en el valor de la palabra clave this mientras se ejecuta el cuerpo de la función. Esto permite un paradigma de programación orientada a objetos en el que las funciones (que llamamos "métodos" cuando se utilizan de esta forma) operan sobre el objeto del que forman parte. Para más detalles, consulta el Capítulo 9.

4.5.1 Invocación condicional

En ES2020, también puedes invocar una función utilizando ?.() en lugar de(). Normalmente, cuando invocas una función, si la expresión a la izquierda del paréntesis es null o undefined o cualquier otra no-función, se lanza un TypeError. Con la nueva sintaxis de invocación ?.(), si la expresión a la izquierda de ?. se evalúa como nullo undefined, entonces toda la expresión de invocación se evalúa comoundefined y no se lanza ninguna excepción.

Los objetos array tienen un método sort() al que opcionalmente se le puede pasar un argumento de función que defina el orden de clasificación deseado para los elementos del array. Antes de ES2020, si querías escribir un método como sort()que tomaba un argumento de función opcional, normalmente utilizabas una sentenciaif para comprobar que el argumento de función estaba definido antes de invocarlo en el cuerpo de if:

function square(x, log) { // The second argument is an optional function
    if (log) {            // If the optional function is passed
        log(x);           // Invoke it
    }
    return x * x;         // Return the square of the argument
}

Sin embargo, con esta sintaxis de invocación condicional de ES2020, puedes escribir simplemente la invocación de la función utilizando ?.(), sabiendo que la invocación sólo se producirá si realmente hay un valor que invocar:

function square(x, log) { // The second argument is an optional function
    log?.(x);             // Call the function if there is one
    return x * x;         // Return the square of the argument
}

Ten en cuenta, sin embargo, que ?.() sólo comprueba si el lado izquierdo esnull o undefined. No verifica que el valor sea realmente una función. Así que la función square() de este ejemplo seguiría lanzando una excepción si le pasaras dos números, por ejemplo.

Al igual que las expresiones de acceso condicional a propiedades(§4.4.1), la invocación de funciones con ?.() es un cortocircuito: si el valor a la izquierda de ?. es null oundefined, entonces no se evalúa ninguna de las expresiones de argumentos dentro del paréntesis:

let f = null, x = 0;
try {
    f(x++); // Throws TypeError because f is null
} catch(e) {
    x       // => 1: x gets incremented before the exception is thrown
}
f?.(x++)    // => undefined: f is null, but no exception thrown
x           // => 1: increment is skipped because of short-circuiting

Las expresiones de invocación condicional con ?.() funcionan igual de bien para los métodos que para las funciones. Pero como la invocación de métodos también implica el acceso a propiedades, merece la pena que te tomes un momento para asegurarte de que entiendes las diferencias entre las siguientes expresiones:

o.m()     // Regular property access, regular invocation
o?.m()    // Conditional property access, regular invocation
o.m?.()   // Regular property access, conditional invocation

En la primera expresión, o debe ser un objeto con una propiedad m y el valor de esa propiedad debe ser una función. En la segunda expresión, si o es null o undefined, entonces la expresión se evalúa como undefined. Pero si o tiene cualquier otro valor, entonces debe tener una propiedad m cuyo valor sea una función. Y en la tercera expresión, o no debe ser null ni undefined. Si no tiene una propiedad m, o si el valor de esa propiedad es null, entonces toda la expresión se evalúa a undefined.

La invocación condicional con ?.() es una de las características más recientes de JavaScript. Desde los primeros meses de 2020, esta nueva sintaxis es compatible con las versiones actuales o beta de la mayoría de los principales navegadores.

4.6 Expresiones de creación de objetos

Una expresión de creación de objeto crea un nuevo objeto e invoca una función (llamada constructor) para inicializar las propiedades de ese objeto. Las expresiones de creación de objetos son como las expresiones de invocación, salvo que llevan como prefijo la palabra clave new:

new Object()
new Point(2,3)

Si no se pasan argumentos a la función constructora en una expresión de creación de objeto, se puede omitir el par de paréntesis vacío:

new Object
new Date

El valor de una expresión de creación de objeto es el objeto recién creado. Los constructores se explican con más detalle en el capítulo 9.

4.7 Visión general del operador

Operadores se utilizan para las expresiones aritméticas de JavaScript, las expresiones de comparación, las expresiones lógicas, las expresiones de asignación, etc.La Tabla 4-1 resume los operadores y sirve de cómoda referencia.

Ten en cuenta que la mayoría de los operadores se representan mediante caracteres de puntuación, como + y =. Algunos, sin embargo, se representan mediante palabras clave, como delete y instanceof. Los operadores con palabras clave son operadores regulares, igual que los expresados con signos de puntuación; simplemente tienen una sintaxis menos sucinta.

La Tabla 4-1 está organizada por la precedencia de los operadores. Los operadores enumerados en primer lugar tienen mayor precedencia que los enumerados en último lugar. Los operadores separados por una línea horizontal tienen distintos niveles de precedencia. La columna A indica la asociatividad del operador, que puede ser L (de izquierda a derecha) o R (de derecha a izquierda), y la columna N especifica el número de operandos. La columna Tipos enumera los tipos esperados de los operandos y (después del símbolo →) el tipo de resultado del operador. Las subsecciones que siguen a la tabla explican los conceptos de precedencia, asociatividad y tipo de operando. Los propios operadores se documentan individualmente a continuación.

Tabla 4-1. Operadores de JavaScript
Operario Operación A N Tipos

++

Aumento previo o posterior

R

1

lval→num

--

Antes o después de la disminución

R

1

lval→num

-

Negar número

R

1

num→num

+

Convertir a número

R

1

cualquier→num

~

Invertir bits

R

1

int→int

!

Invertir valor booleano

R

1

bool→bool

delete

Eliminar una propiedad

R

1

lval→bool

typeof

Determinar el tipo de operando

R

1

cualquier→str

void

Devuelve un valor indefinido

R

1

cualquier→undef

**

Exponenciar

R

2

num,num→num

*, /, %

Multiplicar, dividir, resto

L

2

num,num→num

+, -

Suma, resta

L

2

num,num→num

+

Concatenar cadenas

L

2

str,str→str

<<

Desplazamiento a la izquierda

L

2

int,int→int

>>

Desplazamiento a la derecha con extensión del signo

L

2

int,int→int

>>>

Desplazamiento a la derecha con extensión cero

L

2

int,int→int

<, <=,>, >=

Comparar en orden numérico

L

2

num,num→bool

<, <=,>, >=

Comparar por orden alfabético

L

2

str,str→bool

instanceof

Clase de objeto de prueba

L

2

obj,func→bool

in

Comprueba si la propiedad existe

L

2

cualquiera,obj→bool

==

Prueba de igualdad no estricta

L

2

any,any→bool

!=

Prueba de desigualdad no estricta

L

2

any,any→bool

===

Prueba de igualdad estricta

L

2

any,any→bool

!==

Prueba de desigualdad estricta

L

2

any,any→bool

&

Calcular AND a nivel de bit

L

2

int,int→int

^

Calcular XOR a nivel de bit

L

2

int,int→int

|

Calcula la OR a nivel de bit

L

2

int,int→int

&&

Calcula el AND lógico

L

2

cualquiera, cualquiera→cualquiera

||

Calcula la OR lógica

L

2

cualquiera, cualquiera→cualquiera

??

Elige el 1er operando definido

L

2

cualquiera, cualquiera→cualquiera

?:

Elige el 2º o 3º operando

R

3

bool,any,any→any

=

Asignar a una variable o propiedad

R

2

lval,cualquier→cualquier

**=, *=, /=, %=,

Operar y asignar

R

2

lval,cualquier→cualquier

+=, -=, &=, ^=, |=,

<<=, >>=, >>>=

,

Descarta el 1er operando, devuelve el 2º

L

2

cualquiera, cualquiera→cualquiera

4.7.1 Número de operandos

Los operadores pueden clasificarse en función del número de operandos que esperan (su aridad). La mayoría de los operadores de JavaScript, como el operador de multiplicación *, son operadores binarios que combinan dos expresiones en una única expresión más compleja. Es decir, esperan dos operandos. JavaScript también admite una serie de operadores unarios, que convierten una expresión simple en una única expresión más compleja. El operador de la expresión −x es un operador unario que realiza la operación de negación sobre el operando x. Por último, JavaScript admite un operador ternario, el operador condicional ?:, que combina tres expresiones en una sola.

4.7.2 Tipo de operando y resultado

Algunos operadores de funcionan con valores de cualquier tipo, pero la mayoría esperan que sus operandos sean de un tipo específico, y la mayoría de los operadores devuelven (o evalúan a) un valor de un tipo específico. La columna Tipos dela Tabla 4-1 especifica los tipos de operando (antes de la flecha) y de resultado (después de la flecha) de los operadores.

Los operadores de JavaScript suelen convertir el tipo (véase §3.9) de sus operandos según sea necesario. El operador de multiplicación * espera operandos numéricos, pero la expresión "3" * "5" es legal porque JavaScript puede convertir los operandos en números. El valor de esta expresión es el número 15, no la cadena "15", por supuesto. Recuerda también que todos los valores de JavaScript son "verdaderos" o "falsos", por lo que los operadores que esperan operandos booleanos funcionarán con un operando de cualquier tipo.

Algunos operadores se comportan de forma diferente según el tipo de operandos que se utilicen con ellos. En particular, el operador + suma operandos numéricos pero concatena operandos de cadena. Del mismo modo, los operadores de comparación como < realizan la comparación en orden numérico o alfabético según el tipo de los operandos. Las descripciones de los operadores individuales explican sus dependencias de tipo y especifican qué conversiones de tipo realizan.

Observa en que los operadores de asignación y algunos de los demás operadores enumerados en la Tabla 4-1 esperan un operando del tipo lval. lvalue es un término histórico que significa "una expresión que puede aparecer legalmente en el lado izquierdo de una expresión de asignación". En JavaScript, las variables, las propiedades de los objetos y los elementos de las matrices son lvalues.

4.7.3 Efectos secundarios del operador

Evaluar una expresión simple como 2 * 3 nunca afecta al estado de tu programa, y cualquier cálculo futuro que realice tu programa no se verá afectado por esa evaluación. Sin embargo, algunas expresiones tienenefectos secundarios, y su evaluación puede afectar al resultado de futuras evaluaciones. Los operadores de asignación son el ejemplo más obvio: si asignas un valor a una variable o propiedad, eso cambia el valor de cualquier expresión que utilice esa variable o propiedad. Los operadores de incremento y decremento ++ y-- son similares, ya que realizan una asignación implícita. El operador delete también tiene efectos secundarios: borrar una propiedad es como (pero no lo mismo que) asignar undefined a la propiedad.

Ningún otro operador de JavaScript tiene efectos secundarios, pero las expresiones de invocación de funciones y de creación de objetos tendrán efectos secundarios si alguno de los operadores utilizados en el cuerpo de la función o del constructor tiene efectos secundarios.

4.7.4 Precedencia del operador

Los operadores de que aparecen en la Tabla 4-1 están ordenados de mayor a menor precedencia, con líneas horizontales que separan grupos de operadores del mismo nivel de precedencia. La precedencia de los operadores controla el orden en que se realizan las operaciones. Los operadores con mayor precedencia (más cerca de la parte superior de la tabla) se ejecutan antes que los de menor precedencia (más cerca de la parte inferior).

Considera la siguiente expresión:

w = x + y*z;

El operador de multiplicación * tiene mayor precedencia que el operador de suma +, por lo que la multiplicación se realiza antes que la suma. Además, el operador de asignación = tiene la precedencia más baja, por lo que la asignación se realiza después de que se hayan completado todas las operaciones del lado derecho.

La precedencia de los operadores puede anularse con el uso explícito de paréntesis. Para forzar que la suma del ejemplo anterior se realice primero, escribe:

w = (x + y)*z;

Ten en cuenta que las expresiones de acceso e invocación de propiedades tienen mayor precedencia que cualquiera de los operadores enumerados en la Tabla 4-1. Considera esta expresión:

// my is an object with a property named functions whose value is an
// array of functions. We invoke function number x, passing it argument
// y, and then we ask for the type of the value returned.
typeof my.functions[x](y)

Aunque typeof es uno de los operadores de mayor prioridad, la operacióntypeof se realiza sobre el resultado del acceso a la propiedad, el índice de la matriz y la invocación a la función, todos los cuales tienen mayor prioridad que los operadores.

En la práctica, si no estás seguro de la precedencia de tus operadores, lo más sencillo es utilizar paréntesis para explicitar el orden de evaluación. Las reglas que es importante conocer son las siguientes: la multiplicación y la división se realizan antes que la suma y la resta, y la asignación tiene una precedencia muy baja y casi siempre se realiza en último lugar.

Cuando se añaden nuevos operadores a JavaScript, no siempre encajan de forma natural en este esquema de precedencia. El operador ?? (§4.13.2) aparece en la tabla como de menor precedencia que ||y &&, pero, de hecho, su precedencia relativa a esos operadores no está definida, y ES2020 requiere que utilices explícitamente paréntesis si mezclas ?? con || o &&. De forma similar, el nuevo operador de exponenciación **no tiene una precedencia bien definida en relación con el operador unario de negación, y debes utilizar paréntesis cuando combines la negación con la exponenciación.

4.7.5 Asociatividad de operadores

En la Tabla 4-1, la columna etiquetada como A especifica la asociatividad del operador. Un valor de L especifica la asociatividad de izquierda a derecha, y un valor de R especifica la asociatividad de derecha a izquierda. La asociatividad de un operador especifica el orden en que se realizan las operaciones de la misma precedencia. La asociatividad de izquierda a derecha significa que las operaciones se realizan de izquierda a derecha. Por ejemplo, el operador resta tiene asociatividad de izquierda a derecha, por lo que:

w = x - y - z;

es lo mismo que

w = ((x - y) - z);

Por otra parte, las expresiones siguientes:

y = a ** b ** c;
x = ~-y;
w = x = y = z;
q = a?b:c?d:e?f:g;

son equivalentes:

y = (a ** (b ** c));
x = ~(-y);
w = (x = (y = z));
q = a?b:(c?d:(e?f:g));

porque los operadores de exponenciación, unario, asignación y condicional ternario tienen asociatividad de derecha a izquierda.

4.7.6 Orden de evaluación

La precedencia y la asociatividad del operador especifican el orden en que se realizan las operaciones en una expresión compleja, pero no especifican el orden en que se evalúan las subexpresiones. JavaScript siempre evalúa las expresiones en orden estrictamente de izquierda a derecha. En la expresión w = x + y * z, por ejemplo, se evalúa primero la subexpresión w, seguida de x, y y z. A continuación, los valores de yy z se multiplican, se suman al valor de x, y se asignan a la variable o propiedad especificada por la expresión w. Añadir paréntesis a las expresiones puede cambiar el orden relativo de la multiplicación, la suma y la asignación, pero no el orden de evaluación de izquierda a derecha.

El orden de evaluación sólo tiene importancia si alguna de las expresiones que se evalúan tiene efectos secundarios que afectan al valor de otra expresión. Si la expresión x incrementa una variable que es utilizada por la expresión z, entonces el hecho de que x se evalúe antes que z es importante.

4.8 Expresiones aritméticas

Esta sección de cubre los operadores que realizan manipulaciones aritméticas u otras manipulaciones numéricas en sus operandos. Los operadores de exponenciación, multiplicación, división y sustracción son sencillos y se tratan en primer lugar. El operador de suma tiene su propia subsección porque también puede realizar la concatenación de cadenas y tiene algunas reglas de conversión de tipos poco habituales. Los operadores unarios y los operadores bit a bit también se tratan en subsecciones propias.

La mayoría de estos operadores aritméticos (excepto los que se indican a continuación) pueden utilizarse con operandos BigInt (véase §3.2.5) o con números normales, siempre que no mezcles ambos tipos.

Los operadores aritméticos básicos son ** (exponenciación), *(multiplicación), / (división), % (módulo: resto tras la división), + (suma) y - (resta). Como se ha indicado, hablaremos del operador + en una sección propia. Los otros cinco operadores básicos se limitan a evaluar sus operandos, convertir los valores en números si es necesario y, a continuación, calcular la potencia, el producto, el cociente, el resto o la diferencia. Los operandos no numéricos que no pueden convertirse a números se convierten al valor NaN. Si alguno de los operandos es (o se convierte en) NaN, el resultado de la operación es (casi siempre) NaN.

El operador ** tiene mayor precedencia que *, / y % (que a su vez tienen mayor precedencia que + y -). A diferencia de los otros operadores, ** funciona de derecha a izquierda, por lo que 2**2**3 es lo mismo que2**8, no que 4**3. Existe una ambigüedad natural en expresiones como-3**2. Dependiendo de la precedencia relativa del menos unario y la exponenciación, esa expresión podría significar (-3)**2 o-(3**2). Los distintos lenguajes manejan esto de forma diferente, y en lugar de tomar partido, JavaScript simplemente convierte en un error sintáctico omitir los paréntesis en este caso, obligándote a escribir una expresión inequívoca. ** es El operador aritmético más reciente de JavaScript: se añadió al lenguaje con ES2016. Sin embargo, la función Math.pow() ha estado disponible desde las primeras versiones de JavaScript, y realiza exactamente la misma operación que el operador **.

El operador / divide su primer operando por el segundo. Si estás acostumbrado a lenguajes de programación que distinguen entre números enteros y de coma flotante, es posible que esperes obtener un resultado entero al dividir un número entero por otro. En JavaScript, sin embargo, todos los números son de coma flotante, por lo que todas las operaciones de división tienen resultados de coma flotante: 5/2 se evalúa como 2.5, no como 2. La división por cero da infinito positivo o negativo, mientras que 0/0 se evalúa comoNaN: en ninguno de estos casos se produce un error.

El operador % calcula el primer operando a partir del segundo operando, es decir, devuelve el resto de la división entera del primer operando por el segundo operando. El signo del resultado es el mismo que el del primer operando. Por ejemplo, 5 % 2 equivale a 1, y -5 % 2 equivale a -1.

Aunque el operador módulo se utiliza normalmente con operandos enteros, también funciona con valores de coma flotante. Por ejemplo, 6.5 % 2.1se evalúa como 0.2.

4.8.1 El operador +

El operador binario + suma operandos numéricos o concatena operandos de cadena:

1 + 2                        // => 3
"hello" + " " + "there"      // => "hello there"
"1" + "2"                    // => "12"

Cuando los valores de ambos operandos son números, o ambos son cadenas, entonces es obvio lo que hace el operador +. En cualquier otro caso, sin embargo, es necesaria la conversión de tipos, y la operación a realizar depende de la conversión realizada. Las reglas de conversión de + dan prioridad a la concatenación de cadenas: si alguno de los operandos es una cadena o un objeto que se convierte en cadena, el otro operando se convierte en cadena y se realiza la concatenación. La suma sólo se realiza si ninguno de los operandos es una cadena.

Técnicamente, el operador + se comporta así:

  • Si alguno de sus valores operandos es un objeto, lo convierte en primitivo mediante el algoritmo de conversión de objeto a primitivo descrito en§3.9.3. Los objetos fecha se convierten mediante su método toString() , y todos los demás objetos se convierten mediante valueOf(), si ese método devuelve un valor primitivo. Sin embargo, la mayoría de los objetos no tienen un métodovalueOf() útil, por lo que también se convierten mediante toString().

  • Tras la conversión de objeto a primitivo, si alguno de los dos operandos es una cadena, el otro se convierte en cadena y se realiza la concatenación.

  • En caso contrario, ambos operandos se convierten en números (o en NaN) y se realiza la suma.

He aquí algunos ejemplos:

1 + 2         // => 3: addition
"1" + "2"     // => "12": concatenation
"1" + 2       // => "12": concatenation after number-to-string
1 + {}        // => "1[object Object]": concatenation after object-to-string
true + true   // => 2: addition after boolean-to-number
2 + null      // => 2: addition after null converts to 0
2 + undefined // => NaN: addition after undefined converts to NaN

Por último, es importante tener en cuenta que cuando el operador + se utiliza con cadenas y números, puede no ser asociativo. Es decir, el resultado puede depender del orden en que se realicen las operaciones.

Por ejemplo:

1 + 2 + " blind mice"    // => "3 blind mice"
1 + (2 + " blind mice")  // => "12 blind mice"

La primera línea no tiene paréntesis, y el operador + tiene asociatividad de izquierda a derecha, por lo que los dos números se suman primero, y su suma se concatena con la cadena. En la segunda línea, los paréntesis alteran este orden de operaciones: el número 2 se concatena con la cadena para producir una nueva cadena. A continuación, el número 1 se concatena con la nueva cadena para producir el resultado final.

4.8.2 Operadores aritméticos unarios

Los operadores unarios modifican el valor de un único operando para producir un nuevo valor. En JavaScript, todos los operadores unarios tienen precedencia alta y son asociativos a la derecha. Todos los operadores aritméticos unarios descritos en esta sección (+, -, ++, y --) convierten su único operando en un número, si es necesario. Ten en cuenta que los caracteres de puntuación + y - se utilizan como operadores unarios y binarios.

Los operadores aritméticos unarios son los siguientes:

Unario más (+)

El operador unario más convierte su operando en un número (o en NaN) y devuelve ese valor convertido. Cuando se utiliza con un operando que ya es un número, no hace nada. Este operador no se puede utilizar con valores BigInt, ya que no se pueden convertir en números normales.

Menos unario (-)

Cuando se utiliza - como operador unario, convierte su operando en un número, si es necesario, y luego cambia el signo del resultado.

Incremento (++)

El operador ++ incrementa (es decir, suma 1) su único operando, que debe ser un lvalor (una variable, un elemento de una matriz o una propiedad de un objeto). El operador convierte su operando en un número, le suma 1 y asigna el valor incrementado a la variable, elemento o propiedad.

El valor de retorno del operador ++ depende de su posición respecto al operando. Cuando se utiliza antes del operando, lo que se conoce como operador preincremento, incrementa el operando y se evalúa con el valor incrementado de dicho operando. Si se utiliza después del operando, lo que se conoce como operador postincremento, incrementa su operando pero se evalúa con el valor no incrementado de dicho operando. Observa la diferencia entre estas dos líneas de código:

let i = 1, j = ++i;    // i and j are both 2
let n = 1, m = n++;    // n is 2, m is 1

Ten en cuenta que la expresión x++ no siempre es igual ax=x+1. El operador ++ nunca realiza una concatenación de cadenas: siempre convierte su operando en un número y lo incrementa. Si x es la cadena "1", ++x es el número 2, pero x+1 es la cadena "11".

Ten en cuenta también que, debido a la inserción automática de punto y coma de JavaScript, no puedes insertar un salto de línea entre el operador de postincremento y el operando que lo precede. Si lo haces, JavaScript tratará el operando como una sentencia completa por sí misma e insertará un punto y coma antes de él.

Este operador, tanto en su forma de preincremento como de postincremento, se utiliza normalmente para incrementar un contador que controla un bucle for (§5.4.3).

Disminución (--)

El operador -- espera un operando lvalor. Convierte el valor del operando en un número, resta 1 y asigna el valor decrementado de nuevo al operando. Al igual que el operador ++, el valor de retorno de -- depende de su posición respecto al operando. Si se utiliza antes del operando, decrementa y devuelve el valor decrementado. Si se utiliza después del operando, decrementa el operando pero devuelve el valor sin decrementar. Si se utiliza después del operando, no se permite ningún salto de línea entre el operando y el operador.

4.8.3 Operadores bit a bit

Los operadores bit a bit de realizan una manipulación de bajo nivel de los bits en la representación binaria de los números. Aunque no realizan operaciones aritméticas tradicionales, aquí se clasifican como operadores aritméticos porque operan sobre operandos numéricos y devuelven un valor numérico. Cuatro de estos operadores realizan álgebra booleana sobre los bits individuales de los operandos, comportándose como si cada bit de cada operando fuera un valor booleano (1=verdadero, 0=falso). Los otros tres operadores bit a bit se utilizan para desplazar bits a izquierda y derecha. Estos operadores no se utilizan habitualmente en la programación en JavaScript, y si no estás familiarizado con la representación binaria de los enteros, incluida la representación en complemento a dos de los enteros negativos, probablemente puedas saltarte esta sección.

Los operadores bit a bit esperan operandos enteros y se comportan como si esos valores estuvieran representados como enteros de 32 bits en lugar de valores de coma flotante de 64 bits. Estos operadores convierten sus operandos en números, si es necesario, y luego coaccionan los valores numéricos a enteros de 32 bits eliminando cualquier parte fraccionaria y cualquier bit más allá del 32. Los operadores de desplazamiento requieren un operando a la derecha entre 0 y 31. Después de convertir este operando a un entero de 32 bits sin signo, eliminan cualquier bit más allá del 5, lo que produce un número en el rango apropiado. Sorprendentemente, NaN, Infinity, y -Infinity se convierten todos en 0 cuando se utilizan como operandos de estos operadores bit a bit.

Todos estos operadores bit a bit, excepto >>>, pueden utilizarse con operandos numéricos normales o con operandos BigInt (véase §3.2.5).

Bitwise AND (&)

El operador & realiza una operación booleana AND en cada bit de sus argumentos enteros. Un bit se activa en el resultado sólo si el bit correspondiente está activado en ambos operandos. Por ejemplo, 0x1234 & 0x00FF se evalúa como 0x0034.

Bitwise OR (|)

El operador | realiza una operación booleana OR en cada bit de sus argumentos enteros. Un bit se activa en el resultado si el bit correspondiente está activado en uno o ambos operandos. Por ejemplo, 0x1234 | 0x00FF se evalúa como 0x12FF.

XOR Bitwise (^)

El operador ^ realiza una operación booleana OR exclusiva en cada bit de sus argumentos enteros. O exclusivo significa que o bien el operando uno es true o bien el operando dos es true, pero no ambos. Se activa un bit en el resultado de esta operación si se activa el bit correspondiente en uno (pero no en ambos) de los dos operandos. Por ejemplo, 0xFF00 ^ 0xF0F0 se evalúa como 0x0FF0.

Bitwise NOT (~)

El operador ~ es un operador unario que aparece delante de su único operando entero. Funciona invirtiendo todos los bits del operando. Debido a la forma en que se representan los enteros con signo en JavaScript, aplicar el operador ~ a un valor equivale a cambiar su signo y restarle 1. Por ejemplo, ~0x0Fse evalúa como 0xFFFFFFF0, o -16.

Desplazamiento a la izquierda (<<)

El operador << desplaza todos los bits de su primer operando hacia la izquierda el número de posiciones especificado en el segundo operando, que debe ser un número entero entre 0 y 31. Por ejemplo, en la operación a << 1, el primer bit (el bit de unos) de a se convierte en el segundo bit (el bit de dos), el segundo bit de a se convierte en el tercero, etc. Se utiliza un cero para el nuevo primer bit, y se pierde el valor del bit 32. Desplazar un valor una posición a la izquierda equivale a multiplicar por 2, desplazarlo dos posiciones equivale a multiplicar por 4, y así sucesivamente. Por ejemplo, 7 << 2 se evalúa como 28.

Desplazamiento a la derecha con signo (>>)

El operador >> desplaza todos los bits de su primer operando hacia la derecha el número de posiciones especificado en el segundo operando (un entero entre 0 y 31). Los bits desplazados a la derecha se pierden. Los bits rellenados a la izquierda dependen del bit de signo del operando original, para preservar el signo del resultado. Si el primer operando es positivo, el resultado tiene ceros en los bits altos; si el primer operando es negativo, el resultado tiene unos en los bits altos. Desplazar un valor positivo un lugar a la derecha equivale a dividir por 2 (descartando el resto), desplazar dos lugares a la derecha equivale a dividir enteros por 4, y así sucesivamente. 7 >> 1 se evalúa a 3, por ejemplo, pero ten en cuenta que y −7 >> 1 se evalúa a -4.

Desplazamiento a la derecha con relleno cero (>>>)

El operador >>> es igual que el operador >>, salvo que los bits desplazados a la izquierda son siempre cero, independientemente del signo del primer operando. Esto es útil cuando quieres tratar valores de 32 bits con signo como si fueran enteros sin signo. −1 >> 4 se evalúa como -1, pero −1 >>> 4 se evalúa como 0x0FFFFFFF, por ejemplo. Éste es el único de los operadores bit a bit de JavaScript que no puede utilizarse con valores BigInt. BigInt no representa los números negativos poniendo el bit alto como hacen los enteros de 32 bits, y este operador sólo tiene sentido para esa representación particular en complemento a dos.

4.9 Expresiones relacionales

Esta sección describe los operadores relacionales de JavaScript. Estos operadores comprueban si existe una relación (como "igual a", "menor que" o "propiedad de") entre dos valores y devuelven true o falsedependiendo de si existe esa relación. Las expresiones relacionales siempre se evalúan con un valor booleano, y ese valor se utiliza a menudo para controlar el flujo de ejecución del programa en las sentencias if, while y for(consulta el Capítulo 5). Las subsecciones siguientes documentan los operadores de igualdad y desigualdad, los operadores de comparación y los otros dos operadores relacionales de JavaScript, in y instanceof.

4.9.1 Operadores de igualdad y desigualdad

Los operadores == y === comprueban si dos valores son iguales, utilizando dos definiciones diferentes de igualdad. Ambos operadores aceptan operandos de cualquier tipo, y ambos devuelven true si sus operandos son iguales y false si son diferentes. El operador === se conoce como operador de igualdad estricta (o a veces operador de identidad), y comprueba si sus dos operandos son "idénticos" utilizando una definición estricta de igualdad. El operador == se conoce como operador de igualdad; comprueba si sus dos operandos son "iguales" utilizando una definición más relajada de igualdad que permite conversiones de tipo.

Los operadores != y !== comprueban el opuesto exacto de los operadores ==y ===. El operador de desigualdad != devuelve false si dos valores son iguales entre sí según == y devuelve trueen caso contrario. El operador !== devuelve false si dos valores son estrictamente iguales y true en caso contrario. Como verás en §4.10, el operador ! calcula la operación booleana NO. Esto facilita recordar que != y !== significan "no igual a" y "no estrictamente igual a".

Como se menciona en §3.8, los objetos de JavaScript se comparan por referencia, no por valor. Un objeto es igual a sí mismo, pero no a cualquier otro objeto. Si dos objetos distintos tienen el mismo número de propiedades, con los mismos nombres y valores, siguen sin ser iguales. Del mismo modo, dos matrices que tienen los mismos elementos en el mismo orden no son iguales entre sí.

Igualdad estricta

El operador de igualdad estricta === evalúa sus operandos y luego compara los dos valores de la siguiente manera, sin realizar ninguna conversión de tipos:

  • Si los dos valores tienen tipos diferentes, no son iguales.

  • Si ambos valores son null o ambos valores son undefined, son iguales.

  • Si ambos valores son el valor booleano true o ambos son el valor booleano false, son iguales.

  • Si uno o ambos valores son NaN, no son iguales. (Esto es sorprendente, pero el valor NaN nunca es igual a ningún otro valor, ¡incluido él mismo! Para comprobar si un valor x es NaN, utiliza x !== x, o la función global isNaN() ).

  • Si ambos valores son números y tienen el mismo valor, son iguales. Si un valor es 0 y el otro es -0, también son iguales.

  • Si ambos valores son cadenas y contienen exactamente los mismos valores de 16 bits (véase la barra lateral en §3.3) en las mismas posiciones, son iguales. Si las cadenas difieren en longitud o contenido, no son iguales. Dos cadenas pueden tener el mismo significado y el mismo aspecto visual, pero estar codificadas con secuencias diferentes de valores de 16 bits. JavaScript no realiza ninguna normalización Unicode, y un par de cadenas como éste no se considera igual para los operadores === o ==.

  • Si ambos valores se refieren al mismo objeto, matriz o función, son iguales. Si se refieren a objetos diferentes, no son iguales, aunque ambos objetos tengan propiedades idénticas.

Igualdad con conversión de tipos

El operador de igualdad == es como el operador de igualdad estricta, pero es menos estricto. Si los valores de los dos operandos no son del mismo tipo, intenta algunas conversiones de tipo y vuelve a intentar la comparación:

  • Si los dos valores tienen el mismo tipo, comprueba su igualdad estricta como se ha descrito anteriormente. Si son estrictamente iguales, son iguales. Si no son estrictamente iguales, no son iguales.

  • Si los dos valores no tienen el mismo tipo, el operador == puede considerarlos iguales. Utiliza las siguientes reglas y conversiones de tipo para comprobar la igualdad:

    • Si un valor es null y el otro es undefined, son iguales.

    • Si un valor es un número y el otro es una cadena, convierte la cadena en un número y vuelve a intentar la comparación utilizando el valor convertido.

    • Si alguno de los valores es true, conviértelo en 1 y vuelve a intentar la comparación. Si alguno de los valores es false, conviértelo en 0 y vuelve a intentar la comparación.

    • Si un valor es un objeto y el otro es un número o una cadena, convierte el objeto en una primitiva utilizando el algoritmo descrito en§3.9.3 y vuelve a intentar la comparación. Un objeto se convierte en un valor primitivo mediante su método toString() o su método valueOf(). Las clases incorporadas del núcleo de JavaScript intentan la conversión valueOf()antes que la conversión toString(), excepto la clase Fecha, que realiza la conversión toString().

    • Cualquier otra combinación de valores no es igual.

Como ejemplo de comprobación de la igualdad, considera la comparación:

"1" == true  // => true

Esta expresión se evalúa como true, lo que indica que estos valores de aspecto tan diferente son en realidad iguales. El valor booleano true se convierte primero en el número 1, y se vuelve a realizar la comparación. A continuación, la cadena "1" se convierte en el número 1. Como ahora ambos valores son iguales, la comparación devuelve true.

4.9.2 Operadores de comparación

Los operadores de comparación comprueban el orden relativo (numérico o alfabético) de sus dos operandos:

Menos de (<)

El operador < evalúa a true si su primer operando es menor que su segundo operando; en caso contrario, evalúa a false.

Mayor que (>)

El operador > evalúa a true si su primer operando es mayor que su segundo operando; en caso contrario, evalúa a false.

Menor o igual (<=)

El operador <= evalúa a true si su primer operando es menor o igual que su segundo operando; en caso contrario, evalúa a false.

Mayor o igual (>=)

El operador >= evalúa a true si su primer operando es mayor o igual que su segundo operando; en caso contrario, evalúa afalse.

Los operandos de estos operadores de comparación pueden ser de cualquier tipo. Sin embargo, la comparación sólo puede realizarse con números y cadenas, por lo que los operandos que no sean números o cadenas se convierten.

La comparación y la conversión se producen del siguiente modo:

  • Si cualquiera de los operandos se evalúa como un objeto, ese objeto se convierte en un valor primitivo, como se describe al final de §3.9.3; si su métodovalueOf() devuelve un valor primitivo, se utiliza ese valor. En caso contrario, se utiliza el valor devuelto por su método toString().

  • Si, después de cualquier conversión necesaria de objeto a primitivo, ambos operandos son cadenas, las dos cadenas se comparan, utilizando el orden alfabético, donde "orden alfabético" se define por el orden numérico de los valores Unicode de 16 bits que componen las cadenas.

  • Si, tras la conversión de objeto a primitivo, al menos un operando no es una cadena, ambos operandos se convierten en números y se comparan numéricamente. 0 y -0 se consideran iguales. Infinity es mayor que cualquier número que no sea él mismo, y -Infinity es menor que cualquier número que no sea él mismo. Si cualquiera de los dos operandos es (o se convierte en) NaN, el operador de comparación siempre devuelve false. Aunque los operadores aritméticos no permiten mezclar valores BigInt con números normales, los operadores de comparación sí permiten realizar comparaciones entre números y BigInts.

Recuerda que las cadenas de JavaScript son secuencias de valores enteros de 16 bits, y que la comparación de cadenas es sólo una comparación numérica de los valores de las dos cadenas. El orden de codificación numérica definido por Unicode puede no coincidir con el orden de cotejo tradicional utilizado en un idioma o localización concretos. Ten en cuenta, en particular, que la comparación de cadenas distingue entre mayúsculas y minúsculas, y que todas las letras ASCII mayúsculas son "menos que" todas las letras ASCII minúsculas. Esta regla puede provocar resultados confusos si no te lo esperas. Por ejemplo, según el operador <, la cadena "Zoo" está antes que la cadena "oso hormiguero".

Para un algoritmo de comparación de cadenas más robusto, prueba el métodoString.localeCompare(), que también tiene en cuenta las definiciones del orden alfabético específicas de cada localidad. Para comparar sin distinguir mayúsculas de minúsculas, puedes convertir las cadenas a minúsculas o mayúsculas utilizando String.toLowerCase() o String.toUpperCase(). Y, para una herramienta de comparación de cadenas más general y mejor localizada, utiliza la clase Intl.Collator descrita en §11.7.3.

Tanto el operador + como los operadores de comparación se comportan de forma diferente para los operandos numéricos y los de cadena. + favorece a las cadenas: realiza la concatenación si cualquiera de los operandos es una cadena. Los operadores de comparación favorecen a los números y sólo realizan la comparación de cadenas si ambos operandos son cadenas:

1 + 2        // => 3: addition.
"1" + "2"    // => "12": concatenation.
"1" + 2      // => "12": 2 is converted to "2".
11 < 3       // => false: numeric comparison.
"11" < "3"   // => true: string comparison.
"11" < 3     // => false: numeric comparison, "11" converted to 11.
"one" < 3    // => false: numeric comparison, "one" converted to NaN.

Por último, ten en cuenta que los operadores <= (menor que o igual) y >= (mayor que o igual) no se basan en los operadores de igualdad o igualdad estricta para determinar si dos valores son "iguales". En su lugar, el operador menor que o igual se define simplemente como "no mayor que", y el operador mayor que o igual se define como "no menor que". La única excepción se produce cuando alguno de los operandos es (o se convierte en) NaN, en cuyo caso, los cuatro operadores de comparación devuelven false.

4.9.3 El operador in

El operador in espera un operando del lado izquierdo que sea una cadena, un símbolo o un valor que pueda convertirse en cadena. Espera un operando del lado derecho que sea un objeto. Se evalúa como true si el valor del lado izquierdo es el nombre de una propiedad del objeto del lado derecho. Por ejemplo

let point = {x: 1, y: 1};  // Define an object
"x" in point               // => true: object has property named "x"
"z" in point               // => false: object has no "z" property.
"toString" in point        // => true: object inherits toString method

let data = [7,8,9];        // An array with elements (indices) 0, 1, and 2
"0" in data                // => true: array has an element "0"
1 in data                  // => true: numbers are converted to strings
3 in data                  // => false: no element 3

4.9.4 El operador instanceof

El operador instanceof espera un operando del lado izquierdo que sea un objeto y un operando del lado derecho que identifique una clase de objetos. El operador se evalúa como true si el objeto del lado izquierdo es una instancia de la clase del lado derecho y se evalúa como false en caso contrario. En el capítulo 9se explica que, en JavaScript, las clases de objetos se definen mediante la función constructora que los inicializa. Por tanto, el operando del lado derecho de instanceof debe ser una función. Aquí tienes algunos ejemplos:

let d = new Date();  // Create a new object with the Date() constructor
d instanceof Date    // => true: d was created with Date()
d instanceof Object  // => true: all objects are instances of Object
d instanceof Number  // => false: d is not a Number object
let a = [1, 2, 3];   // Create an array with array literal syntax
a instanceof Array   // => true: a is an array
a instanceof Object  // => true: all arrays are objects
a instanceof RegExp  // => false: arrays are not regular expressions

Ten en cuenta que todos los objetos son instancias de Object. instanceof tiene en cuenta las "superclases" a la hora de decidir si un objeto es una instancia de una clase. Si el operando del lado izquierdo de instanceof no es un objeto,instanceof devuelve false. Si el operando del lado derecho no es una clase de objetos, lanza un TypeError.

Para entender cómo funciona el operador instanceof, debes comprender la "cadena de prototipos". Éste es el mecanismo de herencia de JavaScript, y se describe en §6.3.2. Para evaluar la expresión o instanceof f, JavaScript evalúa f.prototype, y luego busca ese valor en la cadena de prototipos de o. Si lo encuentra, entonces o es una instancia de f (o de una subclase de f) y el operador devuelve true. Si f.prototype no es uno de los valores de la cadena prototipo de o, entonces o no es una instancia de f yinstanceof devuelve false.

4.10 Expresiones lógicas

Los operadores lógicos && , ||, y ! realizan álgebra booleana y suelen utilizarse junto con los operadores relacionales para combinar dos expresiones relacionales en una expresión más compleja. Estos operadores se describen en las subsecciones siguientes. Para entenderlos bien, quizá quieras repasar el concepto de valores "verdaderos" y "falsos" introducido en §3.4.

4.10.1 Y lógico (&&)

El operador && puede entenderse en tres niveles diferentes. En el nivel más sencillo, cuando se utiliza con operandos booleanos, &&realiza la operación booleana AND sobre los dos valores: devuelve truesi y sólo si tanto su primer operando como su segundo operando sontrue. Si uno o ambos operandos son false, devuelve false.

&& se utiliza a menudo como conjunción para unir dos expresiones relacionales:

x === 0 && y === 0   // true if, and only if, x and y are both 0

Las expresiones relacionales siempre se evalúan a true o false, por lo que cuando se utilizan así, el propio operador && devuelve true ofalse. Los operadores relacionales tienen mayor precedencia que &&(y ||), por lo que expresiones como éstas pueden escribirse sin paréntesis.

Pero && no requiere que sus operandos sean valores booleanos. Recuerda que todos los valores de JavaScript son "verdaderos" o "falsos" (para más detalles, consulta §3.4 ). Los valores falsos son false, null,undefined, 0, -0, NaN y "". Todos los demás valores, incluidos todos los objetos, son verdaderos). El segundo nivel en el que se puede entender && es como un operador booleano AND para valores verdaderos y falsos. Si ambos operandos son verdaderos, el operador devuelve un valor verdadero; en caso contrario, uno o ambos operandos deben ser falsos, y el operador devuelve un valor falso. En JavaScript, cualquier expresión o sentencia que espere un valor booleano funcionará con un valor verdadero o falso, por lo que el hecho de que && no siempre devuelva true o false no causa problemas prácticos.

Observa que esta descripción dice que el operador devuelve "un valor verdadero" o "un valor falso", pero no especifica cuál es ese valor. Para ello, necesitamos describir && en el tercer y último nivel. Este operador comienza evaluando su primer operando, la expresión de su izquierda. Si el valor de la izquierda es falso, el valor de toda la expresión también debe ser falso, por lo que && simplemente devuelve el valor de la izquierda y ni siquiera evalúa la expresión de la derecha.

Por otra parte, si el valor de la izquierda es verdadero, el valor total de la expresión depende del valor de la derecha. Si el valor de la derecha es verdadero, el valor total debe ser verdadero, y si el valor de la derecha es falso, el valor total debe ser falso. Por tanto, cuando el valor de la izquierda es verdadero, el operador&& evalúa y devuelve el valor de la derecha:

let o = {x: 1};
let p = null;
o && o.x     // => 1: o is truthy, so return value of o.x
p && p.x     // => null: p is falsy, so return it and don't evaluate p.x

Es importante entender que && puede o no evaluar su operando derecho. En este ejemplo de código, la variable p se establece ennull, y la expresión p.x, si se evaluara, provocaría un TypeError. Pero el código utiliza && de forma idiomática, de modo quep.x sólo se evalúa si p es verdadero, y no null o undefined.

El comportamiento de && se denomina a veces cortocircuito, y a veces puedes ver código que aprovecha a propósito este comportamiento para ejecutar código condicionalmente. Por ejemplo, las dos líneas de código JavaScript siguientes tienen efectos equivalentes:

if (a === b) stop();   // Invoke stop() only if a === b
(a === b) && stop();   // This does the same thing

En general, debes tener cuidado siempre que escribas una expresión con efectos secundarios (asignaciones, incrementos, decrementos o invocaciones de funciones) en el lado derecho de &&. Que se produzcan esos efectos secundarios depende del valor del lado izquierdo.

A pesar de la forma un tanto compleja en que funciona realmente este operador, lo más habitual es utilizarlo como un simple operador del álgebra booleana que funciona con valores verdaderos y falsos .

4.10.2 OR lógico (||)

El operador || realiza la operación booleana OR sobre sus dos operandos. Si uno o ambos operandos son verdaderos, devuelve un valor verdadero. Si ambos operandos son falsos, devuelve un valor falso.

Aunque el operador || se suele utilizar simplemente como operador booleano OR, al igual que el operador &&, tiene un comportamiento más complejo. Comienza evaluando su primer operando, la expresión de su izquierda. Si el valor de este primer operando es verdadero, se cortocircuita y devuelve ese valor verdadero sin evaluar nunca la expresión de la derecha. Si, por el contrario, el valor del primer operando es falso, entonces || evalúa su segundo operando y devuelve el valor de esa expresión.

Al igual que con el operador &&, debes evitar los operandos del lado derecho que incluyan efectos secundarios, a menos que quieras utilizar a propósito el hecho de que la expresión del lado derecho no pueda evaluarse.

Un uso idiomático de este operador es seleccionar el primer valor verdadero de un conjunto de alternativas:

// If maxWidth is truthy, use that. Otherwise, look for a value in
// the preferences object. If that is not truthy, use a hardcoded constant.
let max = maxWidth || preferences.maxWidth || 500;

Ten en cuenta que si 0 es un valor legal para maxWidth, este código no funcionará correctamente, ya que 0 es un valor falso. Consulta el operador ??(§4.13.2) como alternativa.

Antes de ES6, este modismo se utilizaba a menudo en las funciones para proporcionar valores por defecto a los parámetros:

// Copy the properties of o to p, and return p
function copy(o, p) {
    p = p || {};  // If no object passed for p, use a newly created object.
    // function body goes here
}

En ES6 y posteriores, sin embargo, este truco ya no es necesario porque el valor del parámetro por defecto podría escribirse simplemente en la propia definición de la función: function copy(o, p={}) { ... }.

4.10.3 Lógico NOT (!)

El operador ! es un operador unario; se coloca delante de un único operando. Su finalidad es invertir el valor booleano de su operando. Por ejemplo, si x es verdadero, !x se evalúa como false. Si x es falso, entonces !x es true.

A diferencia de los operadores && y ||, el operador ! convierte su operando en un valor booleano (utilizando las reglas descritas en elCapítulo 3) antes de invertir el valor convertido. Esto significa que! siempre devuelve true o false y que puedes convertir cualquier valor x a su valor booleano equivalente aplicando dos veces este operador: !!x (véase §3.9.2).

Como operador unario, ! tiene alta precedencia y se vincula fuertemente. Si quieres invertir el valor de una expresión como p && q, debes utilizar paréntesis: !(p && q). Merece la pena señalar aquí dos leyes del álgebra booleana que podemos expresar utilizando sintaxis de JavaScript:

// DeMorgan's Laws
!(p && q) === (!p || !q)  // => true: for all values of p and q
!(p || q) === (!p && !q)  // => true: for all values of p and q

4.11 Expresiones de asignación

JavaScript utiliza el operador = para asignar un valor a una variable o propiedad. Por ejemplo

i = 0;     // Set the variable i to 0.
o.x = 1;   // Set the property x of object o to 1.

El operador = espera que su operando izquierdo sea un lvalue: una variable o propiedad de objeto (o elemento de matriz). Espera que su operando derecho sea un valor arbitrario de cualquier tipo. El valor de una expresión de asignación es el valor del operando derecho. Como efecto secundario, el operador = asigna el valor de la derecha a la variable o propiedad de la izquierda, de modo que las futuras referencias a la variable o propiedad se evalúen al valor.

Aunque las expresiones de asignación suelen ser bastante sencillas, a veces puedes ver que el valor de una expresión de asignación se utiliza como parte de una expresión mayor. Por ejemplo, puedes asignar y comprobar un valor en la misma expresión con un código como éste:

(a = b) === 0

Si lo haces, ¡asegúrate de que tienes clara la diferencia entre los operadores =y ===! Ten en cuenta que = tiene una precedencia muy baja, y que los paréntesis suelen ser necesarios cuando el valor de una asignación se va a utilizar en una expresión mayor.

El operador de asignación tiene asociatividad de derecha a izquierda, lo que significa que cuando aparecen varios operadores de asignación en una expresión, se evalúan de derecha a izquierda. Por tanto, puedes escribir código como éste para asignar un único valor a varias variables:

i = j = k = 0;       // Initialize 3 variables to 0

4.11.1 Asignación con operación

Además de, el operador de asignación normal =, JavaScript admite otros operadores de asignación que proporcionan atajos combinando la asignación con alguna otra operación. Por ejemplo, el operador+= realiza una suma y una asignación. La siguiente expresión:

total += salesTax;

es equivalente a ésta:

total = total + salesTax;

Como era de esperar, el operador += funciona para números o cadenas. Para los operandos numéricos, realiza la suma y la asignación; para los operandos de cadena, realiza la concatenación y la asignación.

Otros operadores similares son -=, *=, &=, etc.La Tabla 4-2 los enumera todos.

Tabla 4-2. Operadores de asignación
Operario Ejemplo Equivalente

+=

a += b

a = a + b

-=

a -= b

a = a - b

*=

a *= b

a = a * b

/=

a /= b

a = a / b

%=

a %= b

a = a % b

**=

a **= b

a = a ** b

<<=

a <<= b

a = a << b

>>=

a >>= b

a = a >> b

>>>=

a >>>= b

a = a >>> b

&=

a &= b

a = a & b

|=

a |= b

a = a | b

^=

a ^= b

a = a ^ b

En la mayoría de los casos, la expresión:

a op= b

donde op es un operador, es equivalente a la expresión

a = a op b

En la primera línea, la expresión a se evalúa una vez. En la segunda, se evalúa dos veces. Los dos casos sólo diferirán si a incluye efectos secundarios como una llamada a una función o un operador de incremento. Las dos asignaciones siguientes, por ejemplo, no son iguales:

data[i++] *= 2;
data[i++] = data[i++] * 2;

4.12 Expresiones de evaluación

Como muchos lenguajes interpretados, JavaScript tiene la capacidad de interpretar cadenas de código fuente JavaScript, evaluándolas para producir un valor. JavaScript hace esto con la función global eval():

eval("3+2")    // => 5

La evaluación dinámica de cadenas de código fuente es una potente característica del lenguaje que casi nunca es necesaria en la práctica. Si te encuentras utilizando eval(), deberías pensar detenidamente si realmente necesitas utilizarla. En particular, eval() puede ser un agujero de seguridad, y nunca debes pasar ninguna cadena derivada de la entrada del usuario aeval(). Con un lenguaje tan complicado como JavaScript, no hay forma de sanear la entrada del usuario para que sea seguro utilizarlo con eval(). Debido a estos problemas de seguridad, algunos servidores web utilizan la cabecera HTTP "Content-Security-Policy" para desactivar eval() para todo un sitio web.

Las subsecciones siguientes explican el uso básico de eval() y explican dos versiones restringidas del mismo que tienen menos impacto en el optimizador.

4.12.1 eval()

eval() espera un argumento. Si pasas cualquier valor que no sea una cadena, simplemente devuelve ese valor. Si pasas una cadena, intentará analizarla como código JavaScript, lanzando un error de sintaxis si falla. Si analiza correctamente la cadena, evalúa el código y devuelve el valor de la última expresión o sentencia de la cadena o undefined si la última expresión o sentencia no tenía valor. Si la cadena evaluada lanza una excepción, esa excepción se propaga desde la llamada a eval().

La clave de eval() (cuando se invoca así) es que utiliza el entorno de variables del código que lo llama. Es decir, busca los valores de las variables y define nuevas variables y funciones del mismo modo que lo hace el código local. Si una función define una variable local x y luego llama a eval("x"), obtendrá el valor de la variable local. Si llama a eval("x=1"), cambiará el valor de la variable local. Y si la función llama a eval("var y = 3;"), declara una nueva variable local y. En cambio, si la cadena evaluada utiliza let o const, la variable o constante declarada será local a la evaluación y no estará definida en el entorno de llamada.

Del mismo modo, una función puede declarar una función local con un código como éste:

eval("function f() { return x+1; }");

Si llamas a eval() desde código de nivel superior, opera sobre variables globales y funciones globales, por supuesto.

Ten en cuenta que la cadena de código que pases a eval() debe tener sentido sintáctico por sí misma: no puedes utilizarla para pegar fragmentos de código en una función. No tiene sentido escribir eval("return;"), por ejemplo, porque return sólo es legal dentro de funciones, y el hecho de que la cadena evaluada utilice el mismo entorno de variables que la función que la llama no la convierte en parte de esa función. Si tu cadena tiene sentido como secuencia de comandos independiente (aunque sea muy corta, como x=0 ), es legal pasarla a eval(). De lo contrario, eval() lanzará un SyntaxError.

4.12.2 Eval() global

Es la capacidad de eval() de cambiar variables locales lo que resulta tan problemático para los optimizadores de JavaScript. Sin embargo, como solución, los intérpretes simplemente optimizan menos cualquier función que llame aeval(). Pero, ¿qué debe hacer un intérprete de JavaScript si un script define un alias para eval() y luego llama a esa función con otro nombre? La especificación de JavaScript declara que cuando eval()es invocado por cualquier nombre que no sea "eval", debe evaluar la cadena como si fuera código global de nivel superior. El código evaluado puede definir nuevas variables globales o funciones globales, y puede establecer variables globales, pero no utilizará ni modificará ninguna variable local de la función invocadora, y por tanto no interferirá con las optimizaciones locales.

Una "eval directa" es una llamada a la función eval()con una expresión que utiliza el nombre exacto y no cualificado "eval" (que empieza a parecer una palabra reservada). Las llamadas directas a eval() utilizan el entorno de variables del contexto de llamada. Cualquier otra llamada -una llamada indirecta- utiliza el objeto global como entorno de variables y no puede leer, escribir ni definir variables o funciones locales. (Tanto las llamadas directas como las indirectas sólo pueden definir nuevas variables con var. Los usos de let y const dentro de una cadena evaluada crean variables y constantes que son locales a la evaluación y no alteran el entorno de llamada ni el global).

El código siguiente lo demuestra:

const geval = eval;               // Using another name does a global eval
let x = "global", y = "global";   // Two global variables
function f() {                    // This function does a local eval
    let x = "local";              // Define a local variable
    eval("x += 'changed';");      // Direct eval sets local variable
    return x;                     // Return changed local variable
}
function g() {                    // This function does a global eval
    let y = "local";              // A local variable
    geval("y += 'changed';");     // Indirect eval sets global variable
    return y;                     // Return unchanged local variable
}
console.log(f(), x); // Local variable changed: prints "localchanged global":
console.log(g(), y); // Global variable changed: prints "local globalchanged":

Observa que la capacidad de hacer una eval global no es sólo una adaptación a las necesidades del optimizador; en realidad es una función tremendamente útil que te permite ejecutar cadenas de código como si fueran scripts independientes de nivel superior. Como se indicó alprincipio de esta sección, es raro que realmente necesites evaluar una cadena de código. Pero si te resulta necesario, es más probable que quieras hacer una eval global que una eval local.

4.12.3 Eval() estricto

El modo estricto de (véase §5.6.3) impone más restricciones al comportamiento de la función eval() e incluso al uso del identificador "eval". Cuando se llama a eval() desde código en modo estricto, o cuando la propia cadena de código a evaluar comienza con una directiva "use strict", entonces eval() realiza una eval local con un entorno de variable privada. Este significa que, en modo estricto, el código evaluado puede consultar y establecer variables locales, pero no puede definir nuevas variables o funciones en el ámbito local.

Además, el modo estricto hace que eval() sea aún más parecido a un operador, convirtiendo "eval" en una palabra reservada. No se te permite sobrescribir la función eval() con un nuevo valor. Y no se te permite declarar una variable, función, parámetro de función o parámetro de bloque de captura con el nombre "eval".

4.13 Operadores Varios

JavaScript admite otros operadores diversos, que se describen en las secciones siguientes.

4.13.1 El operador condicional (?:)

El operador condicional es el único operador ternario (tres operandos) de JavaScript y, en realidad, a veces se denomina operador ternario. Este operador a veces se escribe ?:, aunque no aparece exactamente así en el código. Como este operador tiene tres operandos, el primero va antes del ?, el segundo va entre el ? y el :, y el tercero va después del :. Se utiliza así:

x > 0 ? x : -x     // The absolute value of x

Los operandos del operador condicional pueden ser de cualquier tipo. El primer operando se evalúa e interpreta como un booleano. Si el valor del primer operando es verdadero, se evalúa el segundo operando y se devuelve su valor. En caso contrario, si el primer operando es falso, se evalúa el tercer operando y se devuelve su valor. Sólo se evalúa uno de los dos operandos, nunca los dos.

Aunque puedes conseguir resultados similares utilizando la sentencia if (§5.3.1), el operador ?: a menudo proporciona un atajo práctico. He aquí un uso típico, que comprueba que una variable está definida (y tiene un valor significativo y verdadero) y la utiliza si es así o proporciona un valor por defecto si no:

greeting = "hello " + (username ? username : "there");

Esto es equivalente, pero más compacto, a la siguiente declaración if:

greeting = "hello ";
if (username) {
    greeting += username;
} else {
    greeting += "there";
}

4.13.2 Primera definición (??)

El operador definido en primer lugar ?? evalúa a su primer operando definido: si su operando izquierdo no es null ni undefined, devuelve ese valor. En caso contrario, devuelve el valor del operando derecho. Al igual que los operadores && y ||, ?? es de cortocircuito: sólo evalúa su segundo operando si el primer operando se evalúa comonull o undefined. Si la expresión a no tiene efectos secundarios, entonces la expresión a ?? b es equivalente a:

(a !== null && a !== undefined) ? a : b

?? es una alternativa útil a || (§4.10.2) cuando quieres seleccionar el primer operando definido en lugar del primer operando verdadero. Aunque || es nominalmente un operador lógico OR, también se utiliza idiomáticamente para seleccionar el primer operando no verdadero con código como éste:

// If maxWidth is truthy, use that. Otherwise, look for a value in
// the preferences object. If that is not truthy, use a hardcoded constant.
let max = maxWidth || preferences.maxWidth || 500;

El problema de este uso idiomático es que cero, la cadena vacía y false son valores falsos que pueden ser perfectamente válidos en algunas circunstancias. En este ejemplo de código, si maxWidth es cero, ese valor se ignorará. Pero si cambiamos el operador || por ??, acabamos con una expresión en la que cero es un valor válido:

// If maxWidth is defined, use that. Otherwise, look for a value in
// the preferences object. If that is not defined, use a hardcoded constant.
let max = maxWidth ?? preferences.maxWidth ?? 500;

Aquí tienes más ejemplos que muestran cómo funciona ?? cuando el primer operando es falso. Si ese operando es falso pero está definido, entonces ?? lo devuelve. Sólo cuando el primer operando es "nulo" (es decir, null oundefined) este operador evalúa y devuelve el segundo operando:

let options = { timeout: 0, title: "", verbose: false, n: null };
options.timeout ?? 1000     // => 0: as defined in the object
options.title ?? "Untitled" // => "": as defined in the object
options.verbose ?? true     // => false: as defined in the object
options.quiet ?? false      // => false: property is not defined
options.n ?? 10             // => 10: property is null

Ten en cuenta que las expresiones timeout, title y verbose tendrían aquí valores diferentes si utilizáramos || en lugar de ??.

El operador ?? es similar a los operadores && y ||, pero no tiene mayor precedencia ni menor precedencia que ellos. Si lo utilizas en una expresión con cualquiera de esos operadores, debes utilizar paréntesis explícitos para especificar qué operación quieres realizar primero:

(a ?? b) || c   // ?? first, then ||
a ?? (b || c)   // || first, then ??
a ?? b || c     // SyntaxError: parentheses are required

El operador ?? se define en ES2020 y, a partir de principios de 2020, es compatible con las versiones actuales o beta de los principales navegadores. Este operador se denomina formalmente operador de "coalescencia nula", pero evito ese término porque este operador selecciona uno de sus operandos pero no los "coalesce" de ninguna manera que yo pueda ver.

4.13.3 El operador typeof

typeof es un operador unario que se coloca delante de su único operando, que puede ser de cualquier tipo. Su valor es una cadena que especifica el tipo del operando. La Tabla 4-3 especifica el valor del operador typeofpara cualquier valor de JavaScript.

Tabla 4-3. Valores devueltos por el operador typeof
x typeof x

undefined

"undefined"

null

"object"

true o false

"boolean"

cualquier número o NaN

"number"

cualquier BigInt

"bigint"

cualquier cadena

"string"

cualquier símbolo

"symbol"

cualquier función

"function"

cualquier objeto no funcional

"object"

Puedes utilizar el operador typeof en una expresión como ésta:

// If the value is a string, wrap it in quotes, otherwise, convert
(typeof value === "string") ? "'" + value + "'" : value.toString()

Ten en cuenta que typeof devuelve "objeto" si el valor del operando es null. Si quieres distinguir null de los objetos, tendrás que comprobar explícitamente este valor de caso especial.

Aunque las funciones de JavaScript son un tipo de objeto, el operador typeofconsidera que las funciones son lo suficientemente diferentes como para tener su propio valor de retorno.

Dado que typeof se evalúa como "objeto" para todos los valores de objetos y matrices que no sean funciones, sólo es útil para distinguir los objetos de otros tipos primitivos. Para distinguir una clase de objeto de otra, debes utilizar otras técnicas, como el operadorinstanceof (véase §4.9.4), el atributo class (véase§14.4.3) o la propiedad constructor (véanse§9.2.2 y §14.3).

4.13.4 El Operador Borrar

delete es un operador unario que intenta borrar la propiedad del objeto o el elemento de la matriz especificado como operando. Al igual que los operadores de asignación, incremento y decremento, delete se utiliza normalmente por su efecto secundario de borrado de propiedades y no por el valor que devuelve. Algunos ejemplos:

let o = { x: 1, y: 2}; // Start with an object
delete o.x;            // Delete one of its properties
"x" in o               // => false: the property does not exist anymore

let a = [1,2,3];       // Start with an array
delete a[2];           // Delete the last element of the array
2 in a                 // => false: array element 2 doesn't exist anymore
a.length               // => 3: note that array length doesn't change, though

Ten en cuenta que una propiedad o elemento de matriz eliminado no se limita a establecer el valorundefined. Cuando se borra una propiedad, ésta deja de existir. Intentar leer una propiedad inexistente devuelve undefined, pero puedes comprobar la existencia real de una propiedad con el operador in(§4.9.3). Borrar un elemento de una matriz deja un "hueco" en la matriz y no cambia su longitud. La matriz resultante es dispersa(§7.3).

delete espera que su operando sea un valor l. Si no es un lvalue, el operador no realiza ninguna acción y devuelve true. En caso contrario, deleteintenta borrar el lvalue especificado. delete devuelve true si consigue borrar el lvalue especificado. Sin embargo, no todas las propiedades pueden borrarse: las propiedades no configurables(§14.1) son inmunes al borrado.

En el modo estricto de, delete provoca un error de sintaxis si su operando es un identificador no cualificado, como una variable, una función o un parámetro de función: sólo funciona cuando el operando es una expresión de acceso a una propiedad(§4.4). El modo estricto también especifica quedelete genera un TypeError si se le pide que elimine cualquier propiedad no configurable (es decir, no eliminable). Fuera del modo estricto, no se produce ninguna excepción en estos casos, y delete simplemente devuelve false para indicar que no se ha podido borrar el operando.

Aquí tienes algunos ejemplos de uso del operador delete:

let o = {x: 1, y: 2};
delete o.x;   // Delete one of the object properties; returns true.
typeof o.x;   // Property does not exist; returns "undefined".
delete o.x;   // Delete a nonexistent property; returns true.
delete 1;     // This makes no sense, but it just returns true.
// Can't delete a variable; returns false, or SyntaxError in strict mode.
delete o;
// Undeletable property: returns false, or TypeError in strict mode.
delete Object.prototype;

Volveremos a ver el operador delete en §6.4.

4.13.5 El operador de espera

await fue introducido en ES2017 como una forma de hacer más natural la programación asíncrona en JavaScript. Tendrás que leer el Capítulo 13 para entender este operador. En pocas palabras,await espera un objeto Promise (que representa un cálculo asíncrono) como único operando, y hace que tu programa se comporte como si estuviera esperando a que se complete el cálculo asíncrono (pero lo hace sin bloquearlo realmente, y no impide que se realicen otras operaciones asíncronas al mismo tiempo). El valor del operador await es el valor de cumplimiento del objeto Promesa. Es importante destacar que await sólo es legal dentro de funciones que hayan sido declaradas asíncronas con la palabra clave async. De nuevo, consultael Capítulo 13 para más detalles.

4.13.6 El operador vacío

void es un operador unario que aparece antes de su único operando, que puede ser de cualquier tipo. Este operador es poco habitual y se utiliza con poca frecuencia; evalúa su operando, luego descarta el valor y devuelveundefined. Dado que el valor del operando se descarta, utilizar el operador voidsólo tiene sentido si el operando tiene efectos secundarios.

El operador void es tan oscuro que resulta difícil encontrar un ejemplo práctico de su uso. Un caso sería cuando quieres definir una función que no devuelve nada, pero que también utiliza la sintaxis abreviada de la función flecha (véase §8.1.3), en la que el cuerpo de la función es una única expresión que se evalúa y devuelve. Si evalúas la expresión únicamente por sus efectos secundarios y no quieres devolver su valor, lo más sencillo es utilizar llaves alrededor del cuerpo de la función. Pero, como alternativa, también podrías utilizar el operador void en este caso:

let counter = 0;
const increment = () => void counter++;
increment()   // => undefined
counter       // => 1

4.13.7 El operador coma (,)

El operador comma es un operador binario cuyos operandos pueden ser de cualquier tipo. Evalúa su operando izquierdo, evalúa su operando derecho y, a continuación, devuelve el valor del operando derecho. Así, la línea siguiente:

i=0, j=1, k=2;

evalúa a 2 y es básicamente equivalente a:

i = 0; j = 1; k = 2;

La expresión de la izquierda siempre se evalúa, pero su valor se descarta, lo que significa que sólo tiene sentido utilizar el operador coma cuando la expresión de la izquierda tiene efectos secundarios. La única situación en la que se suele utilizar el operador coma es con un bucle for(§5.4.3) que tiene múltiples variables de bucle:

// The first comma below is part of the syntax of the let statement
// The second comma is the comma operator: it lets us squeeze 2
// expressions (i++ and j--) into a statement (the for loop) that expects 1.
for(let i=0,j=10; i < j; i++,j--) {
    console.log(i+j);
}

4.14 Resumen

Este capítulo abarca una gran variedad de temas, y aquí hay mucho material de referencia que tal vez quieras releer en el futuro a medida que sigas aprendiendo JavaScript. Sin embargo, algunos puntos clave que debes recordar son los siguientes:

  • Las expresiones son las frases de un programa JavaScript.

  • Cualquier expresión puede evaluarse a un valor de JavaScript.

  • Las expresiones también pueden tener efectos secundarios (como la asignación de variables), además de producir un valor.

  • Las expresiones sencillas, como los literales, las referencias a variables y los accesos a propiedades, pueden combinarse con operadores para producir expresiones mayores.

  • JavaScript define operadores para la aritmética, las comparaciones, la lógica booleana, la asignación y la manipulación de bits, junto con algunos operadores varios, incluido el operador condicional ternario.

  • El operador + de JavaScript se utiliza tanto para sumar números como para concatenar cadenas.

  • Los operadores lógicos && y || tienen un comportamiento especial de "cortocircuito" y a veces sólo evalúan uno de sus argumentos. Los modismos comunes de JavaScript requieren que comprendas el comportamiento especial de estos operadores.

Get JavaScript: La Guía Definitiva, 7ª Edición now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.