Introducción
Se acerca una revolución. Hay una nueva incorporación a JavaScript que cambiará todo lo que crees saber sobre la vinculación de datos. También cambiará la cantidad de bibliotecas de MVC que se acercan a los modelos de observación para realizar ediciones y actualizaciones. ¿Estás listo para obtener mejoras de rendimiento en las apps que se preocupan por la observación de propiedades?
Muy bien. Sin más demoras, me complace anunciar que Object.observe()
llegó a la versión estable de Chrome 36. [WOOOO. THE CROWD GOES WILD].
Object.observe()
, que forma parte de un futuro estándar de ECMAScript, es un método para observar de forma asíncrona los cambios en los objetos de JavaScript… sin necesidad de una biblioteca independiente. Permite que un observador reciba una secuencia ordenada en el tiempo de registros de cambios que describen el conjunto de cambios que se produjeron en un conjunto de objetos observados.
// Let's say we have a model with data
var model = {};
// Which we then observe
Object.observe(model, function(changes){
// This asynchronous callback runs
changes.forEach(function(change) {
// Letting us know what changed
console.log(change.type, change.name, change.oldValue);
});
});
Cada vez que se realiza un cambio, se informa lo siguiente:

Con Object.observe()
(me gusta llamarlo O.o() o Oooooooo), puedes implementar la vinculación de datos de dos vías sin necesidad de un framework.
Eso no significa que no debas usar uno. Para proyectos grandes con una lógica empresarial complicada, los frameworks con opiniones son inestimables y debes seguir usándolos. Simplifican la orientación de los desarrolladores nuevos, requieren menos mantenimiento de código y, además, imponen patrones sobre cómo realizar tareas comunes. Cuando no lo necesites, puedes usar bibliotecas más pequeñas y enfocadas, como Polymer (que ya aprovechan O.o()).
Incluso si usas mucho un framework o una biblioteca de MV*, O.o() tiene el potencial de proporcionarles algunas mejoras de rendimiento saludables, con una implementación más rápida y sencilla, y manteniendo la misma API. Por ejemplo, el año pasado, Angular descubrió que, en una comparativa en la que se realizaban cambios en un modelo, la verificación de estado no sincronizado tardaba 40 ms por actualización y O.o() tardaba entre 1 y 2 ms por actualización (una mejora de entre 20 y 40 veces más rápida).
La vinculación de datos sin necesidad de toneladas de código complicado también significa que ya no tienes que sondear los cambios, lo que aumenta la duración de la batería.
Si ya te decidiste por O.o(), avanza a la introducción de la función o sigue leyendo para obtener más información sobre los problemas que resuelve.
¿Qué queremos observar?
Cuando hablamos de observación de datos, por lo general, nos referimos a estar atentos a algunos tipos específicos de cambios:
- Cambios en los objetos de JavaScript sin procesar
- Cuándo se agregan, cambian o borran propiedades
- Cuando los arrays tienen elementos intercalados dentro y fuera de ellos
- Cambios en el prototipo del objeto
La importancia de la vinculación de datos
La vinculación de datos comienza a ser importante cuando te preocupa la separación del control de modelo-vista. HTML es un excelente mecanismo declarativo, pero es completamente estático. Idealmente, solo debes declarar la relación entre tus datos y el DOM, y mantener el DOM actualizado. Esto crea una ventaja y te ahorra mucho tiempo escribiendo código realmente repetitivo que solo envía datos desde y hacia el DOM entre el estado interno de tu aplicación o el servidor.
La vinculación de datos es especialmente útil cuando tienes una interfaz de usuario compleja en la que necesitas conectar relaciones entre varias propiedades de tus modelos de datos con varios elementos de tus vistas. Esto es bastante común en las aplicaciones de una sola página que estamos compilando hoy.
Cuando integramos una forma de observar datos de forma nativa en el navegador, les damos a los frameworks de JavaScript (y a las pequeñas bibliotecas de utilidad que escribes) una forma de observar los cambios en los datos de modelos sin depender de algunos de los hacks lentos que se usan en la actualidad.
Cómo se ve el mundo hoy
Verificación de estado sucio
¿Dónde has visto la vinculación de datos antes? Bueno, si usas una biblioteca MV* moderna para compilar tus aplicaciones web (p. ej., Angular o Knockout), es probable que estés acostumbrado a vincular los datos del modelo al DOM. A modo de repaso, este es un ejemplo de una app de lista de teléfonos en la que vinculamos el valor de cada teléfono en un array phones
(definido en JavaScript) a un elemento de lista para que nuestros datos y la IU estén siempre sincronizados:
<html ng-app>
<head>
...
<script src='angular.js'></script>
<script src='controller.js'></script>
</head>
<body ng-controller='PhoneListCtrl'>
<ul>
<li ng-repeat='phone in phones'>
<p></p>
</li>
</ul>
</body>
</html>
y el código JavaScript para el controlador:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM',
'snippet': 'The Next, Next Generation tablet.'}
];
});
Cada vez que cambian los datos del modelo subyacente, se actualiza nuestra lista en el DOM. ¿Cómo lo logra Angular? Bueno, en segundo plano, realiza algo llamado verificación de estado no sincronizado.

