Punto de corte en cambio de propiedad

147

Firebug para Firefox tiene una característica agradable, llamada "Interrupción en el cambio de propiedad", donde puedo marcar cualquier propiedad de cualquier objeto, y detendrá la ejecución de JavaScript justo antes del cambio.

Estoy tratando de lograr lo mismo en Google Chrome, y no puedo encontrar la función en el depurador de Chrome. ¿Cómo hago esto en Google Chrome?

Arsen Zahray
fuente
1
Si desea hacer esto con elementos HTML, consulte stackoverflow.com/a/32686203/308851
chx

Respuestas:

106

Si no te importa jugar con la fuente, puedes redefinir la propiedad con un descriptor de acceso.

// original object
var obj = {
    someProp: 10
};

// save in another property
obj._someProp = obj.someProp;

// overwrite with accessor
Object.defineProperty(obj, 'someProp', {
    get: function () {
        return obj._someProp;
    },

    set: function (value) {
        debugger; // sets breakpoint
        obj._someProp = value;
    }
});
katspaugh
fuente
2
¿hay algún enchufe que haga eso por mí?
Arsen Zahray
3
@ArsenZahray, no sé. Sin embargo, puede hacer una función práctica y usar like console.watch(obj, 'someProp').
katspaugh
55
Esto no funciona para propiedades integradas, como window.locationpor razones de seguridad.
qJake
1
Para depurar los establecedores de elementos DOM, este patrón debe modificarse ligeramente. Ver mnaoumov.wordpress.com/2015/11/29/… para más detalles
mnaoumov
@katspaugh, ¿puedo preguntar por qué necesita esto? obj._someProp = obj.someProp;Parece no tener relación con lo que está tratando de archivar (probablemente porque me falta algo)
Victor
109

Editar 2016.03: Object.observeestá en desuso y se elimina en Chrome 50

Editar 2014.05: Object.observese agregó en Chrome 36

Chrome 36 se entrega con Object.observeimplementación nativa que se puede aprovechar aquí:

myObj = {a: 1, b: 2};
Object.observe(myObj, function (changes){
    console.log("Changes:");
    console.log(changes);
    debugger;
})
myObj.a = 42;

Si lo desea solo temporalmente, debe almacenar la devolución de llamada en una variable y llamar Object.unobservecuando haya terminado:

myObj = {a: 1, b: 2};
func = function() {debugger;}
Object.observe(myObj, func);
myObj.a = 42;
Object.unobserve(myObj, func);
myObj.a = 84;

Tenga en cuenta que cuando lo use Object.observe, no se le notificará cuando la tarea no haya cambiado nada, por ejemplo, si ha escrito myObj.a = 1.

Para ver la pila de llamadas, debe habilitar la opción "pila de llamadas asíncronas" en Dev Tools:

pila de llamadas asíncronas de Chrome


Respuesta original (2012.07):

Un console.watchboceto sugerido por @katspaugh:

var console = console || {}; // just in case
console.watch = function(oObj, sProp) {
   var sPrivateProp = "$_"+sProp+"_$"; // to minimize the name clash risk
   oObj[sPrivateProp] = oObj[sProp];

   // overwrite with accessor
   Object.defineProperty(oObj, sProp, {
       get: function () {
           return oObj[sPrivateProp];
       },

       set: function (value) {
           //console.log("setting " + sProp + " to " + value); 
           debugger; // sets breakpoint
           oObj[sPrivateProp] = value;
       }
   });
}

Invocación:

console.watch(obj, "someProp");

Compatibilidad:

  • En Chrome 20, puedes pegarlo directamente en Dev Tools en tiempo de ejecución.
  • Para completar: en Firebug 1.10 (Firefox 14), debe inyectarlo en su sitio web (por ejemplo, a través de Fiddler si no puede editar la fuente manualmente); Lamentablemente, las funciones definidas de Firebug no parecen activarse debugger(¿o es una cuestión de configuración? Corríjame entonces), pero console.logfunciona.

Editar:

Tenga en cuenta que en Firefox, console.watchya existe, debido al no estándar de Firefox Object.watch. Por lo tanto, en Firefox, puede observar los cambios de forma nativa:

>>> var obj = { foo: 42 }
>>> obj.watch('foo', function() { console.log('changed') })
>>> obj.foo = 69
changed
69

Sin embargo, esto se eliminará pronto (a fines de 2017) .

