function recordData(){
    var element = document.getElementsByClassName("bubble");
    for(var i = 0; i < element.length; i++){
        element[i].addEventListener("click", function(){
            var id = element[i].attributes.id.value;
            var x_cordinate = element[i].children[2].attributes.x.value;
            var y_cordinate = element[i].children[2].attributes.y.value;
            var keyword = element[i].children[0].textContent;
            clicked_elements.push({
                id: id,
                x_cordinate: x_cordinate,
                y_cordinate: y_cordinate,
                keyword: keyword
            })
        }, false);
    }
}

Cuando intento hacer clic en los elementos en html, aparece un error "Error de tipo no capturado: no se pueden leer los atributos de propiedad de undefined". No puedo acceder a la identificación y otros atributos de ese elemento en particular en el método addEventListener. Por favor explique qué estoy haciendo mal aquí si necesito registrar en qué se está haciendo clic.

0
Hassan Abbas 13 may. 2016 a las 11:27

4 respuestas

La mejor respuesta
function recordData(){
	var element = document.getElementsByClassName("bubble");
	for(var i = 0; i < element.length; i++){
		element[i].addEventListener("click", function(){
			var id = this.attributes.id.value;
			var x_cordinate = this.children[2].attributes.x.value;
			var y_cordinate = this.children[2].attributes.y.value;
			var keyword = this.children[0].textContent;

			clicked_elements.push({
				id: id,
				x_cordinate: x_cordinate,
				y_cordinate: y_cordinate,
				keyword: keyword
			})
		}, false);
	}
}

Esta es la versión final de mi código que funciona bien. 'this' me ayudó a acceder al objeto dentro del detector de eventos.

0
Hassan Abbas 13 may. 2016 a las 09:00

No puede usar i dentro de la devolución de llamada addEventListener porque se ejecutará después del final del bucle for.

Use this en lugar de elements[i] para obtener el elemento actual dentro del cierre.

1
keul 13 may. 2016 a las 08:34

Está ejecutando un problema común con Javascript, en su 'para' está registrando los controladores de eventos en función del valor i, cuando el evento que se dispara el valor de 'i' es igual a 'element.length' que devuelve indefinido esto es la razón por la que recibes tu error.

Para resolver esto, debe hacer un clousere para mantener una referencia al elemento de la matriz, no a su índice. Intente utilizar Array.forEach en lugar de un 'for'.

Aquí puede encontrar más información sobre cómo funcionan los cierres.

0
Community 23 may. 2017 a las 12:16

Su problema aquí es que la variable i no tendrá el valor que cree que tendría. Eso se debe a cómo funcionan los cierres.

Prueba este sencillo ejemplo:

for(var i = 0; i < 5; i++) {
    console.log("Before timeout: " + i);
    setTimeout(function() {
        console.log("After timeout: " + i);
    }, 1000);
}

¿Qué esperarías cuando ejecutas esto? Probablemente después de un segundo, ¿ves esto en la consola?

Before timeout: 0
Before timeout: 1
Before timeout: 2
Before timeout: 3
Before timeout: 4
After timeout: 0
After timeout: 1
After timeout: 2
After timeout: 3
After timeout: 4

Pero adivina qué, verás esto:

Before timeout: 0
Before timeout: 1
Before timeout: 2
Before timeout: 3
Before timeout: 4
After timeout: 5
After timeout: 5
After timeout: 5
After timeout: 5
After timeout: 5

¿Por qué 5? La instrucción for básicamente establece i en 0, luego cuenta cada iteración y comprueba cada vez si todavía está por debajo de 5, y si no, rompe el ciclo. Entonces, después del ciclo, i tiene el valor 5, así:

for(var i = 0; i < 5; i++) {
    /* ... */
}
console.log("After loop: " + i);

...da:

After loop: 5

Bien, pero ¿por qué se usa el valor que i tiene después del ciclo? ¡Esto se debe a que la devolución de llamada de función que está pasando a setTimeout accede directamente a la variable i en el ámbito externo! Y debido a que la función solo se ejecutará después de un segundo (después de que el ciclo se haya completado durante mucho tiempo), el valor de i en este momento será 5, como se muestra arriba.

Entonces, la solución sería crear una copia local de esta variable que sea local para cada iteración. Ahora, esto no es tan trivial como parece porque en JavaScript, los bloques de control como for no crean un alcance (a menos que use ES6 y let). Debe usar otra función anónima a su alrededor para crear una copia local que luego sea diferente cada vez:

for(var i = 0; i < 5; i++) {
    // This is a so-called "immediately executed function expression"
    // It's an anonymous function which is then immediately called due
    // to the `()` at the end.
    (function() {
        var j = i; // Now we have a local `j`
        console.log("Before timeout: " + j);
        setTimeout(function() {
            console.log("After timeout: " + j);
        }, 1000);
    })();
}

Otra forma de hacerlo, que es un poco más corto, es este truco:

for(var i = 0; i < 5; i++) {
    // See how this anonymous function now takes a parameter `i`?
    // And we pass this parameter when calling this function below,
    // which basically creates an inner copy which we can now also call
    // `i`.
    (function(i) {
        console.log("Before timeout: " + i);
        setTimeout(function() {
            console.log("After timeout: " + i);
        }, 1000);
    })(i);
}

Es por eso que recomiendo usar cosas como forEach al iterar sobre matrices porque toman una devolución de llamada de función como parámetro para que tenga automáticamente un alcance local en su código.

Ahora, en su caso, puede usar una de las soluciones de funciones anónimas de arriba, o algo así como forEach, pero hay un problema: la variable element no es una matriz JavaScript normal, es un lista de elementos DOM, que no tiene forEach. Pero aún puede hacerlo funcionar con el truco Array.prototype.forEach.call:

function recordData(){
    var element = document.getElementsByClassName("bubble");
    Array.prototype.forEach.call(element, function(i) {
        element[i].addEventListener("click", function(){
            var id = element[i].attributes.id.value;
            var x_cordinate = element[i].children[2].attributes.x.value;
            var y_cordinate = element[i].children[2].attributes.y.value;
            var keyword = element[i].children[0].textContent;
            clicked_elements.push({
                id: id,
                x_cordinate: x_cordinate,
                y_cordinate: y_cordinate,
                keyword: keyword
            })
        }, false);
    });
}

Antes, no funcionaba porque estaba utilizando efectivamente element[element.length].attributes (porque i ya era uno más allá de su límite superior de la matriz), por lo que element[i] no estaba definido.

Ahí tienes!

Sin embargo, una forma aún mejor sería usar this porque dentro de un detector de eventos, this se refiere al objetivo del evento. Entonces, en lugar de element[i], ¡puedes usar this en todas partes dentro de tu oyente!

1
CherryDT 13 may. 2016 a las 08:56