O ECMAScript 6 (codinome Harmony ou ES.next) já não é novidade para quem acompanha de perto os avanços do desenvolvimento web, e suas especificações já bem avançadas causam um certo alvoroço a respeito de como vais ser o futuro do Javascript.
Uma das esperadas especificações é o Object.observe, que documenta a capacidade de observar e notificar aplicações sobre as mudanças ocorridas em objetos Javascript, recurso bastante requisitado em aplicações funcionais.
O uso de funcionalidades similares já é possível com algumas bibliotecas como o Watch.js e também por frameworks como o Backbone (além de outros), permitindo a criação de mecanismos declarativos e independentes das demais implementações da aplicação.
Para começar, precisamos entender o que esta funcionalidade é capaz de observar um objeto:
- As mudanças no valor das propriedades;
- A adição, exclusão e reconfiguração de todas as propriedades visíveis através das APIs de reflexão;
- Mudanças no protótipo e extensibilidade do próprio objeto.
A API pública atual disponibiliza as seguintes métodos:
Object.observe(Object, callback[, accepts])
Trata-se do método principal da API que é responsável pela funcionalidade de observar um objeto e sua assinatura é bastante simples, sendo necessário apenas informar o objeto a ser observado, a função a ser executada quando alguma notificação for disparada e opcionalmente um vetor de tipos de notificações a serem manipuladas (os tipos de notificações permitidas são: ”add”, “update”, “delete”, “reconfigure”, “setPrototype” e “preventExtensions”); Esta chamada pode ser executada quantas vezes for necessário (você pode querer executar callbacks distintos).
Sua utilização segue o seguinte exemplo:
function observer(regs) { console.log(regs); } var obj = { id: 1 }; Object.observe(obj, observer); obj.a = 'b'; obj.id++;
A saída desta rotina, desconsiderando as propriedades herdadas pelo argumento regs, seria uma lista de objetos de notificações:
> [{ name: "a", object: {id: 2, a: "b"}, oldValue: "b", type: "updated" },{ name: "id", object: {id: 2, a: "b"}, type: "new" }]
Object.unobserve(Object, callback)
Eventualmente, poderá ser necessário não acompanhar mais as notificações de um determinado objeto, ou apenas interromper a execução de um callback especifico; Para isso, basta executar conforme o exemplo, especificando o objeto observado e o callback a ser removido:
function updateObserver(regs) { regs.forEach(function(notification){ if(notification.type === "updated"){ console.log(notification); } }); } function deleteObserver(regs) { regs.forEach(function(notification){ if(notification.type === "deleted"){ console.log(notification); } }); } var obj = { id: 1 }; Object.observe(obj, updateObserver); Object.observe(obj, deleteObserver); obj.id++; Object.unobserve(obj, deleteObserver); delete obj.id;
A saída desta rotina, também seria uma lista de objetos de notificações:
> [{ name: "id", object: {}, oldValue: 1, type: "updated" }]
Array.observe(Object, callback)
É apenas um atalho, equivalente a:
function(obj, callback) { return Object.observe(obj, callback, ["add", "update", "delete", "splice"]); }
Array.unobserve(Object, callback)
Outro atalho, equivalente a:
function(obj, callback) { return Object.unobserve(obj, callback); }
Object.deliverChangeRecords
Se notarmos o primeiro exemplo utilizado no método Object.observe, é possível perceber que a lista de notificações foi entregue de forma acumulada em um vetor, ou seja, no final de todas as alterações. Caso seja necessário intervir de forma antecipada o método Object.deliverChangeRecords garante uma chamada imediata do callback especificado (necessita ser um callback já aplicado) com uma lista das notificações pendentes:
function observer(regs) { console.log(regs); } var obj = { id: 1 }; Object.observe(obj, observer); obj.a = 'b'; obj.id++; console.log('Primeira chamada'); Object.deliverChangeRecords(observer); obj.b = 'c'; console.log('Segunda chamada'); Object.deliverChangeRecords(observer);
Esta execução revelaria duas pilhas de notificações:
> Primeira chamada > [{ name: "a", object: { a: "b", b: "c", id: 2 }, type: "new" },{ name: "id" object: { a: "b", b: "c", id: 2 } oldValue: 1 type: "updated" }] > Segunda chamada > [{ name: "b", object: {a: "b", b: "c"}, id: 2, type: "new" }]
Object.getNotifier
Permite recuperar o objeto notificador e forçar uma notificação através do método notify():
var obj = { id: 1 }; function basicObserver(changeList){ changeList.forEach(function(change){ console.log(change); }); } Object.observe(obj,basicObserver); Object.getNotifier(obj).notify({ type: "new", name: "id" });
É importante observarmos que implementações nativas tendem a ter um desempenho superior às implementações customizadas (ex. frameworks), e considerando este como apenas um dos aspectos das futuras implementações do ECMAScript 6 já podemos afirmar que elas representam um passo importante para a maturidade do desenvolvimento web.
Caso você não queira aguardar sentado o Javascript Harmony existem algumas alternativas como o Object.observe (polyfill/shim) ou a utilização do Chrome Canary com a flag “Enable Experimental JS APIs” ativada.