Understanding Vue 2's Reactivity System

Vue 2 implements a reactivity system that tracks dependencies and updates views when data changes. This system works differently for objects versus arrays.

Objectt Reactivity

For objects, Vue utilizes the defineReactive function, which internally employs Object.defineProperty to establish getter and setter methods for each property. The system recursively traverses object properties to establish reactivity at all level.

Array Reactivity

Arrays require special handling since Vue can't directly detect changes when using array indices. Instead, Vue intercepts and wraps seven key array methods:

  • push - Adds elements to the end of an array, returns new length
  • pop - Removes the last element, returns the removed element
  • shift - Removes the first element, returns the removed element
  • unshift - Adds elements to the beginning, returns new length
  • sort - Sorts elements by Unicode code points, returns the array
  • splice - Adds/removes elements at any position, returns removed elements
  • reverse - Reverses array order, returns the array

Project Structure

public/index.html


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Vue Reactivity Demo</title>
</head>
<body>
    <div id="app">Hello Vue</div>
    <script src="../dist/umd/framework.js"></script>
    <script>
        const app = new Framework({
            el: '#app',
            data: {
                count: 100,
                info: { level: 10 },
                value: 30,
                items: [8, 9, 10, { score: 99 }]
            }
        })
    </script>
</body>
</html>

state.js

import { observe } from "./observer/index.js"

export function initializeState(vm) {
    const options = vm.$options
    
    if (options.props) {
        initializeProps(vm)
    }

    if (options.methods) {
        initializeMethods(vm)
    }

    if (options.data) {
        initializeData(vm)
    }

    if (options.computed) {
        initializeComputed(vm)
    }

    if (options.watch) {
        initializeWatch(vm)
    }
}

function initializeProps(vm) {
    // Implementation would go here
}

function initializeMethods(vm) {
    // Implementation would go here
}

function initializeData(vm) {
    let data = vm.$options.data
    vm.data = data = typeof data === 'function' ? data.call(vm) : data
    
    // Observe the data
    observe(data)
}

function initializeComputed(vm) {
    // Implementation would go here
}

function initializeWatch(vm) {
    // Implementation would go here
}

init.js

import initializeState from "./state"

export function setupInitMixin(Framework) {
    Framework.prototype._init = function (options) {
        const vm = this
        vm.$options = options
        
        // Initialize state
        initializeState(vm)
    }
}

index.js

import setupInitMixin from "./init"

function Framework(options) {
    // Initialize
    this._init(options)
}

// Add _init method to prototype

// Setup init mixin
setupInitMixin(Framework)

export default Framework

observer/index.js

import arrayMethods from "./array";

class Observer {
    constructor(value) {
        // Mark the value as observed
        Object.defineProperty(value, '__observer__', {
            enumerable: false,
            configurable: false,
            value: this
        })
        
        if (Array.isArray(value)) {
            // Override array prototype
            value.__proto__ = arrayMethods
            this.observeArray(value)
        } else {
            this.walk(value)
        }
    }

    observeArray(value) {
        value.forEach(item => observe(item));
        // Observe each array element
    }

    walk(value) {
        const keys = Object.keys(value)
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i]
            let v = value[key]
            defineReactive(value, key, v)
        }
    }
}

function defineReactive(data, key, value) {
    // Value might be an object, so observe it
    observe(value)
    
    Object.defineProperty(data, key, {
        get() {
            console.log(`Getting ${key}`);
            return value
        },
        set(newV) {
            if (newV === value) return
            observe(newV) // New value might be an object
            console.log(`Setting ${key} to`, newV);
            value = newV
        }
    })
}

export function observe(data) {
    if (typeof data !== 'object' || data === null) {
        return;
    }
    return new Observer(data)
}

array.js

// Create a new object with Array.prototype as its prototype
let arrayOverrides = Object.create(Array.prototype);

// Override array methods for reactivity
['push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse']
    .forEach((method) => {
        arrayOverrides[method] = function (...args) {
            console.log('Array modified, triggering view update');
            
            // Handle newly inserted elements
            let inserted;
            switch (method) {
                case 'push':
                case 'unshift':
                    inserted = args;
                    break;
                case 'splice': 
                    inserted = args.slice(2)
            }
            
            if (inserted) {
                // Observe each inserted element
                this.__observer__.observeArray(inserted)
            }
            
            // Call original method
            return Array.prototype[method].call(this, ...args)
        }
    });

export default arrayOverrides

package.json

{
  "name": "vue-reactivity-demo",
  "version": "1.0.0",
  "description": "A simplified implementation of Vue's reactivity system",
  "main": "index.js",
  "scripts": {
    "dev": "rollup -c -w"
  },
  "keywords": ["vue", "reactivity", "frontend"],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.22.9",
    "@babel/preset-env": "^7.22.9",
    "rollup": "^2.79.1",
    "rollup-plugin-babel": "^4.4.0",
    "rollup-plugin-serve": "^2.0.2"
  }
}

Tags: vuejs reactivity-system data-binding observer-pattern frontend-framework

Posted on Wed, 10 Jun 2026 19:01:37 +0000 by mykmallett