La idea básica de la verificación de estado "dirty" es que, cada vez que los datos podrían haber cambiado, la biblioteca debe verificar si realmente cambiaron a través de un resumen o un ciclo de cambio. En el caso de Angular, un ciclo de resumen identifica todas las expresiones registradas para que se supervisen y ver si hay un cambio. Conoce los valores anteriores de un modelo y, si cambiaron, se activa un evento de cambio. Para un desarrollador, el beneficio principal es que puedes usar datos de objetos de JavaScript sin procesar, que son agradables de usar y se componen bastante bien. La desventaja es que tiene un comportamiento algorítmico deficiente y puede ser muy costoso.

El gasto de esta operación es proporcional a la cantidad total de objetos observados. Es posible que deba hacer muchas verificaciones de errores. También es posible que necesites una forma de activar la verificación de estado no sincronizado cuando los datos puedan haber cambiado. Existen muchos trucos inteligentes que usan los frameworks para esto. No está claro si esto alguna vez será perfecto.
El ecosistema web debería tener más capacidad para innovar y evolucionar sus propios mecanismos declarativos, p.ej.:
- Sistemas de modelos basados en restricciones
- Sistemas de persistencia automática (p. ej., cambios persistentes en IndexedDB o localStorage)
- Objetos de contenedor (Ember, Backbone)
Los objetos contenedor son donde un framework crea objetos que contienen los datos en su interior. Tienen acceso a los datos y pueden capturar lo que configuras o obtienes y transmitirlo de forma interna. Esto funciona bien. Tiene un rendimiento relativamente bueno y un buen comportamiento algorítmico. A continuación, se muestra un ejemplo de objetos de contenedor que usan Ember:
// Container objects
MyApp.president = Ember.Object.create({
name: "Barack Obama"
});
MyApp.country = Ember.Object.create({
// ending a property with "Binding" tells Ember to
// create a binding to the presidentName property
presidentNameBinding: "MyApp.president.name"
});
// Later, after Ember has resolved bindings
MyApp.country.get("presidentName");
// "Barack Obama"
// Data from the server needs to be converted
// Composes poorly with existing code
El costo de descubrir qué cambió aquí es proporcional a la cantidad de elementos que cambiaron. Otro problema es que ahora estás usando este tipo de objeto diferente. En términos generales, debes convertir los datos que obtienes del servidor a estos objetos para que sean observables.
Esto no se compone muy bien con el código JS existente porque la mayoría de los códigos suponen que pueden operar en datos sin procesar. No para estos tipos de objetos especializados.
Introducing Object.observe()
Lo ideal es obtener lo mejor de ambos mundos: una forma de observar datos con compatibilidad con objetos de datos sin procesar (objetos de JavaScript normales) si así lo deseamos Y sin necesidad de verificar todo todo el tiempo. Algo con un buen comportamiento algorítmico. Algo que se componga bien y esté integrado en la plataforma. Esta es la belleza de lo que Object.observe()
aporta.
Nos permite observar un objeto, mutar propiedades y ver el informe de cambios de lo que cambió. Pero basta de teoría, veamos un poco de código.

