tutorial - ecmascript vs javascript




¿Puede(a== 1 && a== 2 && a== 3) alguna vez evaluar como verdadero? (18)

JavaScript

a == a +1

En JavaScript, no hay integers sino solo Number s, que se implementan como números de coma flotante de doble precisión.

Significa que si un Número a es lo suficientemente grande, puede considerarse igual a tres enteros consecutivos:

a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
  console.log("Precision loss!");
}

Es cierto que no es exactamente lo que preguntó el entrevistador (no funciona con a=0 ), pero no implica ningún truco con funciones ocultas o sobrecarga del operador.

Otros idiomas

Como referencia, hay soluciones a==1 && a==2 && a==3 en Ruby y Python. Con una ligera modificación, también es posible en Java.

Rubí

Con una costumbre == :

class A
  def ==(o)
    true
  end
end

a = A.new

if a == 1 && a == 2 && a == 3
  puts "Don't do this!"
end

O un creciente a :

def a
  @a ||= 0
  @a += 1
end

if a == 1 && a == 2 && a == 3
  puts "Don't do this!"
end

Pitón

class A:
    def __eq__(self, who_cares):
        return True
a = A()

if a == 1 and a == 2 and a == 3:
    print("Don't do that!")

Java

Es posible modificar el caché de Integer Java:

package stackoverflow;

import java.lang.reflect.Field;

public class IntegerMess
{
    public static void main(String[] args) throws Exception {
        Field valueField = Integer.class.getDeclaredField("value");
        valueField.setAccessible(true);
        valueField.setInt(1, valueField.getInt(42));
        valueField.setInt(2, valueField.getInt(42));
        valueField.setInt(3, valueField.getInt(42));
        valueField.setAccessible(false);

        Integer a = 42;

        if (a.equals(1) && a.equals(2) && a.equals(3)) {
            System.out.println("Bad idea.");
        }
    }
}

Nota para el moderador: resista la necesidad de editar el código o eliminar este aviso. El patrón de espacios en blanco puede ser parte de la pregunta y, por lo tanto, no debe ser manipulado innecesariamente. Si está en el campo "el espacio en blanco es insignificante", debería poder aceptar el código tal como está.

¿Es posible que (a== 1 && a ==2 && a==3) pueda evaluar como true en JavaScript?

Esta es una pregunta de entrevista realizada por una importante empresa de tecnología. Sucedió hace dos semanas, pero todavía estoy tratando de encontrar la respuesta. Sé que nunca escribimos ese código en nuestro trabajo diario, pero tengo curiosidad.


¡Sí, es posible! 😎

»JavaScript

if‌=()=>!0;
var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    document.write("<h1>Yes, it is possible!😎</h1>")
}

El código anterior es una versión corta (gracias a @Forivin por su nota en los comentarios) y el siguiente código es original:

var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    //console.log("Yes, it is possible!😎")
    document.write("<h1>Yes, it is possible!😎</h1>")
}

//--------------------------------------------

function if‌(){return true;}

Si solo ves la parte superior de mi código y lo ejecutas, dices WOW, ¿cómo?

Entonces, creo que es suficiente decir Sí, es posible para alguien que te dijo: Nada es imposible

Truco: Utilicé un carácter oculto después de if para hacer una función cuyo nombre sea similar a if . En JavaScript no podemos anular las palabras clave, así que me vi obligado a usarlo de esta manera. Es falso if , ¡pero te funciona en este caso!

» C #

También escribí una versión de C # ( con una técnica de aumento del valor de la propiedad ):

static int _a;
public static int a => ++_a;

public static void Main()
{
    if(a==1 && a==2 && a==3)
    {
        Console.WriteLine("Yes, it is possible!😎");
    }
}

Demo en vivo


Al anular valueOf una declaración de clase, se puede hacer:

class Thing {
    constructor() {
        this.value = 1;
    }

    valueOf() {
        return this.value++;
    }
}

const a = new Thing();

if(a == 1 && a == 2 && a == 3) {
    console.log(a);
}

Lo que sucede es que valueOf se llama en cada operador de comparación. En el primero, a será igual 1 , en el segundo, a será igual 2 , y así sucesivamente, porque cada vez que valueOf se llama, el valor de a se incrementa.

Por lo tanto, console.log se activará y generará (de todos modos en mi terminal) Thing: { value: 4} , lo que indica que el condicional era verdadero.


