El mecanismo central de Vue.js se basa en la reactividad de datos y la actualización automática del DOM. A continuación se describe una implementación simplificada llamada MyVue, que replica los principios fundamentales del framework original.
- Principios básicos
La arquitectura MVVM (Model-View-ViewModel) permite la sincronización bidireccional entre el modelo de datos y la vista. Esto se logra mediante:
- Interceptación de cambios en propiedades del objeto usando
Object.defineProperty. - Patrón Observer (suscriptor-publicador) para notificar actualizaciones a las dependencias.
- Compilación del DOM para identifciar expresiones de enlace como
{{prop}}o directivas comov-model.
- Interceptación de propiedades con Object.defineProperty
Esta API nativa de JavaScript permite definir comportamientos personalizados al leer (get) o escribir (set) propiedades de un objeto:
const reactiveObj = {};
Object.defineProperty(reactiveObj, 'message', {
get() {
console.log('Leyendo valor');
return this._message;
},
set(newValue) {
console.log('Actualizando valor:', newValue);
this._message = newValue;
}
});
- Compilación del DOM
Se recorre el árbol DOM para detectar enlaces de datos. Se utilizan fragmentos de documento para manipulación eficiente:
function createFragment(rootNode, context) {
const fragment = document.createDocumentFragment();
let child;
while ((child = rootNode.firstChild)) {
processNode(child, context);
fragment.appendChild(child);
if (child.childNodes.length) {
createFragment(child, context);
}
}
return fragment;
}
- Sistema de reactividad
Cada propiedad reactiva mantiene una lista de observadores (watchers) que deben actualizarse cuando cambia su valor:
class Dependency {
constructor() {
this.watchers = [];
}
add(watcher) {
this.watchers.push(watcher);
}
notify() {
this.watchers.forEach(w => w.update());
}
}
function makeReactive(obj, key, value) {
const dep = new Dependency();
Object.defineProperty(obj, key, {
get() {
if (Dependency.currentWatcher) {
dep.add(Dependency.currentWatcher);
}
return value;
},
set(newValue) {
if (newValue === value) return;
value = newValue;
dep.notify();
}
});
}
- Observadores (Watchers)
Los watchers conectan las propiedades reactivas con nodos específicos del DOM:
class Watcher {
constructor(vm, node, property) {
Dependency.currentWatcher = this;
this.vm = vm;
this.node = node;
this.property = property;
this.update();
Dependency.currentWatcher = null;
}
update() {
this.value = this.vm[this.property];
if (this.node.nodeType === 1) {
this.node.value = this.value;
} else if (this.node.nodeType === 3) {
this.node.textContent = this.value;
}
}
}
- Implementación completa
Integrando todos los componentes en una clase principle:
class MyVue {
constructor(options) {
this.$data = options.data;
this.observe(this.$data);
const root = document.querySelector(options.el);
const fragment = this.createFragment(root.cloneNode(true));
root.innerHTML = '';
root.appendChild(fragment);
}
observe(obj) {
Object.keys(obj).forEach(key => {
makeReactive(this, key, obj[key]);
});
}
createFragment(node) {
if (node.nodeType === 1) {
const attrs = [...node.attributes];
attrs.forEach(attr => {
if (attr.name === 'v-model') {
const prop = attr.value;
node.value = this[prop];
node.addEventListener('input', e => {
this[prop] = e.target.value;
});
new Watcher(this, node, prop);
}
});
} else if (node.nodeType === 3) {
const match = node.textContent.match(/\{\{(.+?)\}\}/);
if (match) {
const prop = match[1].trim();
node.textContent = this[prop];
new Watcher(this, node, prop);
}
}
node.childNodes.forEach(child => {
this.createFragment(child);
});
return node;
}
}
- Flujo de funcionamiento
- Inicialización: Las propiedades del modelo se convierten en reactivas mediante
Object.defineProperty. - Compilación: El DOM se analiza para identificar enlaces de datos y crear watchers correspondientes.
- Dependencias: Durante la creación de watchers, se establecen relaciones entre propiedades y nodos del DOM.
- Actualización: Al modificar una propiedad, se notifica a todos sus watchers para actualizar la vista.