Object.observe() y Object.unobserve()
Supongamos que tenemos un objeto JavaScript simple que representa un modelo:
// A model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
Luego, podemos especificar una devolución de llamada para cada vez que se realicen mutaciones (cambios) en el objeto:
function observer(changes){
changes.forEach(function(change, i){
console.log('what property changed? ' + change.name);
console.log('how did it change? ' + change.type);
console.log('whats the current value? ' + change.object[change.name]);
console.log(change); // all changes
});
}
Luego, podemos observar estos cambios con O.o(), pasando el objeto como primer argumento y la devolución de llamada como segundo:
Object.observe(todoModel, observer);
Comencemos a hacer algunos cambios en nuestro objeto de modelo de Todos:
todoModel.label = 'Buy some more milk';
Si observamos la consola, obtenemos información útil. Sabemos qué propiedad cambió, cómo se cambió y cuál es el valor nuevo.

¡Bravo! Adiós, verificación de estado no sincronizado. Tu lápida debe estar tallada en Comic Sans. Cambiemos otra propiedad. Esta vez, completeBy
:
todoModel.completeBy = '01/01/2014';
Como podemos ver, nuevamente recibimos correctamente un informe de cambios:

Muy bien. ¿Qué sucede si ahora decidimos borrar la propiedad "completed" de nuestro objeto?
delete todoModel.completed;

Como podemos ver, el informe de cambios que se muestra incluye información sobre la eliminación. Como se esperaba, el valor nuevo de la propiedad ahora no está definido. Ahora sabemos que puedes saber cuándo se agregaron propiedades. Cuando se hayan borrado. Básicamente, el conjunto de propiedades en un objeto ("nuevo", "eliminado", "reconfigurado") y su prototipo cambia (proto).
Como en cualquier sistema de observación, también existe un método para dejar de escuchar los cambios. En este caso, es Object.unobserve()
, que tiene la misma firma que O.o(), pero se puede llamar de la siguiente manera:
Object.unobserve(todoModel, observer);
Como podemos ver a continuación, cualquier mutación que se realice en el objeto después de que se ejecute ya no generará una lista de registros de cambios.

Especifica los cambios de interés
Así, analizamos los conceptos básicos para obtener una lista de cambios en un objeto observado. ¿Qué sucede si solo te interesa un subconjunto de los cambios que se realizaron en un objeto en lugar de todos ellos? Todos necesitan un filtro de spam. Los observadores pueden especificar solo los tipos de cambios que desean conocer a través de una lista de aceptación. Esto se puede especificar con el tercer argumento de O.o() de la siguiente manera:
Object.observe(obj, callback, optAcceptList)
Veamos un ejemplo de cómo se puede usar esto:
// Like earlier, a model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
// We then specify a callback for whenever mutations
// are made to the object
function observer(changes){
changes.forEach(function(change, i){
console.log(change);
})
};
// Which we then observe, specifying an array of change
// types we're interested in
Object.observe(todoModel, observer, ['delete']);
// without this third option, the change types provided
// default to intrinsic types
todoModel.label = 'Buy some milk';
// note that no changes were reported
Sin embargo, si ahora borramos la etiqueta, observa que este tipo de cambio sí se informa:
delete todoModel.label;
Si no especificas una lista de tipos de aceptación para O.o(), se usarán de forma predeterminada los tipos de cambio de objetos "intrínsecos" (add
, update
, delete
, reconfigure
, preventExtensions
(para cuando no se puede observar que un objeto se vuelve no extensible)).
Notificaciones
O.o() también incluye la noción de notificaciones. No son como esas cosas molestas que recibes en un teléfono, sino que son bastante útiles. Las notificaciones son similares a los observadores de mutaciones. Ocurren al final de la microtarea. En el contexto del navegador, esto casi siempre estará al final del controlador de eventos actual.
El momento es bueno porque, por lo general, se termina una unidad de trabajo y ahora los observadores pueden hacer su trabajo. Es un buen modelo de procesamiento por turnos.
El flujo de trabajo para usar un notificador se ve de la siguiente manera:

