Break on Property Change of an Arbitrary Object

- - | Comments

Hold on to your hats, this debugger trick is awesome. If you’ve ever wondered how to figure out what is changing some property on some object behind your back, this is the way to do it.

Let’s say that you have an object in one part of your code that you’re working with.

1
2
3
4
5
6
  // /src/paging.js
  window.pagingData = {
    page: 3,
    perPage: 100,
    lastFetchedId: 1234
  };

And some other remote part of your code, that you had no idea existed, is modifying your object behind your back.

1
2
3
4
5
6
7
  // /src/path/youve/never/noticed.js

  // This is because I, the author, hate everyone

  setInterval(function(){
    window.pagingData.lastFetchedId = 42;
  }, 1000);

It’s straightforward to setup a watch in the Chrome debugger, so you could easily see that something was being changed, but the hard part is figuring out what piece of code is doing the changing. If you happen to sometimes live in a world with dozens and dozens of script tags on any given page, you’re pretty much SOL when it comes to grepping for the culprit.

This is where one very handy feature of ES5 comes in. ES5 defines many APIs in JavaScript that we use regularly, like Array.prototype.indexOf and JSON.parse, but it also has fancier pieces that like Object.defineProperty. Object.defineProperty allows you to setup accessors called get and set for any property on an object in such a way that any code that uses that property doesn’t have to know that a function is being run.

1
2
3
4
5
6
7
8
9
10
11
12
13
  (function(){
  var localLastFetchedId;

  Object.defineProperty(window.pagingData, 'lastFetchedId', {
    get: function(){
      return localLastFetchedId;
    },
    set: function(val){
      localLastFetchedId = val;
    }
  });

  }());

This code changes very little external behavior of the pagingData object. All it does is run these particular functions whenever pagingData.lastFetchedId is set to a new value or the value is being read. The exciting part is that now, you can add a breakpoint in your debugger inside of the set function that will then break whenever something sets that property.

Once you have this breakpoint, you can look through the callstack and you’ll be pointed directly at /src/path/youve/never/noticed.js.

If you want an even easier way to get that breakpoint in there, you can just add a debugger statement directly into your new debugging code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  (function(){
  var localLastFetchedId;

  Object.defineProperty(window.pagingData, 'lastFetchedId', {
    get: function(){
      return localLastFetchedId;
    },
    set: function(val){
      localLastFetchedId = val;
      debugger; // BOOM!
    }
  });

  }());

As always, make sure that you remove this kind of debugging code before you commit, and don’t let it into production. I hope you have questions. I’ll see you in the comments!

Comments