Edit in JSFiddle

const arrayFunctions = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

function reactiveArray(ary, callback) {
  const wrapped = ary.slice()

  arrayFunctions.forEach(function(fn) {
    const original = Array.prototype[fn]
    Object.defineProperty(wrapped, fn, {
      value: function() {
        console.log('feelin manipulative?')
    	  const result = original.apply(this, arguments)
        callback()
        return result
      }
    })
  })
 
  return wrapped
}

function reactive(obj, callback) {
  if (typeof obj !== 'object') return obj
  if (Array.isArray(obj)) return reactiveArray(obj, callback)
  const wrapped = {}

  for (let key in obj) {
    let value = obj[key]
    
    if (Array.isArray(value)) {
      value = reactiveArray(value, callback)
    } else if (typeof value === 'object') {
      value = reactive(value, callback)
    }
 
    Object.defineProperty(wrapped, key, {
      get: function reactiveGetter() {
        return value
      },
      set: function reactiveGetter(newValue) {
        console.log('setting value from', value, 'to', newValue)
        value = reactive(newValue, callback)
        callback()
      }
    })
  }

  return wrapped
}

function createComponent (obj) {
  const data = obj.data || {}
  const render = obj.render.bind(obj)
  obj.data = reactive(data, render)
  obj.render() // sneaked in an initial render call
  
  return obj
}

const helloComponent = createComponent({
  el: document.getElementById('content'),
  data: {
    name: {}
  },
  render: function () {
    console.log('rendering helloComponent')
    this.el.innerHTML = `<h1>Hello ${this.data.name.first} ${this.data.name.last}</h1>`
  }
})

const lipsumComponent = createComponent({
  el: document.getElementById('content2'),
  data: {
    content: []
  },
  render: function () {
    console.log('rendering lipsumComponent')
    const elements = this.data.content.map(function(el) {
      const tag = el[0], text = el[1]
      return `<${tag}>${text}</${tag}>`
    })
    this.el.innerHTML = elements.join('')
  }
})

helloComponent.data.name = {
  first: 'Hansi',
  last: 'Hinterseer'
}
helloComponent.data.name.first = 'Ada'
helloComponent.data.name.last = 'Lovelace'

lipsumComponent.data.content = [
  ['p', 'Lorem Ipsum sit amet dolor…']
]
lipsumComponent.data.content.push(['p', 'Hereby I kindly declare that I am out of ideas!'])
The classic:

<div id="content">…loading</div>
<div id="content2"></div>