Aquí hay otra variación, utilizando una matriz para extraer los valores que desee.

const a = {
  n: [3,2,1],
  toString: function () {
    return a.n.pop();
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Yes');
}


De acuerdo, otro truco con generadores:

const value = function* () {
  let i = 0;
  while(true) yield ++i;
}();

Object.defineProperty(this, 'a', {
  get() {
    return value.next().value;
  }
});

if (a === 1 && a === 2 && a === 3) {
  console.log('yo!');
}


En realidad, la respuesta a la primera parte de la pregunta es "Sí" en todos los lenguajes de programación. Por ejemplo, esto es en el caso de C / C ++:

#define a   (b++)
int b = 1;
if (a ==1 && a== 2 && a==3) {
    std::cout << "Yes, it's possible!" << std::endl;
} else {
    std::cout << "it's impossible!" << std::endl;
}

Igual, pero diferente, pero igual (puede ser "probado" varias veces):

const a = { valueOf: () => this.n = (this.n || 0) % 3 + 1}
    
if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

Mi idea comenzó de cómo funciona la ecuación de tipo de objeto Número.


Si alguna vez recibe una pregunta de entrevista (o nota un comportamiento igualmente inesperado en su código) piense en qué tipo de cosas podrían causar un comportamiento que parece imposible a primera vista:

  1. Codificación : en este caso, la variable que estás viendo no es la que crees que es. Esto puede suceder si intencionalmente juega con Unicode usando homoglyphs o caracteres de espacio para hacer que el nombre de una variable se parezca a otra, pero los problemas de codificación también se pueden introducir accidentalmente, por ejemplo, al copiar y pegar código de la Web que contiene código Unicode inesperado puntos (p. ej., porque un sistema de gestión de contenido realizó un "formateo automático", como reemplazarlo fl con 'LATIN SMALL LIGATURE FL' (U + FB02) de Unicode).

  2. Condiciones de carrera : puede ocurrir una race-condition , es decir, una situación en la que el código no se ejecuta en la secuencia esperada por el desarrollador. Las condiciones de carrera a menudo suceden en código de subprocesos múltiples, pero varios subprocesos no son un requisito para que las condiciones de carrera sean posibles: la asincronía es suficiente (y no se confunda, async no significa que se usen múltiples subprocesos debajo del capó ).

    Tenga en cuenta que, por lo tanto, JavaScript tampoco está libre de condiciones de carrera solo porque es de un solo subproceso. Vea here un ejemplo simple de un solo subproceso, pero asíncrono. Sin embargo, en el contexto de una sola declaración, la condición de carrera sería bastante difícil de alcanzar en JavaScript.

    JavaScript con los trabajadores web es un poco diferente, ya que puede tener múltiples hilos. @mehulmpt nos ha mostrado una gran race-condition .

  3. Efectos secundarios : un efecto secundario de la operación de comparación de igualdad (que no tiene que ser tan obvio como en los ejemplos aquí, a menudo los efectos secundarios son muy sutiles).

Este tipo de problemas puede aparecer en muchos lenguajes de programación, no solo en JavaScript, por lo que no estamos viendo uno de los WTF de JavaScript clásicos aquí 1 .

Por supuesto, la pregunta de la entrevista y las muestras aquí parecen muy artificiales. Pero son un buen recordatorio de que:

  • Los efectos secundarios pueden volverse realmente desagradables y un programa bien diseñado debe estar libre de efectos secundarios no deseados.
  • El estado de subprocesamiento múltiple y mutable puede ser problemático.
  • No realizar correctamente la codificación de caracteres y el procesamiento de cadenas puede provocar errores desagradables.

1 Por ejemplo, puede encontrar un ejemplo en un lenguaje de programación totalmente diferente (C #) que exhibe un efecto secundario (uno obvio) here .


Usando Proxies :

var a = new Proxy({ i: 0 }, {
    get: (target, name) => name === Symbol.toPrimitive ? () => ++target.i : target[name],
});
console.log(a == 1 && a == 2 && a == 3);

Los proxies básicamente pretenden ser un objeto de destino (el primer parámetro), pero interceptan operaciones en el objeto de destino (en este caso, la operación "obtener propiedad") para que haya una oportunidad de hacer algo que no sea el comportamiento predeterminado del objeto. En este caso, se activa la acción "obtener propiedad" a cuando == coacciona su tipo para compararlo con cada número. Esto pasa:

  1. Creamos un objeto objetivo { i: 0 } , donde la i propiedad es nuestro contador
  2. Creamos un Proxy para el objeto de destino y lo asignamos a a
  3. Para cada a == comparación, a el tipo se coacciona a un valor primitivo
  4. Este tipo de coerción resulta en llamadas a[Symbol.toPrimitive]() internas
  5. El Proxy intercepta obtener la a[Symbol.toPrimitive] función usando el "get handler"
  6. "Obtener handler" del proxy comprueba que la propiedad que está siendo conseguido es Symbol.toPrimitive , en cuyo caso se incrementa y luego devuelve el contador desde el objeto de destino: ++target.i . Si se recupera una propiedad diferente, simplemente volvemos a devolver el valor de propiedad predeterminado, target[name]

Asi que:

var a = ...; // a.valueOf == target.i == 0
a == 1 && // a == ++target.i == 1
a == 2 && // a == ++target.i == 2
a == 3    // a == ++target.i == 3

Como con la mayoría de las otras respuestas, esto solo funciona con una verificación de igualdad suelta ( == ), porque las verificaciones de igualdad estrictas ( === ) no hacen una coerción de tipo que el Proxy puede interceptar.


¡ES POSIBLE!

var i = 0;

with({
  get a() {
    return ++i;
  }
}) {
  if (a == 1 && a == 2 && a == 3)
    console.log("wohoo");
}

Esto usa un captador dentro de una instrucción with para permitir a evaluación de tres valores diferentes.

... esto todavía no significa que deba usarse en código real ...

Peor aún, este truco también funcionará con el uso de === .

  var i = 0;

  with({
    get a() {
      return ++i;
    }
  }) {
    if (a !== a)
      console.log("yep, this is printed.");
  }


Cuando no puedes hacer nada sin expresiones regulares:

var a = {
  r: /\d/g, 
  valueOf: function(){
    return this.r.exec(123)[0]
  }
}

if (a == 1 && a == 2 && a == 3) {
    console.log("!")
}

Funciona debido al método valueOf personalizado que se llama cuando Object se compara con primitivo (como Number). El truco principal es que a.valueOf devuelve un nuevo valor cada vez porque llama a exec en una expresión regular con el indicador g , lo que provoca que se actualice lastIndex de esa expresión regular cada vez que se encuentra una coincidencia. Entonces, la primera vez this.r.lastIndex == 0 , coincide con 1 y actualiza lastIndex : this.r.lastIndex == 1 , por lo que la próxima vez la expresión regular coincidirá con 2 y así sucesivamente.


Ejemplo sin captadores o valueOf:

a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);

Esto funciona porque == invoca toString que llama a .join para las matrices.

Otra solución, usando Symbol.toPrimitive que es un equivalente ES6 de toString/valueOf :

let i = 0;
let a = { [Symbol.toPrimitive]: () => ++i };

console.log(a == 1 && a == 2 && a == 3);


Esto es posible en caso de que se acceda a a variable, digamos 2 trabajadores web a través de SharedArrayBuffer, así como algún script principal. La posibilidad es baja, pero es posible que cuando el código se compila en código máquina, los trabajadores web actualizan la variable a tiempo para que se cumplan las condiciones a==1 , a==2 y a==3 .

Este puede ser un ejemplo de condición de carrera en un entorno de subprocesos múltiples proporcionado por los trabajadores web y SharedArrayBuffer en JavaScript.

Aquí está la implementación básica de lo anterior:

main.js

// Main Thread

const worker = new Worker('worker.js')
const modifiers = [new Worker('modifier.js'), new Worker('modifier.js')] // Let's use 2 workers
const sab = new SharedArrayBuffer(1)

modifiers.forEach(m => m.postMessage(sab))
worker.postMessage(sab)

trabajador.js

let array

Object.defineProperty(self, 'a', {
  get() {
    return array[0]
  }
});

addEventListener('message', ({data}) => {
    array = new Uint8Array(data)
    let count = 0
    do {
        var res = a == 1 && a == 2 && a == 3
        ++count
    } while(res == false) // just for clarity. !res is fine
    console.log(`It happened after ${count} iterations`)
    console.log('You should\'ve never seen this')
})

modifier.js

addEventListener('message' , ({data}) => {
    setInterval( () => {
        new Uint8Array(data)[0] = Math.floor(Math.random()*3) + 1
    })
})

En mi MacBook Air, sucede después de alrededor de 10 mil millones de iteraciones en el primer intento:

Segundo intento:

Como dije, las posibilidades serán bajas, pero dado el tiempo suficiente, llegará a la condición.

Sugerencia: si tarda demasiado en su sistema. Pruebe solo a == 1 && a == 2 y cambie Math.random()*3 a Math.random()*2 . Agregar más y más a la lista reduce la posibilidad de golpear.


Esto también es posible utilizando una serie de captadores de autoescritura:

(Esto es similar a la solución de jontro, pero no requiere una variable de contador).

(() => {
    "use strict";
    Object.defineProperty(this, "a", {
        "get": () => {
            Object.defineProperty(this, "a", {
                "get": () => {
                    Object.defineProperty(this, "a", {
                        "get": () => {
                            return 3;
                        }
                    });
                    return 2;
                },
                configurable: true
            });
            return 1;
        },
        configurable: true
    });
    if (a == 1 && a == 2 && a == 3) {
        document.body.append("Yes, it’s possible.");
    }
})();


No veo esta respuesta ya publicada, así que también incluiré esta en la mezcla. Esto es similar a la respuesta de Jeff con el espacio Hangul de ancho medio.

var a = 1;
var  = 2;
var а = 3;
if(a == 1 &&  == 2 && а == 3) {
    console.log("Why hello there!")
}

Puede notar una ligera discrepancia con el segundo, pero el primero y el tercero son idénticos a simple vista. Los 3 son caracteres distintos:

a - minúscula latina A
- Ancho completo en minúscula latina A
а - Cirílica minúscula A

El término genérico para esto es "homoglifos": diferentes caracteres unicode que tienen el mismo aspecto. Por lo general, es difícil obtener tres que sean completamente indistinguibles, pero en algunos casos puedes tener suerte. A, Α, А y Ꭺ funcionarían mejor (latín-A, griego alfa , Cyrillic-A y Cherokee-A respectivamente; desafortunadamente, las letras minúsculas griegas y cherokee son demasiado diferentes del latín a : α , , y no ayuda con el fragmento anterior).

Hay toda una clase de ataques de homoglifos, más comúnmente en nombres de dominio falsos (por ejemplo, wikipediа.org (cirílico) vs wikipedia.org (latín)), pero también puede aparecer en el código; normalmente se las conoce como poco claras (como se menciona en un comentario, las preguntas [underhanded] ahora están fuera de tema en PPCG , pero solían ser un tipo de desafío donde aparecían este tipo de cosas). Usé este sitio web para encontrar los homoglifos utilizados para esta respuesta.


Regla número uno de entrevistas; Nunca digas imposible.

No hay necesidad de trucos de personajes ocultos.

window.__defineGetter__( 'a', function(){
    if( typeof i !== 'number' ){
        // define i in the global namespace so that it's not lost after this function runs
        i = 0;
    }
    return ++i;
});

if( a == 1 && a == 2 && a == 3 ){
    alert( 'Oh dear, what have we done?' );
}


Si aprovecha cómo funciona == , simplemente puede crear un objeto con una función personalizada toString (o valueOf ) que cambie lo que devuelve cada vez que se usa de modo que satisfaga las tres condiciones.

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

La razón por la que esto funciona se debe al uso del operador de igualdad suelta. Cuando se utiliza la igualdad suelta, si uno de los operandos es de un tipo diferente al otro, el motor intentará convertir uno a otro. En el caso de un objeto a la izquierda y un número a la derecha, intentará convertir el objeto en un número llamando primero a valueOf si es invocable y, en su defecto, llamará a toString . Solía toString en este caso simplemente porque es lo que me vino a la mente, valueOf tendría más sentido. Si, en cambio, devolviera una cadena de toString , el motor habría intentado convertir la cadena en un número que nos diera el mismo resultado final, aunque con una ruta un poco más larga.


Si se le pregunta si es posible (no DEBE), puede pedirle a "a" que devuelva un número aleatorio. Sería cierto si genera 1, 2 y 3 secuencialmente.

with({
  get a() {
    return Math.floor(Math.random()*4);
  }
}){
  for(var i=0;i<1000;i++){
    if (a == 1 && a == 2 && a == 3){
      console.log("after " + (i+1) + " trials, it becomes true finally!!!");
      break;
    }
  }
}





ecmascript-6