🕰️ Throwback Thursday: La Guerra de los Frameworks - De Prototype.js a React, la Batalla por el DOM

Los jueves exploramos la historia tech y las lecciones del pasado. Hoy recordamos las “guerras de frameworks” JavaScript que definieron una década de desarrollo frontend y las lecciones atemporales que nos dejaron.

:date: La Era Pre-Framework (2000-2007)

El Mundo Salvaje del JavaScript

Antes de React, Vue, o Angular, el desarrollo JavaScript era el “Lejano Oeste” del código. Cada proyecto inventaba sus propios patrones:

// Patrón típico de 2005 - "Código Espagueti"
var MyApp = {
    users: [],
    currentUser: null,
    
    init: function() {
        this.loadUsers();
        this.bindEvents();
    },
    
    loadUsers: function() {
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                MyApp.users = JSON.parse(xhr.responseText);
                MyApp.renderUsers();
            }
        };
        xhr.open('GET', '/api/users');
        xhr.send();
    },
    
    bindEvents: function() {
        document.getElementById('userList').onclick = function(e) {
            if (e.target.tagName === 'LI') {
                MyApp.selectUser(e.target.dataset.userId);
            }
        };
    }
};

Los Problemas Universales:

  • Gestión del Estado: Variables globales por doquier

  • Event Handling: addEventListener/attachEvent chaos

  • Template System: innerHTML string concatenation

  • Code Organization: Todo mezclado en un gran objeto

:crossed_swords: La Primera Guerra: Prototype vs jQuery (2005-2010)

Prototype.js - El Pionero Elegante

// Prototype.js - Extending native objects
$('myElement').observe('click', function(event) {
    $('content').update('New content');
    $('loading').hide();
});

// Object-oriented approach
var User = Class.create({
    initialize: function(name, email) {
        this.name = name;
        this.email = email;
    },
    
    save: function() {
        new Ajax.Request('/users', {
            method: 'post',
            parameters: this.toJSON(),
            onSuccess: this.onSave.bind(this)
        });
    }
});

Innovaciones de Prototype:

  • Extending native prototypes (String.prototype.capitalize())

  • Class system con herencia

  • AJAX helpers elegantes

  • Event delegation patterns

jQuery - El Democratizador

// jQuery - Chain everything
$('#userForm')
    .validate()
    .on('submit', function(e) {
        e.preventDefault();
        
        $.post('/users', $(this).serialize())
            .done(function(data) {
                $('#userList').append('<li>' + data.name + '</li>');
                $('#message').text('User saved!').fadeIn();
            })
            .fail(function() {
                alert('Error saving user');
            });
    });

Por qué ganó jQuery:

  • Learning curve mínima - Si sabías CSS, sabías jQuery

  • Cross-browser abstraction perfecta

  • Plugin ecosystem masivo

  • Documentation excepcional

:building_construction: La Era MVC: Backbone, Ember y Angular 1 (2010-2014)

Backbone.js - Structure Without Opinions

// Backbone - MVC pattern
var User = Backbone.Model.extend({
    urlRoot: '/api/users',
    defaults: {
        name: '',
        email: ''
    }
});

var UserView = Backbone.View.extend({
    tagName: 'li',
    
    events: {
        'click .delete': 'deleteUser',
        'click .edit': 'editUser'
    },
    
    render: function() {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    },
    
    deleteUser: function() {
        this.model.destroy();
        this.remove();
    }
});

Angular 1 - The Game Changer

// Angular 1 - Two-way data binding magic
angular.module('myApp', [])
.controller('UserController', function($scope, $http) {
    $scope.users = [];
    $scope.newUser = {};
    
    $scope.addUser = function() {
        $http.post('/api/users', $scope.newUser)
            .success(function(data) {
                $scope.users.push(data);
                $scope.newUser = {};
            });
    };
});

<!-- Template with two-way binding -->
<div ng-controller="UserController">
    <input ng-model="newUser.name" placeholder="Name">
    <input ng-model="newUser.email" placeholder="Email">
    <button ng-click="addUser()">Add User</button>
    
    <li ng-repeat="user in users">
        {{user.name}} - {{user.email}}
    </li>
</div>

Angular 1 revolucionó porque:

  • Two-way data binding eliminó la manipulación manual del DOM

  • Dependency injection profesionalizó la arquitectura

  • Directives hicieron el HTML extensible

  • Testing se volvió integral al desarrollo

:fire: La Guerra Moderna: Angular 2 vs React vs Vue (2014-2020)