Veamos un ejemplo de cómo se pueden usar los notificadores en la práctica para definir notificaciones personalizadas para cuando se obtienen o establecen propiedades en un objeto. Consulta los comentarios aquí:
// Define a simple model
var model = {
a: {}
};
// And a separate variable we'll be using for our model's
// getter in just a moment
var _b = 2;
// Define a new property 'b' under 'a' with a custom
// getter and setter
Object.defineProperty(model.a, 'b', {
get: function () {
return _b;
},
set: function (b) {
// Whenever 'b' is set on the model
// notify the world about a specific type
// of change being made. This gives you a huge
// amount of control over notifications
Object.getNotifier(this).notify({
type: 'update',
name: 'b',
oldValue: _b
});
// Let's also log out the value anytime it gets
// set for kicks
console.log('set', b);
_b = b;
}
});
// Set up our observer
function observer(changes) {
changes.forEach(function (change, i) {
console.log(change);
})
}
// Begin observing model.a for changes
Object.observe(model.a, observer);

Aquí, informamos cuando cambia el valor de las propiedades de datos ("actualización"). Cualquier otro elemento que la implementación del objeto decida informar (notifier.notifyChange()
).
Años de experiencia en la plataforma web nos enseñaron que un enfoque síncrono es lo primero que debes probar porque es lo más fácil de entender. El problema es que crea un modelo de procesamiento fundamentalmente peligroso. Si escribes código y, por ejemplo, actualizas la propiedad de un objeto, no quieres que se actualice la propiedad de ese objeto y que se invite a algún código arbitrario a hacer lo que quiera. No es ideal que se invaliden tus suposiciones mientras ejecutas una función.
Si eres observador, lo ideal es que no te llamen si alguien está en medio de una tarea. No quieres que se te pida que trabajes en un estado inconsistente del mundo. Realiza muchas más verificaciones de errores. Intenta tolerar muchas más situaciones negativas y, en general, es un modelo difícil con el que trabajar. Es más difícil de abordar, pero es un mejor modelo al final del día.
La solución a este problema son los registros de cambios sintéticos.
Registros de cambios sintéticos
Básicamente, si quieres tener accesores o propiedades calculadas, es tu responsabilidad notificar cuando estos valores cambien. Es un poco de trabajo adicional, pero está diseñado como una especie de función de primera clase de este mecanismo, y estas notificaciones se entregarán con el resto de las notificaciones de los objetos de datos subyacentes. De las propiedades de los datos

La observación de los accesorios y las propiedades computadas se puede resolver con notifier.notify, otra parte de O.o(). La mayoría de los sistemas de observación desean observar algún tipo de valor derivado. Existen muchas formas de hacerlo. O.o no emite juicios sobre la forma “correcta”. Las propiedades calculadas deben ser accesores que notifiquen cuando cambie el estado interno (privado).
Una vez más, los desarrolladores web deben esperar que las bibliotecas ayuden a facilitar las notificaciones y los diversos enfoques de las propiedades calculadas (y reducir el código estándar).
Configúralo para el siguiente ejemplo, que es una clase de círculo. La idea aquí es que tenemos este círculo y hay una propiedad de radio. En este caso, el radio es un accesor y, cuando cambie su valor, notificará por sí mismo que el valor cambió. Se publicará junto con todos los demás cambios en este objeto o en cualquier otro. En esencia, si implementas un objeto, quieres tener propiedades sintéticas o calculadas, o bien debes elegir una estrategia para saber cómo funcionará. Una vez que lo hagas, se ajustará a todo el sistema.
Omite el código para ver cómo funciona en DevTools.
function Circle(r) {
var radius = r;
var notifier = Object.getNotifier(this);
function notifyAreaAndRadius(radius) {
notifier.notify({
type: 'update',
name: 'radius',
oldValue: radius
})
notifier.notify({
type: 'update',
name: 'area',
oldValue: Math.pow(radius * Math.PI, 2)
});
}
Object.defineProperty(this, 'radius', {
get: function() {
return radius;
},
set: function(r) {
if (radius === r)
return;
notifyAreaAndRadius(radius);
radius = r;
}
});
Object.defineProperty(this, 'area', {
get: function() {
return Math.pow(radius, 2) * Math.PI;
},
set: function(a) {
r = Math.sqrt(a/Math.PI);
notifyAreaAndRadius(radius);
radius = r;
}
});
}
function observer(changes){
changes.forEach(function(change, i){
console.log(change);
})
}