jakub.g
fuente
1
Por cierto, parece que no se puede ejecutar el depurador en el código personalizado es una regresión entre Firebug 1.8 y 1.9: problema 5757 -> duplicado del problema 5221
jakub.g
1
@ColeReed debemos almacenar el valor en algún lugar para recuperarlo en el getter; no se puede almacenar oObj[sProp]porque el captador entraría en una recursión infinita. Pruébalo en Chrome, lo obtendrás RangeError: Maximum call stack size exceeded.
jakub.g
1
Me gustaría agregar esto, ya que la asynccasilla de verificación es tan dorada con este enfoque: html5rocks.com/en/tutorials/developertools/async-call-stack
cnp
1
@PhiLho es posible ver la pila, con la asynccasilla de verificación como @cnp escribió, vea mi actualización
jakub.g
1
Debería actualizar esta respuesta: Object.observeestá en desuso y pronto se eliminará: consulte: chromestatus.com/features/6147094632988672
Amir Gonnen
79

Hay una biblioteca para esto: BreakOn ()

Si lo agrega a las herramientas de desarrollo de Chrome como un fragmento (fuentes -> fragmentos -> clic derecho -> nuevo -> pegar esto ) , puede usarlo en cualquier momento.


Para usarlo, abra las herramientas de desarrollo y ejecute el fragmento. Luego, para romper cuando myObject.myPropertyse cambia, llame a esto desde la consola de desarrollo:

breakOn(myObject, 'myProperty');

También puede agregar la biblioteca a la compilación de depuración de su proyecto para no tener que volver a llamar breakOncada vez que actualice la página.

BlueRaja - Danny Pflughoeft
fuente
5

Esto también se puede hacer utilizando el nuevo objeto Proxy cuyo propósito es exactamente eso: interceptar las lecturas y escrituras en el objeto envuelto por el Proxy. Simplemente envuelva el objeto que desea observar en un Proxy y use el nuevo objeto envuelto en lugar del original.

Ejemplo:

const originalObject = {property: 'XXX', propertyToWatch: 'YYY'};
const watchedProp = 'propertyToWatch';
const handler = {
  set(target, key, value) {
    if (key === watchedProp) {
      debugger;
    }
    target[key] = value;
  }
};
const wrappedObject = new Proxy(originalObject, handler);

Ahora use wrapObject donde proporcionaría originalObject en su lugar y examine la pila de llamadas en el descanso.

Dima Slivin
fuente
El proxy setdebe regresar truepara que no falle por otros casos que no sean rastreados.
keaukraine
4
function debugProperty(obj, propertyName) {
  // save in another property
  obj['_' + propertyName] = obj[propertyName];

  // overwrite with accessor
  Object.defineProperty(obj, propertyName, {
    get: function() {
      return obj['_' + propertyName];
    },

    set: function(value) {
      debugger; // sets breakpoint
      obj['_' + propertyName] = value;
    }
  });
}
Roland Soós
fuente
1

Decidí escribir mi propia versión de esta solución, guardarla en un fragmento en DevTools de Chrome y envolverla en un IIFE que debería admitir tanto Node como Navegadores. También cambió el observador para usar una variable de alcance en lugar de una propiedad en el objeto, de modo que no haya posibilidad de conflictos de nombres, y cualquier código que enumere claves no "verá" la nueva "clave privada" que se crea:

(function (global) {
  global.observeObject = (obj, prop) => {
    let value

    Object.defineProperty(obj, prop, {
      get: function () {
        return value
      },

      set: function (newValue) {
        debugger
        value = newValue
      },
    })
  }
})(typeof process !== 'undefined' ? process : window)
Alexandros Katechis
fuente
-2

Chrome tiene esta característica incorporada en las últimas versiones https://developers.google.com/web/updates/2015/05/view-and-change-your-dom-breakpoints .

Por lo tanto, no necesita más bibliotecas y soluciones personalizadas, simplemente haga clic con el botón derecho en el elemento DOM en el inspector y elija 'Romper' -> 'modificaciones de atributos' y listo.

Ivica Puljic
fuente
10
Pidió cambio de propiedad (objeto js), no cambio de valor de atributo DOM
Z. Khullah
1
@Ivica Esta es una buena técnica, pero este es el lugar equivocado para ponerla. Estaría bien como comentario, pero no como respuesta.
bnieland