React - The Component Revolution

// React - Component thinking
class UserList extends React.Component {
    constructor(props) {
        super(props);
        this.state = { 
            users: [], 
            loading: true 
        };
    }
    
    async componentDidMount() {
        const response = await fetch('/api/users');
        const users = await response.json();
        this.setState({ users, loading: false });
    }
    
    render() {
        const { users, loading } = this.state;
        
        if (loading) return <div>Loading...</div>;
        
        return (
            <ul>
                {users.map(user => (
                    <UserItem 
                        key={user.id} 
                        user={user}
                        onDelete={this.handleDelete}
                    />
                ))}
            </ul>
        );
    }
}

Vue - The Progressive Framework

<!-- Vue - Template + Script + Style in one -->
<template>
  <div>
    <input v-model="newUser.name" placeholder="Name">
    <button @click="addUser">Add User</button>
    
    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }}
        <button @click="deleteUser(user.id)">Delete</button>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  data() {
    return {
      users: [],
      newUser: { name: '', email: '' }
    }
  },
  
  async mounted() {
    const response = await fetch('/api/users');
    this.users = await response.json();
  },
  
  methods: {
    async addUser() {
      const response = await fetch('/api/users', {
        method: 'POST',
        body: JSON.stringify(this.newUser)
      });
      const user = await response.json();
      this.users.push(user);
      this.newUser = { name: '', email: '' };
    }
  }
}
</script>

:bar_chart: Then vs Now: La Evolución en Números

Lines of Code Comparison (same functionality):

Vanilla JS (2005):     ~200 lines
jQuery (2010):         ~80 lines  
Backbone (2012):       ~120 lines
Angular 1 (2014):      ~60 lines
React (2020):          ~45 lines
Vue 3 (2025):          ~35 lines

Bundle Sizes:

Prototype.js:          ~120KB
jQuery 1.x:            ~85KB
Angular 1:             ~60KB
React + ReactDOM:      ~40KB
Vue 3:                 ~35KB
Modern Vanilla:        ~0KB (native APIs)

Developer Experience Evolution:

// 2005: Manual everything
document.getElementById('button').onclick = function() {
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4) {
            document.getElementById('result').innerHTML = xhr.responseText;
        }
    };
    xhr.open('GET', '/api/data');
    xhr.send();
};

// 2025: Declarative and reactive
const [data, setData] = useState(null);

const handleClick = async () => {
    const response = await fetch('/api/data');
    const result = await response.json();
    setData(result);
};

return (
    <button onClick={handleClick}>
        {data ? data.message : 'Click me'}
    </button>
);

:bullseye: Lecciones Atemporales de las Guerras de Frameworks

1. Los Problemas Reales Siempre Ganan

Prototype era técnicamente elegante, pero jQuery resolvía dolor real (cross-browser compatibility).

Angular 1 ganó porque eliminó la manipulación manual del DOM.

React dominó porque el component model escalaba mejor que two-way binding.

2. Developer Experience > Technical Superiority

// Technically superior (Prototype)
var UserForm = Class.create({
    initialize: function(element) {
        this.element = $(element);
        this.bindEvents();
    },
    bindEvents: function() {
        this.element.observe('submit', this.handleSubmit.bind(this));
    }
});

// Better DX (jQuery)
$('#userForm').on('submit', handleSubmit);

3. Ecosystem Beats Features

jQuery no era el framework más poderoso, pero tenía:

  • Miles de plugins

  • Documentación excepcional

  • Comunidad activa

  • CDN hosting gratuito

4. Timing is Everything

Angular 1 llegó cuando SPAs se volvieron necesarias. React apareció cuando las apps se volvieron demasiado complejas para two-way binding. Vue surgió cuando developers querían algo más simple que Angular 2 pero más estructurado que React.

:crystal_ball: Patrones que Sobrevivieron

Component Architecture (from Web Components concept)

// Ideas que evolucionaron
// Web Components (2012) → React Components (2013) → Everywhere (2025)

// The pattern survived, implementations changed
const UserCard = ({ user }) => (
    <div className="user-card">
        <img src={user.avatar} alt={user.name} />
        <h3>{user.name}</h3>
        <p>{user.email}</p>
    </div>
);

Declarative over Imperative

// Imperative (old way)
function updateUserList() {
    const container = document.getElementById('userList');
    container.innerHTML = '';
    users.forEach(user => {
        const li = document.createElement('li');
        li.textContent = user.name;
        li.onclick = () => selectUser(user.id);
        container.appendChild(li);
    });
}