Propiedades de acceso
Nota breve sobre las propiedades de acceso Anteriormente, mencionamos que solo los cambios de valor son observables para las propiedades de datos. No es para propiedades calculadas ni para accesores. El motivo es que JavaScript no tiene la noción de cambios en el valor de los accesores. Un accesor es solo una colección de funciones.
Si asignas a un accessor, JavaScript solo invoca la función allí y, desde su punto de vista, no cambió nada. Solo le dio la oportunidad de ejecutarse a un código.
El problema es que, semánticamente, podemos observar nuestra asignación anterior al valor -5. Deberíamos poder saber qué sucedió aquí. En realidad, es un problema sin solución. En el ejemplo, se muestra por qué. Realmente, no hay forma de que ningún sistema sepa a qué se refiere esto, ya que puede ser un código arbitrario. En este caso, puede hacer lo que quiera. Actualiza el valor cada vez que se accede a él, por lo que no tiene mucho sentido preguntar si cambió.
Observa varios objetos con una devolución de llamada
Otro patrón posible con O.o() es la noción de un solo observador de devolución de llamada. Esto permite que se use una sola devolución de llamada como "observador" para muchos objetos diferentes. La devolución de llamada recibirá el conjunto completo de cambios en todos los objetos que observe al "final de la microtarea" (observa la similitud con los observadores de mutación).

Cambios a gran escala
Tal vez estés trabajando en una app muy grande y debas trabajar con cambios a gran escala con frecuencia. Es posible que los objetos deseen describir cambios semánticos más grandes que afectarán a muchas propiedades de una manera más compacta (en lugar de transmitir toneladas de cambios de propiedades).
O.o() ayuda con esto en forma de dos utilidades específicas: notifier.performChange()
y notifier.notify()
, que ya presentamos.

Veamos esto en un ejemplo de cómo se pueden describir los cambios a gran escala en los que definimos un objeto Thingy con algunas utilidades matemáticas (multiplicar, incrementar, incrementarYmultiplicar). Cada vez que se usa una utilidad, se le indica al sistema que una colección de trabajo comprende un tipo específico de cambio.
Por ejemplo: notifier.performChange('foo', performFooChangeFn);
function Thingy(a, b, c) {
this.a = a;
this.b = b;
}
Thingy.MULTIPLY = 'multiply';
Thingy.INCREMENT = 'increment';
Thingy.INCREMENT_AND_MULTIPLY = 'incrementAndMultiply';
Thingy.prototype = {
increment: function(amount) {
var notifier = Object.getNotifier(this);
// Tell the system that a collection of work comprises
// a given changeType. e.g
// notifier.performChange('foo', performFooChangeFn);
// notifier.notify('foo', 'fooChangeRecord');
notifier.performChange(Thingy.INCREMENT, function() {
this.a += amount;
this.b += amount;
}, this);
notifier.notify({
object: this,
type: Thingy.INCREMENT,
incremented: amount
});
},
multiply: function(amount) {
var notifier = Object.getNotifier(this);
notifier.performChange(Thingy.MULTIPLY, function() {
this.a *= amount;
this.b *= amount;
}, this);
notifier.notify({
object: this,
type: Thingy.MULTIPLY,
multiplied: amount
});
},
incrementAndMultiply: function(incAmount, multAmount) {
var notifier = Object.getNotifier(this);
notifier.performChange(Thingy.INCREMENT_AND_MULTIPLY, function() {
this.increment(incAmount);
this.multiply(multAmount);
}, this);
notifier.notify({
object: this,
type: Thingy.INCREMENT_AND_MULTIPLY,
incremented: incAmount,
multiplied: multAmount
});
}
}
Luego, definimos dos observadores para nuestro objeto: uno que es un todo incluido para los cambios y otro que solo informará sobre los tipos de aceptación específicos que definimos (Thingy.INCREMENT, Thingy.MULTIPLY, Thingy.INCREMENT_AND_MULTIPLY).
var observer, observer2 = {
records: undefined,
callbackCount: 0,
reset: function() {
this.records = undefined;
this.callbackCount = 0;
},
};
observer.callback = function(r) {
console.log(r);
observer.records = r;
observer.callbackCount++;
};
observer2.callback = function(r){
console.log('Observer 2', r);
}
Thingy.observe = function(thingy, callback) {
// Object.observe(obj, callback, optAcceptList)
Object.observe(thingy, callback, [Thingy.INCREMENT,
Thingy.MULTIPLY,
Thingy.INCREMENT_AND_MULTIPLY,
'update']);
}
Thingy.unobserve = function(thingy, callback) {
Object.unobserve(thingy);
}
Ahora podemos comenzar a jugar con este código. Definimos un nuevo elemento Thingy:
var thingy = new Thingy(2, 4);
Observa el resultado y, luego, realiza algunos cambios. ¡Dios mío! Es muy divertido. ¡Hay tantas cosas!
// Observe thingy
Object.observe(thingy, observer.callback);
Thingy.observe(thingy, observer2.callback);
// Play with the methods thingy exposes
thingy.increment(3); // { a: 5, b: 7 }
thingy.b++; // { a: 5, b: 8 }
thingy.multiply(2); // { a: 10, b: 16 }
thingy.a++; // { a: 11, b: 16 }
thingy.incrementAndMultiply(2, 2); // { a: 26, b: 36 }

