A glimpse into the mind of a framework author
Tags: javascript, mithril, html, events
Published: 2019-11-29 17:26:30

---
title: A glimpse into the mind of a framework author
published: true
description: How do library authors think about their code?
tags: javascript, mithril, html, events
cover_image: https://thepracticaldev.s3.amazonaws.com/i/z4f5avrfpephsrgrzeaq.jpeg
---

*This article was first published on [Git Connected](https://levelup.gitconnected.com/a-glimpse-into-the-mind-of-a-javascript-framework-author-689216a8619c).*

Have you ever read the source code of a popular library and come across comments which explain what the code is doing? Sometimes the explanation given is straightforward but other times it leaves you scratching your head. I had one such moment recently and after much head scratching, it left me with a deeper appreciation of the effort that goes into open source tools and inspired me to write this post. 

I came across the code in question whilst investigating how frontend frameworks attach event handlers to DOM elements. Over the last few months, I have been rebuilding different parts of the frontend stack as a way of improving my knowledge, and part of that has involved creating a UI framework based on the virtual DOM paradigm.

The code I was looking at was from [Mithril](https://mithril.js.org). Internally, it registers events on DOM elements by creating an object and passing it as the second argument to the `document.addEventListener` method. The relevant source code is:
```js
// Here's an explanation of how this works:
// 1. The event names are always (by design) prefixed by `on`.
// 2. The EventListener interface accepts either a function or an object // with a `handleEvent` method.
// 3. The object does not inherit from `Object.prototype`, to avoid
//    any potential interference with that (e.g. setters).
// 4. The event name is remapped to the handler before calling it.
// 5. In function-based event handlers, `ev.target === this`. We replicate
//    that below.
// 6. In function-based event handlers, `return false` prevents the default
//    action and stops event propagation. We replicate that below.
function EventDict() {
  // Save this, so the current redraw is correctly tracked.
  this._ = currentRedraw
}
EventDict.prototype = Object.create(null)
EventDict.prototype.handleEvent = function (ev) {
  var handler = this["on" + ev.type]
  var result
  if (typeof handler === "function") result = handler.call(ev.currentTarget, ev)
  else if (typeof handler.handleEvent === "function") handler.handleEvent(ev)
  if (this._ && ev.redraw !== false) (0, this._)()
  if (result === false) {
    ev.preventDefault()
    ev.stopPropagation()
  }
}
``` 
The third point: “*The object does not inherit from `Object.prototype`, to avoid any potential interference with that (e.g. setters).*” is what caught my attention. I reached out to Mithril core maintainer [Isiah Meadows](https://mobile.twitter.com/isiahmeadows1) via the Mithril [Gitter](https://gitter.im/mithriljs/mithril.js/archives/2019/09/04?at=5d6fce6c11f374371a0d6a8b) chat and his explanation is what we will go into next. 

### Guarding against rogue third-party code

Imagine that instead of inheriting from `null`, `EventDict`'s prototype is `Object.prototype`. A Mithril user then writes the following code or uses a library whch does the following: 

```js
Object.defineProperty(Object.prototype, "onsubmit", {
  get() {
    return (e) => { 
      e.preventDefault();
      console.log('I am on the setter');
    }
  },
  set(val) {
    console.log(val);
  }
})
```

The application is then written as follows:

```js
m.render(document.getElementById('app'), 
  m('form', {
    onsubmit: (e) => { 
      e.preventDefault(); 
      console.log('I am on the form element')
    }
  }, [
    m('button', { type: 'submit'}, 'Submit')
  ])
)
```

If the form is submitted, `I am on the setter` will be logged to the console instead of `I am on the form`. The contents of the getter and setter functions are not important but their existence means the `onsubmit` event handler is not registered on the form as intended.

This is because when the event is triggered, the `EventDict.prototype.handleEvent` method is executed and this line `var handler = this["on" + ev.type]` returns the `onsubmit` function on `Object.prototype` instead of the function specified on the form element.  

The problem above also crops up when Mithril runs in an environment where `Object.prototype` has been extended in a much simpler fashion like so:

```js
Object.prototype.onsubmit = 1
```

And the application code is:

```js
m.render(document.getElementById('app'), 
  m('form', {
    onreset: (e) => { 
      console.log('resetting form...')
    },
    onsubmit: (e) => { 
      e.preventDefault(); 
      console.log('submitting form...')
    },
  }, [
    m('button', { type: 'reset'}, 'Reset'),
    m('button', { type: 'submit'}, 'Submit')
  ])
)
```

In this example, we have added the `onreset` event to our form. For us to understand the problems it poses, we first have to look at Mithril's `updateEvent` method. This method runs whenever [on-event handlers](https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Event_handlers) are being set or removed on DOM elements.

```js
function updateEvent(vnode, key, value) {
  if (vnode.events != null) {
    if (vnode.events[key] === value) return
    if (value != null && (typeof value === "function" || typeof value === "object")) {
      if (vnode.events[key] == null) vnode.dom.addEventListener(key.slice(2), vnode.events, false)
      vnode.events[key] = value
    } else {
      if (vnode.events[key] != null) vnode.dom.removeEventListener(key.slice(2), vnode.events, false) 
      vnode.events[key] = undefined
    }
  } else if (value != null && (typeof value === "function" || typeof value === "object")) {
    vnode.events = new EventDict()
    vnode.dom.addEventListener(key.slice(2), vnode.events, false)
    vnode.events[key] = value
  }
}
```
Virtual DOM frameworks like Mithril turn calls such as `m('button', { type: 'reset'}, 'Reset')` into objects which represent a given DOM element. In Mithril, these objects are called `vnodes` (For comparison, in React those objects are called `fibers`. I have written about them [here](https://dev.to/carlmungazi/a-journey-through-reactdom-render-302c)). The `key` is the event name and the `value` is whatever object or function assigned to handle that event.

When the code is executed, the `vnode` object for the form element is passed to the `updateEvent` function. Inside the function, the `else if` clause runs because the `vnode`'s `events` property is `undefined`. Once the function has finished executing, the `vnode` object will look like this:

```js
{
  attrs: {
    onreset: onreset(e) { /* ... */ }, 
    onsubmit: onsubmit(e) { /* ... */ },
  }
  children: [{…}, {…}]
  dom: form
  domSize: undefined
  events: EventDict {
    onreset: onreset(e) { /* ... */ },
    _: undefined
  }
  instance: undefined
  key: undefined
  state: {}
  tag: "form"
  text: undefined
}
```

The `events` property has been assigned an `EventDict` instance and that instance is then given a reference to the `onreset` function as one of its properties. Also, the form DOM element has an event listener attached for the `onreset` event. So far, so good.

`updateEvent` runs for every on-event present on a DOM element. The second time it is called, it is passed `onsubmit` as the `key` argument. This time, however, the first `if` clause runs because `vnode.events` is no longer `null`. 

```js
function updateEvent(vnode, key, value) {
  if (vnode.events != null) {
    if (vnode.events[key] === value) return
    if (value != null && (typeof value === "function" || typeof value === "object")) {
      if (vnode.events[key] == null) vnode.dom.addEventListener(key.slice(2), vnode.events, false)
      vnode.events[key] = value
    }
    // ...
  }
  // ...
}
```

Within this `if` statement are two other `if` statements. The first one is skipped because `vnode.events[key]` is not equal to `value`, which in this case is the `onsubmit` function. In fact, the value of `vnode.events[key]` is `1`. Why? Remember that this code is running in an environment where somebody has written this: `Object.prototype.onsubmit = 1`. Since the form `EventDict` instance does not yet have an `onsubmit` property and it inherits from `Object.prototype`, the JavaScript engine will walk up the prototype chain and find that `onsubmit` exists on `Object.prototype`. 

The next `if` clause then checks that `value` has a function or object. This check passes but crucially, an event listener for the `onsubmit` event is not then attached to the form element because `vnode.events[key]` is not `null` as it should be, its value is `1`. The next line adds the `onsubmit` property to the `EventDict` instance on the form element `vnode` along with the related function. 

Like the first example where `Object.defineProperty` was used to add the `onsubmit` property to `Object.prototype`, when the form is submitted it will not behave as the developer intended. 


### Summary

Both the examples above seem like bizarre things to guard against but that is the kind of defensive coding framework and library authors have to do. Using the constructor function approach to create a new object which does not inherit from `null` means the newly created object is at the mercy of whatever additions may have been made to `Object.prototype`.