// Declarative (modern way)
const UserList = () => (
    <ul>
        {users.map(user => (
            <li key={user.id} onClick={() => selectUser(user.id)}>
                {user.name}
            </li>
        ))}
    </ul>
);

State Management Separation

// From jQuery spaghetti to modern state management
// jQuery (2010)
var currentUser = null;
var users = [];

// Redux pattern (2015) - still relevant
const initialState = {
    currentUser: null,
    users: []
};

const userReducer = (state = initialState, action) => {
    switch (action.type) {
        case 'SET_CURRENT_USER':
            return { ...state, currentUser: action.payload };
        default:
            return state;
    }
};

:trophy: Los Ganadores y Sus Lecciones

jQuery (2006-2016) - The Democratizer

Lección: Solve real pain points simply. Legacy: Simplified DOM manipulation became standard in all modern frameworks.

Angular 1 (2010-2018) - The Professionalizer

Lección: Structure scales better than flexibility. Legacy: Dependency injection, testing culture, MVC patterns.

React (2013-present) - The Component King

Lección: Simple concepts can solve complex problems. Legacy: Component architecture, unidirectional data flow, virtual DOM patterns.

Vue (2014-present) - The Progressive Bridge

Lección: You don’t have to choose between power and simplicity. Legacy: Progressive enhancement, single-file components, approachable APIs.

:thinking: Reflexiones para 2025

¿Qué Framework “Ganó”?

La respuesta: Todos y ninguno.

  • React domina en ecosistemas grandes y complejos

  • Vue excellente para equipos pequeños y medianos

  • Angular sigue fuerte en enterprise y aplicaciones grandes

  • Svelte está creciendo en aplicaciones que priorizan performance

  • Vanilla JS moderno es viable para muchos proyectos

Las Ideas que Perduran:

  1. Component-based architecture - De Web Components a todos los frameworks

  2. Declarative programming - Describe qué quieres, no cómo hacerlo

  3. State management separation - UI y lógica de negocio separadas

  4. Developer tooling - Hot reload, debugging, linting integrado

  5. Testing as first-class citizen - Tests no como afterthought

:thought_balloon: La Lección Meta

Cada “guerra” de frameworks nos enseñó algo fundamental: no hay soluciones perfectas, solo trade-offs apropiados para el contexto.

  • Prototype era elegant pero no pragmatic

  • jQuery era pragmatic pero no scalable

  • Angular 1 era powerful pero complex

  • React es flexible pero requires discipline

  • Vue es approachable pero less opinionated

La tecnología que “gana” no es necesariamente la mejor técnicamente, sino la que mejor balancea:

  • Solving real problems

  • Developer experience

  • Learning curve

  • Ecosystem support

  • Community adoption

:crystal_ball: ¿Cuál Será la Próxima Guerra?

Mirando hacia el futuro, los nuevos “combatientes” podrían ser:

  • Server Components vs Client-side rendering

  • Build-time optimization vs Runtime flexibility

  • Type safety vs Development speed

  • AI-assisted development vs Traditional coding

:speech_balloon: Conversación Nostálgica

¿Recuerdan su primer framework JavaScript? ¿Fue amor a primera vista o una relación complicada?

¿Cuál fue el “momento wow” que los convenció de adoptar React, Vue, Angular, o el framework que usen actualmente?

¿Qué tecnología “perdedora” creen que tenía ideas adelantadas a su tiempo? Prototype tenía concepts brillantes. Knockout.js pioneered reactive programming. Ember tenía convention over configuration.

¿Cuál creen que será el próximo gran shift en frontend development? ¿Web Components finalmente triunfarán? ¿Server-side rendering matará el SPA? ¿AI cambiará cómo escribimos componentes?

Las guerras de frameworks nos enseñaron que la evolución tecnológica no es lineal. A veces retrocedemos para avanzar mejor. A veces las ideas “perdedoras” resurgen años después en nuevas formas.

La historia nos dice que el framework que usamos hoy no será el mismo que usemos en 10 años. Pero los principios que aprendimos - component architecture, declarative programming, separation of concerns - esos sí perdurarán.

La próxima vez que elijan un framework, recuerden: no están eligiendo tecnología, están eligiendo philosophy and trade-offs.

#ThrowbackThursday javascript #Frameworks #HistoriaWeb #React #Vue #Angular #jQuery #TechEvolution #DeveloperWisdom