Todo lo que se encuentra dentro de la "función de ejecución" se considera el trabajo de "cambio importante". Los observadores que acepten "cambio importante" solo recibirán el registro de "cambio importante". Los observadores que no lo hagan recibirán los cambios subyacentes que se generaron a partir del trabajo que realizó "Perform function".
Observa arrays
Hablamos durante un tiempo sobre la observación de cambios en los objetos, pero ¿qué sucede con los arrays? Muy buena pregunta. Cuando alguien me dice: "Buena pregunta". Nunca escucho su respuesta porque estoy ocupado felicitándome por haber hecho una pregunta tan buena, pero eso es otra historia. También tenemos nuevos métodos para trabajar con arrays.
Array.observe()
es un método que trata los cambios a gran escala en sí mismo, por ejemplo, la unión, el desplazamiento o cualquier elemento que cambie implícitamente su longitud, como un registro de cambios de "unión". De forma interna, usa notifier.performChange("splice",...)
.
Este es un ejemplo en el que observamos un "array" de modelos y, de manera similar, recuperamos una lista de cambios cuando hay algún cambio en los datos subyacentes:
var model = ['Buy some milk', 'Learn to code', 'Wear some plaid'];
var count = 0;
Array.observe(model, function(changeRecords) {
count++;
console.log('Array observe', changeRecords, count);
});
model[0] = 'Teach Paul Lewis to code';
model[1] = 'Channel your inner Paul Irish';

Rendimiento
La forma de pensar en el impacto del rendimiento computacional de O.o() es pensar en él como una caché de lectura. En términos generales, una caché es una excelente opción en los siguientes casos (en orden de importancia):
- La frecuencia de las operaciones de lectura domina la frecuencia de las operaciones de escritura.
- Puedes crear una caché que intercambie la cantidad constante de trabajo que se realiza durante las operaciones de escritura por un mejor rendimiento algorítmico durante las operaciones de lectura.
- Se acepta la ralentización constante de las operaciones de escritura.
O.o() está diseñado para casos de uso como 1).
La verificación de estado no sincronizado requiere mantener una copia de todos los datos que observas. Esto significa que incurrimos en un costo de memoria estructural para la verificación de estado que no se obtiene con O.o(). Si bien la verificación de estado es una solución provisional decente, también es una abstracción con fugas fundamentales que puede crear una complejidad innecesaria para las aplicaciones.
¿Por qué? Bueno, la verificación de estado debe ejecutarse cada vez que puedan haber cambiado los datos. Simplemente, no hay una forma muy sólida de hacerlo, y cualquier enfoque tiene desventajas significativas (p. ej., verificar un intervalo de sondeo genera artefactos visuales y condiciones de carrera entre problemas de código). La verificación de estado no sincronizado también requiere un registro global de observadores, lo que crea peligros de fugas de memoria y costos de desmantelamiento que O.o() evita.
Veamos algunos números.
Las siguientes pruebas de comparativa (disponibles en GitHub) nos permiten comparar la verificación de estado no sincronizado con O.o(). Están estructuradas como gráficos de tamaño del conjunto de objetos observados frente a la cantidad de mutaciones. El resultado general es que el rendimiento de la verificación de estado "dirty" es proporcional al algoritmo de la cantidad de objetos observados, mientras que el rendimiento de O.o() es proporcional a la cantidad de mutaciones que se realizaron.
Verificación de estado sucio

