Introdução
Uma revolução está chegando. Uma nova adição ao JavaScript vai mudar tudo o que você acha que sabe sobre vinculação de dados. Isso também vai mudar a forma como muitas das suas bibliotecas MVC abordam a observação de modelos para edições e atualizações. Tudo pronto para melhorar o desempenho dos apps que se preocupam com a observação de propriedades?
OK. Ok. Sem mais atrasos, fico feliz em anunciar que o Object.observe()
chegou à versão estável do Chrome 36. [Que legal! THE CROWD GOES WILD].
Object.observe()
, parte de um futuro padrão ECMAScript, é um método para observar de forma assíncrona as mudanças em objetos JavaScript sem a necessidade de uma biblioteca separada. Ele permite que um observador receba uma sequência ordenada no tempo de registros de mudanças que descrevem o conjunto de mudanças que ocorreram em um 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);
});
});
Sempre que uma alteração é feita, ela é informada:
Com a Object.observe()
(eu gosto de chamá-la de O.o() ou Oooooooo), é possível implementar a vinculação de dados bidirecional sem a necessidade de um framework.
Isso não quer dizer que você não deve usar uma. Para projetos grandes com lógica de negócios complicada, os frameworks opinativos são inestimáveis e você deve continuar usando. Eles simplificam a orientação de novos desenvolvedores, exigem menos manutenção de código e impõem padrões sobre como realizar tarefas comuns. Se você não precisar de uma, use bibliotecas menores e mais específicas, como a Polymer, que já usam a O.o().
Mesmo que você use muito um framework ou biblioteca MV*, o O.o() tem o potencial de fornecer algumas melhorias de desempenho saudáveis, com uma implementação mais rápida e simples, mantendo a mesma API. Por exemplo, no ano passado, o Angular descobriu que, em um comparativo de mercado em que mudanças estavam sendo feitas em um modelo, a verificação de estado sujo levava 40 ms por atualização, e a O.o() levava de 1 a 2 ms por atualização (uma melhoria de 20 a 40 vezes mais rápida).
A vinculação de dados sem a necessidade de muitos códigos complicados também significa que você não precisa mais procurar alterações, o que prolonga a duração da bateria!
Se você já usou o O.o(), pule para a introdução do recurso ou leia mais sobre os problemas que ele resolve.
O que queremos observar?
Quando se trata de observação de dados, geralmente estamos nos referindo a ficar atento a alguns tipos específicos de alterações:
- Mudanças em objetos JavaScript brutos
- Quando as propriedades são adicionadas, alteradas e excluídas
- Quando as matrizes têm elementos separados
- Mudanças no protótipo do objeto
A importância da vinculação de dados
A vinculação de dados começa a se tornar importante quando você se preocupa com a separação de controle de modelo-visualização. HTML é um ótimo mecanismo declarativo, mas é completamente estático. O ideal é declarar apenas a relação entre seus dados e o DOM e mantê-lo atualizado. Isso cria alavancagem e economiza muito tempo na criação de código muito repetitivo que apenas envia dados de e para o DOM entre o estado interno do aplicativo ou o servidor.
A vinculação de dados é particularmente útil quando se tem uma interface de usuário complexa em que é preciso conectar relações entre várias propriedades nos modelos de dados com vários elementos nas visualizações. Isso é muito comum nos aplicativos de página única que estamos criando hoje.
Ao criar uma maneira de observar dados de forma nativa no navegador, oferecemos aos frameworks JavaScript (e pequenas bibliotecas de utilitários que você escreve) uma maneira de observar mudanças nos dados do modelo sem depender de alguns dos hacks lentos que o mundo usa hoje.
Como o mundo está hoje
Verificação de estado
Onde você já viu a vinculação de dados? Se você usa uma biblioteca MV* moderna para criar seus apps da Web (por exemplo, Angular, Knockout), provavelmente já vinculou dados de modelo ao DOM. Confira um exemplo de app de lista de telefones em que vinculamos o valor de cada smartphone em uma matriz phones
(definida em JavaScript) a um item da lista para que os dados e a interface estejam sempre 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>
e o JavaScript para o 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.'}
];
});
Toda vez que os dados do modelo subjacente mudam, nossa lista no DOM é atualizada. Como o Angular faz isso? Nos bastidores, ele faz algo chamado "verificação suja".
A ideia básica da verificação de integridade é que, sempre que os dados podem ter mudado, a biblioteca precisa verificar se isso aconteceu por meio de um resumo ou ciclo de mudança. No caso do Angular, um ciclo de resumo identifica todas as expressões registradas para ver se há uma mudança. Ela sabe sobre os valores anteriores de um modelo e, se eles tiverem sido alterados, um evento de alteração será acionado. Para um desenvolvedor, o principal benefício é que você pode usar dados de objetos JavaScript brutos, que são agradáveis de usar e se compõe muito bem. A desvantagem é que ele tem um comportamento algorítmico ruim e pode ser muito caro.
A despesa dessa operação é proporcional ao número total de objetos observados. Acho que vou precisar fazer muita verificação. Também pode ser necessário acionar a verificação de integridade quando os dados possam ter mudado. Há muitos truques inteligentes que os frameworks usam para isso. Não está claro se isso vai ser perfeito.
O ecossistema da Web deve ter mais capacidade de inovar e desenvolver seus próprios mecanismos declarativos, por exemplo,
- Sistemas de modelos baseados em restrições
- Sistemas de autopersistência (por exemplo, mudanças persistentes no IndexedDB ou localStorage)
- Objetos de contêiner (Ember, Backbone)
Os objetos container são onde um framework cria objetos que armazenam os dados. Eles têm acessórios aos dados e podem capturar o que você define ou recebe e transmitir internamente. Isso funciona bem. Ele é relativamente eficiente e tem um bom comportamento algorítmico. Confira abaixo um exemplo de objetos de contêiner usando o 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
A despesa de descobrir o que mudou aqui é proporcional ao número de coisas que mudaram. Outro problema é que você está usando um tipo diferente de objeto. Em geral, é necessário converter os dados recebidos do servidor para esses objetos para que eles sejam observáveis.
Isso não funciona muito bem com códigos JS existentes, porque a maior parte dos códigos presume que eles podem operar em dados brutos. Não para esses tipos especializados de objetos.
Introducing Object.observe()
Idealmente, o que queremos é o melhor dos dois mundos: uma forma de observar dados com suporte para objetos de dados brutos (objetos JavaScript normais) se escolhermos E sem a necessidade de verificar tudo o tempo todo. Algo com bom comportamento algorítmico. Algo que compõe bem e está incorporado à plataforma. Essa é a beleza do que o Object.observe()
oferece.
Ele permite observar um objeto, modificar propriedades e conferir o relatório de mudanças. Mas chega de teoria. Vamos analisar o código.
Object.observe() e Object.unobserve()
Vamos imaginar que temos um objeto JavaScript baunilha simples representando um modelo:
// A model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
Podemos especificar um callback sempre que ocorrerem mutações (mudanças) no 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
});
}
Podemos então observar essas mudanças usando O.o(), transmitindo o objeto como nosso primeiro argumento e o callback como o segundo:
Object.observe(todoModel, observer);
Vamos começar a fazer algumas mudanças no objeto de modelo Todos:
todoModel.label = 'Buy some more milk';
Analisando o console, recebemos algumas informações úteis! Sabemos qual propriedade foi alterada, como ela foi alterada e qual é o novo valor.
Uhu! Adeus, verificação suja! Sua lápide deve ser gravada em Comic Sans. Vamos mudar outra propriedade. Desta vez, completeBy
:
todoModel.completeBy = '01/01/2014';
Como podemos ver, recebemos novamente um relatório de alterações:
Ótimo. E se você decidir excluir a propriedade "concluída" do objeto?
delete todoModel.completed;
Como podemos ver, o relatório de alterações retornado inclui informações sobre a exclusão. Como esperado, o novo valor da propriedade agora está indefinido. Agora você sabe que é possível descobrir quando as propriedades foram adicionadas. Quando forem excluídos. Basicamente, o conjunto de propriedades em um objeto ("novo", "excluído", "reconfigurado") e a mudança do protótipo (proto).
Como em qualquer sistema de observação, também existe um método para parar de detectar mudanças. Nesse caso, é Object.unobserve()
, que tem a mesma assinatura de O.o(), mas pode ser chamado da seguinte maneira:
Object.unobserve(todoModel, observer);
Como podemos ver abaixo, qualquer mutação feita no objeto depois que ele foi executado não resulta mais em uma lista de registros de mudança retornados.
Como especificar mudanças de interesse
Por isso, vimos os fundamentos por trás de como recuperar uma lista de alterações em um objeto observado. E se você tiver interesse em apenas um subconjunto das alterações feitas em um objeto, e não em todas elas? Todos precisam de um filtro de spam. Bem, os observadores podem especificar apenas os tipos de alterações que querem ouvir por meio de uma lista de aceitação. Isso pode ser especificado usando o terceiro argumento para O.o() da seguinte maneira:
Object.observe(obj, callback, optAcceptList)
Confira um exemplo de como isso pode ser usado:
// 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
No entanto, se excluirmos o rótulo, esse tipo de mudança será informado:
delete todoModel.label;
Se você não especificar uma lista de tipos de aceitação para O.o(), o padrão será os tipos de mudança de objeto "intrínsecos" (add
, update
, delete
, reconfigure
, preventExtensions
(para quando um objeto se tornar não extensível não for observável)).
Notificações
O.o() também acompanha o conceito de notificações. Eles não são nada como aqueles incômodos no smartphone, mas são úteis. As notificações são semelhantes aos observadores de mutação. Eles acontecem no final da microtarefa. No contexto do navegador, isso quase sempre estará no final do manipulador de eventos atual.
O momento é bom porque, geralmente, uma unidade de trabalho está concluída e agora os observadores começam a fazer o trabalho deles. É um bom modelo de processamento baseado em turnos.
O fluxo de trabalho para usar um notificador é mais ou menos assim:
Vejamos um exemplo de como os notificadores podem ser usados na prática para definir notificações personalizadas para quando as propriedades de um objeto são get ou set. Acompanhe os comentários aqui:
// 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);
Aqui, informamos quando o valor das propriedades de dados muda ("update"). Qualquer outra coisa que a implementação do objeto escolha informar (notifier.notifyChange()
).
Anos de experiência na plataforma da Web nos ensinaram que uma abordagem síncrona é a primeira coisa que você tenta porque é a mais fácil de entender. O problema é que isso cria um modelo de processamento fundamentalmente perigoso. Se você estiver escrevendo código e, por exemplo, atualizar a propriedade de um objeto, não vai querer que a atualização da propriedade desse objeto possa ter convidado algum código arbitrário para fazer o que quiser. Não é ideal que suas suposições sejam invalidadas enquanto você está no meio de uma função.
Se você for um observador, não será chamado se alguém estiver no meio de algo. Você não quer que peçam para trabalhar em um estado inconsistente do mundo. Fazer muito mais verificações de erros. Tentar tolerar mais situações ruins e, em geral, é um modelo difícil de trabalhar. O modo assíncrono é mais difícil de lidar, mas é um modelo melhor no final do dia.
A solução para esse problema são os registros de mudança sintética.
Registros de alteração sintética
Basicamente, se você quiser ter acionadores ou propriedades computadas, é sua responsabilidade notificar quando esses valores mudarem. É um pouco mais trabalhoso, mas foi projetado como um tipo de recurso de primeira classe desse mecanismo, e essas notificações serão entregues com o restante das notificações de objetos de dados subjacentes. Das propriedades de dados.
A observação de acessadores e propriedades computadas pode ser resolvida com notifier.notify, outra parte de O.o(). A maioria dos sistemas de observação quer alguma forma de observação de valores derivados. Há muitas maneiras de fazer isso. O O.o não faz julgamentos sobre a maneira "certa". As propriedades computadas precisam ser acessadores que notificam quando o estado interno (privado) muda.
Novamente, os webdevs devem esperar que as bibliotecas ajudem a facilitar a notificação e várias abordagens de propriedades computadas (e reduzir o código boilerplate).
Vamos configurar o próximo exemplo, que é uma classe de círculo. A ideia aqui é que temos esse círculo e uma propriedade de raio. Nesse caso, o raio é um acessório, e quando o valor dele muda, ele notifica a si mesmo que o valor mudou. Isso será enviado com todas as outras mudanças feitas nesse objeto ou em qualquer outro. Essencialmente, se você está implementando um objeto, quer ter propriedades sintéticas ou computadas, ou precisa escolher uma estratégia para como isso vai funcionar. Depois de fazer isso, ela se encaixará no seu sistema como um todo.
Pule o código para conferir como ele funciona no 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);
})
}
Propriedades de acessório
Uma observação rápida sobre as propriedades de acessório. Mencionamos anteriormente que apenas as mudanças de valor podem ser observadas nas propriedades dos dados. Não se aplica a propriedades ou acessadores computados. Isso ocorre porque o JavaScript não tem a noção de mudanças de valor para os acionadores. Um acessador é apenas uma coleção de funções.
Se você atribuir a um assistente de acesso, o JavaScript apenas invocará a função e, do seu ponto de vista, nada será alterado. Ele apenas deu a oportunidade de executar algum código.
O problema é que, semanticamente, podemos analisar a atribuição acima ao valor - 5. Devemos saber o que aconteceu aqui. Esse é um problema sem solução. O exemplo demonstra o motivo. Não há como qualquer sistema saber o que isso significa, porque pode ser um código arbitrário. Nesse caso, ele pode fazer o que quiser. Ele atualiza o valor sempre que é acessado. Por isso, perguntar se ele mudou não faz muito sentido.
Observar vários objetos com um callback
Outro padrão possível com O.o() é a noção de um único observador de callback. Assim, um único callback pode ser usado como um "observador" de vários objetos diferentes. O callback vai receber o conjunto completo de mudanças de todos os objetos que ele observa no "fim da microtarefa" (observe a semelhança com os observadores de mutação).
Mudanças em grande escala
Talvez você esteja trabalhando em um app realmente grande e precise trabalhar regularmente com mudanças em grande escala. Os objetos podem querer descrever mudanças semânticas maiores que afetam muitas propriedades de maneira mais compacta (em vez de transmitir várias mudanças de propriedade).
O O.o() ajuda com isso por meio de dois utilitários específicos: notifier.performChange()
e notifier.notify()
, que já introduzimos.
Vamos analisar isso em um exemplo de como mudanças em grande escala podem ser descritas onde definimos um objeto Thingy com alguns utilitários matemáticos (multiplicar, incrementar, incrementAndMultiply). Sempre que um utilitário é usado, ele informa ao sistema que uma coleção de obras compreende um tipo específico de mudança.
Por exemplo: 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
});
}
}
Em seguida, definimos dois observadores para nosso objeto: um que abrange todas as alterações e outro que só informa tipos de aceitação específicos definidos (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);
}
Agora podemos começar a brincar com esse código. Vamos definir um novo Thingy:
var thingy = new Thingy(2, 4);
Observe-o e faça algumas alterações. Que divertido. São tantas coisinhas!
// 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 }
Tudo dentro da "função de execução" é considerado o trabalho de "grande mudança". Os observadores que aceitam "grande mudança" só recebem o registro de "grande mudança". Os observadores que não receberem as mudanças subjacentes resultantes do trabalho que "executar função" fizeram.
Como observar matrizes
Já falamos um tempo sobre a observação de mudanças em objetos, mas e quanto às matrizes? Ótima pergunta. Quando alguém me diz: "Ótima pergunta". Nunca ouço a resposta porque estou ocupado me parabenizando por fazer uma pergunta tão boa, mas não paro. Também temos novos métodos para trabalhar com matrizes.
Array.observe()
é um método que trata mudanças em grande escala de si mesmo (por exemplo, unir, cancelar mudanças ou qualquer coisa que altere o comprimento dele implicitamente) como um registro de mudança de "união". Internamente, ele usa notifier.performChange("splice",...)
.
Confira um exemplo em que observamos uma "matriz" de modelo e recebemos uma lista de alterações quando há alterações nos dados subjacentes:
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';
Desempenho
Pense no impacto do desempenho computacional de O.o() como um cache de leitura. De modo geral, um cache é uma ótima opção quando (em ordem de importância):
- A frequência de leituras domina a frequência de gravações.
- É possível criar um cache que troca a quantidade constante de trabalho envolvido nas gravações por um desempenho mais eficiente do algoritmo durante as leituras.
- A lentidão constante das gravações é aceitável.
O método O.o() foi criado para casos de uso como 1).
A verificação suja requer uma cópia de todos os dados que você está observando. Isso significa que você incorre em um custo de memória estrutural para fazer verificações sujas que não são feitas com O.o(). A verificação suja, embora seja uma solução de parada decente, também é uma abstração fundamentalmente vazada que pode criar complexidade desnecessária para os aplicativos.
Por quê? Bem, a verificação suja precisa ser executada sempre que os dados podem ter mudado. Não há uma maneira muito robusta de fazer isso, e qualquer abordagem tem desvantagens significativas (por exemplo, a verificação em um intervalo de pesquisa gera artefatos visuais e condições de corrida entre problemas de código). A verificação de estado sujo também exige um registro global de observadores, criando riscos de vazamento de memória e custos de desmontagem que O.o() evita.
Vamos conferir alguns números.
Os testes comparativos abaixo (disponíveis no GitHub) permitem comparar a verificação suja com o O.o(). Eles estão estruturados como gráficos de Observed-Object-Set-Size vs. Number-Of-Mutations. O resultado geral é que o desempenho da verificação suja é proporcional ao número de objetos observados, enquanto o desempenho de O.o() é proporcional ao número de mutações feitas.
Verificação de estado
Chrome com Object.observe() ativado
Preenchimento polido de Object.observe()
Ótimo. O O.o() pode ser usado no Chrome 36, mas e em outros navegadores? Nós podemos ajudar. O Observe-JS do Polymer é um polyfill para O.o(), que vai usar a implementação nativa se ela estiver presente, mas, caso contrário, vai polyfillá-la e incluir algumas funcionalidades úteis. Ele oferece uma visão agregada do mundo que resume as mudanças e fornece um relatório do que mudou. Duas coisas realmente poderosas que ele expõe são:
- Você pode observar caminhos. Isso significa que você pode dizer "Gostaria de observar "foo.bar.baz" em um determinado objeto, e ele vai informar quando o valor nesse caminho mudar. Se o caminho estiver inacessível, ele vai considerar o valor indefinido.
Exemplo de observação de um valor em um caminho de um determinado objeto:
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.
});
- Ele vai informar sobre as emendas de matriz. As uniões de matriz são basicamente o conjunto mínimo de operações de união que você terá que realizar em uma matriz para transformar a versão antiga da matriz na nova versão da matriz. Esse é um tipo de transformação ou visualização diferente da matriz. É o trabalho mínimo que você precisa fazer para passar do estado antigo para o novo.
Exemplo de relatório de mudanças em uma matriz como um conjunto mínimo de emendas:
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 e Object.observe()
Como mencionado, a O.o() vai dar aos frameworks e bibliotecas uma grande oportunidade de melhorar a performance da vinculação de dados em navegadores compatíveis com o recurso.
Yehuda Katz e Erik Bryn, da Ember, confirmaram que a adição de suporte para O.o() está no roteiro de curto prazo da Ember. Misko Hervy do Angular escreveu um documento de design sobre a detecção aprimorada de mudanças do Angular 2.0. A abordagem de longo prazo deles será aproveitar o Object.observe() quando ele for lançado na versão estável do Chrome, optando por Watchtower.js, a própria abordagem de detecção de mudanças até então. Que demais.
Conclusões
O.o() é um complemento poderoso à plataforma da Web que você pode lançar e usar hoje em dia.
Esperamos que, com o tempo, o recurso chegue a mais navegadores, permitindo que frameworks JavaScript obtenham melhorias de desempenho com o acesso a recursos de observação de objetos nativos. Os desenvolvedores voltados ao Chrome devem poder usar o O.o() no Chrome 36 (e superior), e o recurso também deverá estar disponível em uma versão futura do Opera.
Então, converse com os autores de frameworks JavaScript sobre Object.observe()
e como eles planejam usá-lo para melhorar a performance da vinculação de dados nos seus apps. Há momentos muito emocionantes pela frente!
Recursos
- Object.observe() na wiki do Harmony>
- Vinculação de dados com Object.observe() de Rick Waldron (em inglês)
- Tudo o que você queria saber sobre Object.observe() - JSConf
- Por que Object.observe() é o melhor recurso do ES7
Agradecemos a Rafael Weinstein, Jake Archibald, Eric Bidelman, Paul Kinlan e Vivian Cromwell pelas contribuições e avaliações.