The official Vue guide for the Vue instance tells that the instance object proxies all properties found in the instance’s data object.

var data = { a: 1 }

var vm = new Vue({ data })

vm.a === data.a // -> true
// setting the property also affects original data
vm.a = 2
data.a // -> 2

// ... and vice-versa
data.a = 3
vm.a // -> 3

We’ll replicate this behaviour in a vanilla JS function. You don’t have to know about VueJS to follow the rest of the post. It’s just about javascript.

function myVue(opts){
  this._data = opts.data;
  Object
    .keys(opts.data)
    .forEach(key => {
      props = {
        configurable: true,
        enumerable: true,
        get() { return this._data[key]; },
        set(val) { this._data[key] = val; }
      }
      Object.defineProperty(this, key, props);
    });
}

Let’s try this "myVue" function with the same example as from the official guide.

let data = { a: 1 };

let vm = new myVue({ data });

console.log(vm.a === data.a);

// setting the property also affects original data
vm.a = 2;
data.a // -> 2

// ... and vice-versa
data.a = 3;
vm.a // -> 3

It works!

But how?

The key JS feature that enables this behaviour is Getters and Setters of the javascript object.

It lets you define properties which are not hard-coded, instead are represented by functions that dynamically return values.

Conclusion

I came to know about this technique after finding it in the VueJS source code. The magic happens in the src/core/instance/state.js file.

The initData function does the work. It loops over data’s keys and asks the proxy function to define the proxied properties on the Vue instance itself.