Chrome con Object.observe() activado

Polyfilling Object.observe()
Genial. O.o() se puede usar en Chrome 36, pero ¿qué sucede con otros navegadores? Tenemos lo que necesitas. Observe-JS de Polymer es un polyfill para O.o(), que usará la implementación nativa si está presente, pero, de lo contrario, lo polyfillará y, además, incluirá algunos elementos útiles. Ofrece una vista agregada del mundo que resume los cambios y genera un informe de lo que cambió. Dos funciones realmente potentes que expone son las siguientes:
- Puedes observar las rutas. Esto significa que puedes decir: "Me gustaría observar "foo.bar.baz" desde un objeto determinado", y te indicará cuándo cambió el valor en esa ruta. Si no se puede acceder a la ruta, se considera que el valor no está definido.
Ejemplo de observación de un valor en una ruta de un objeto determinado:
var obj = { foo: { bar: 'baz' } };
var observer = new PathObserver(obj, 'foo.bar');
observer.open(function(newValue, oldValue) {
// respond to obj.foo.bar having changed value.
});
- Te informará sobre las uniones de arrays. Los empalmes de arrays son básicamente el conjunto mínimo de operaciones de empalme que deberás realizar en un array para transformar la versión anterior del array en la nueva. Este es un tipo de transformación o una vista diferente del array. Es la cantidad mínima de trabajo que debes realizar para pasar del estado anterior al nuevo.
Ejemplo de cómo informar cambios en un array como un conjunto mínimo de uniones:
var arr = [0, 1, 2, 4];
var observer = new ArrayObserver(arr);
observer.open(function(splices) {
// respond to changes to the elements of arr.
splices.forEach(function(splice) {
splice.index; // index position that the change occurred.
splice.removed; // an array of values representing the sequence of elements which were removed
splice.addedCount; // the number of elements which were inserted.
});
});
Frameworks y Object.observe()
Como se mencionó, O.o() les dará a los frameworks y las bibliotecas una gran oportunidad para mejorar el rendimiento de su vinculación de datos en los navegadores que admiten la función.
Yehuda Katz y Erik Bryn de Ember confirmaron que agregar compatibilidad con O.o() está en la planificación a corto plazo de Ember. Misko Hervy de Angular escribió una documentación de diseño sobre la detección de cambios mejorada de Angular 2.0. Su enfoque a largo plazo será aprovechar Object.observe() cuando llegue a la versión estable de Chrome y optar por Watchtower.js, su propio enfoque de detección de cambios hasta entonces. Es muy emocionante.
Conclusiones
O.o() es una incorporación potente a la plataforma web que puedes usar hoy mismo.
Esperamos que, con el tiempo, la función llegue a más navegadores, lo que permitirá que los frameworks de JavaScript obtengan mejoras de rendimiento a partir del acceso a las capacidades de observación de objetos nativos. Los usuarios que se orienten a Chrome deberían poder usar O.o() en Chrome 36 (y versiones posteriores), y la función también debería estar disponible en una versión futura de Opera.
Así que, habla con los autores de los frameworks de JavaScript sobre Object.observe()
y cómo planean usarlo para mejorar el rendimiento de la vinculación de datos en tus apps. Sin duda, te esperan momentos emocionantes.
Recursos
- Object.observe() en la wiki de Harmony>
- Databinding con Object.observe() de Rick Waldron
- Todo lo que querías saber sobre Object.observe() - JSConf
- Por qué Object.observe() es la mejor función de ES7
Agradecemos a Rafael Weinstein, Jake Archibald, Eric Bidelman, Paul Kinlan y Vivian Cromwell por sus comentarios y revisiones.