Wprowadzenie
Nadchodzi rewolucja. W JavaScript pojawiła się nowa funkcja, która zmieni wszystko, co wiesz o wiązaniu danych. Zmieni się też liczba bibliotek MVC, które będą obserwować modele pod kątem zmian i aktualizacji. Czy jesteś gotowy na zwiększenie wydajności aplikacji, które monitorują usługi?
Bez zbędnych ceregieli z przyjemnością ogłaszam, że Object.observe()
została dodana do stabilnej wersji Chrome 36. [WOOOO. THE CROWD GOES WILD].
Object.observe()
, która jest częścią przyszłego standardu ECMAScript, to metoda asynchronicznego obserwowania zmian w obiektach JavaScriptu bez potrzeby korzystania z osobnej biblioteki. Umożliwia on obserwatorowi otrzymywanie uporządkowanej czasowo sekwencji rekordów zmian, które opisują zestaw zmian wprowadzonych w zbiorze obserwowanych obiektów.
// 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);
});
});
Każda zmiana jest zgłaszana:

Dzięki Object.observe()
(lub O.o() albo Oooooooo) możesz stosować dwukierunkowe wiązanie danych bez konieczności korzystania z ramy konstrukcyjnej.
Nie oznacza to jednak, że nie powinieneś ich używać. W przypadku dużych projektów o skomplikowanej logice biznesowej nieocenione są ramy programistyczne, z których warto nadal korzystać. Ułatwiają oni orientację nowym deweloperom, wymagają mniej konserwacji kodu i określają wzorce wykonywania typowych zadań. Jeśli nie potrzebujesz takiej biblioteki, możesz użyć mniejszych, bardziej skoncentrowanych bibliotek, takich jak Polymer (która korzysta już z O.o()).
Nawet jeśli często używasz frameworka lub biblioteki MV*, O.o() może zapewnić Ci znaczną poprawę wydajności dzięki szybszemu i prostszemu wdrożeniu przy zachowaniu tego samego interfejsu API. Na przykład w ubiegłym roku Angular odkrył, że w ramach testu porównawczego, w którym wprowadzano zmiany w modelu, sprawdzanie zmian trwało 40 ms na aktualizację, a funkcja O.o() – 1–2 ms na aktualizację (co oznacza 20–40-krotne przyspieszenie).
Powiązanie danych bez konieczności stosowania skomplikowanego kodu oznacza też, że nie musisz już przeprowadzać ankiety w celu sprawdzania zmian, co przekłada się na dłuższy czas pracy baterii.
Jeśli jesteś już przekonany/a do funkcji O.o(), przejdź do sekcji wprowadzającej lub dowiedz się więcej o problemach, które rozwiązuje.
Co chcemy obserwować?
Gdy mówimy o obserwowaniu danych, mamy na myśli zwracanie uwagi na określone typy zmian:
- Zmiany w surowych obiektach JavaScriptu
- Kiedy usługi są dodawane, zmieniane lub usuwane
- Gdy tablice zawierają elementy, które są w nich wstawiane lub usuwane
- zmiany w prototypie obiektu.
Znaczenie powiązań danych
Wiązanie danych staje się ważne, gdy zależy Ci na rozdzieleniu kontroli modelu i widoku. HTML to świetny mechanizm deklaratywny, ale jest całkowicie statyczny. Najlepiej jest po prostu zadeklarować relację między danymi a DOM i utrzymywać DOM w aktualnym stanie. Dzięki temu możesz oszczędzić sporo czasu, który musiałbyś poświęcić na pisanie powtarzającego się kodu, który po prostu wysyła dane do i z DOM między stanem wewnętrznym aplikacji a serwerem.
Wiązanie danych jest szczególnie przydatne, gdy masz złożony interfejs użytkownika, w którym musisz nawiązywać relacje między wieloma właściwościami w modelach danych z wieloma elementami w widokach. Jest to dość powszechne w przypadku aplikacji jednostronicowych, które obecnie tworzymy.
Dzięki możliwości obserwowania danych w naturalny sposób w przeglądarce zapewniamy frameworkom JavaScript (oraz małym bibliotekom narzędziowym, które piszesz) możliwość obserwowania zmian w modelu danych bez konieczności korzystania z powolniejszych rozwiązań, z których obecnie korzystają użytkownicy.
Jak wygląda świat dzisiaj
Sprawdzanie niespójności
Gdzie wcześniej spotykaliśmy się z pojęciem „wiązania danych”? Jeśli do tworzenia aplikacji internetowych używasz nowoczesnej biblioteki MV*, np.Angular lub Knockout, pewnie wiesz, jak wiązać dane modelu z DOM. Oto przykład aplikacji Lista telefonów, w której wartość każdego telefonu w tablicy phones
(zdefiniowanej w JavaScript) wiążemy z elementem listy, aby dane i interfejs użytkownika były zawsze zsynchronizowane:
<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>
oraz JavaScript dla kontrolera:
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.'}
];
});
Za każdym razem, gdy dane modelu podstawowego ulegną zmianie, nasza lista w DOM zostanie zaktualizowana. Jak to osiąga Angular? Za kulisami odbywa się coś, co nazywamy sprawdzaniem niespójności.

Podstawowa idea sprawdzania zmian polega na tym, że w każdej chwili, gdy dane mogą ulec zmianie, biblioteka musi sprawdzić, czy tak się stało, za pomocą cyklu zmian lub skrótu. W przypadku Angulara cykl przetwarzania identyfikuje wszystkie zarejestrowane wyrażenia, które mają być monitorowane pod kątem zmian. Wie o poprzednich wartościach modelu, a jeśli ulegną zmianie, uruchamia zdarzenie zmiany. Główną zaletą dla dewelopera jest możliwość korzystania z nieprzetworzonych danych obiektów JavaScript, które są przyjemne w użyciu i dobrze się łączą. Minusem jest to, że ma ono nieprawidłowe działanie algorytmiczne i może być bardzo drogie.

Koszty tej operacji są proporcjonalne do łącznej liczby obserwowanych obiektów. Może być konieczne wykonanie wielu niestandardowych kontroli. Może też być potrzebny sposób na wywołanie sprawdzania zmian, gdy dane mogą ulec zmianie. W tym celu frameworki wykorzystują wiele sprytnych sztuczek. Nie wiadomo, czy kiedykolwiek będzie to idealne rozwiązanie.
Ekosystem internetowy powinien mieć większą zdolność do wprowadzania innowacji i ulepszania własnych mechanizmów deklaratywnych, takich jak
- Systemy modeli oparte na ograniczeniach
- systemy automatycznego przechowywania (np.przechowywanie zmian w IndexedDB lub localStorage);
- Obiekty kontenera (Ember, Backbone)
Obiekty kontenera to miejsce, w którym framework tworzy obiekty, które wewnątrz przechowują dane. Mają dostęp do danych i mogą rejestrować to, co ustawiasz lub pobierasz, oraz przesyłać je wewnętrznie. To działa. Jest on stosunkowo wydajny i działa dobrze w ramach algorytmu. Poniżej znajdziesz przykład obiektów kontenera korzystających z 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
Koszt wykrycia zmian jest proporcjonalny do liczby zmienionych elementów. Innym problemem jest to, że używasz innego rodzaju obiektu. Ogólnie rzecz biorąc, musisz przekształcić dane otrzymywane z serwera w takie obiekty, aby były widoczne.
Nie jest ono szczególnie dobrze dopasowane do dotychczasowego kodu JS, ponieważ większość kodu zakłada, że może on działać na danych nieprzetworzonych. Nie dotyczy to wyspecjalizowanych rodzajów obiektów.
Introducing Object.observe()
Chcielibyśmy połączyć zalety obu tych rozwiązań – mieć możliwość obserwowania danych z wsparciem dla obiektów danych nieprzetworzonych (zwykłych obiektów JavaScript), jeśli zdecydujemy się na to, oraz bez konieczności ciągłego sprawdzania wszystkiego. coś z dobrym zachowaniem algorytmu. Coś, co dobrze się składa i jest wbudowane w platformę. To właśnie wnosi Object.observe()
.
Umożliwia nam to obserwowanie obiektu, modyfikowanie jego właściwości i sprawdzanie raportu zmian. Wystarczy już teorii. Przyjrzyjmy się kodowi.

Object.observe() i Object.unobserve()
Załóżmy, że mamy prosty obiekt JavaScriptu reprezentujący model:
// A model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
Następnie możemy określić funkcję wywołania zwrotnego, która będzie wykonywana przy każdej mutacji (zmianie) obiektu:
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
});
}
Możemy obserwować te zmiany za pomocą funkcji O.o(), przekazując obiekt jako pierwszy argument, a drugi jako funkcję wywołania zwrotnego:
Object.observe(todoModel, observer);
Zacznijmy od wprowadzenia zmian w obiekcie modelu Todos:
todoModel.label = 'Buy some more milk';
W konsoli znajdziesz przydatne informacje. Wiemy, która właściwość się zmieniła, jak zmieniła się jej wartość i jaka jest jej nowa wartość.

Super! Do widzenia, sprawdzanie nieczystości! Napis na nagrobku powinien być wyryty czcionką Comic Sans. Zmieńmy inną usługę. Tym razem completeBy
:
todoModel.completeBy = '01/01/2014';
Jak widać, ponownie otrzymaliśmy raport o zmianach:

Świetnie! Co się stanie, jeśli zdecydujemy się usunąć z obiektu właściwość „ukończono”:
delete todoModel.completed;

Jak widać, zwrócony raport zmian zawiera informacje o usunięciu. Zgodnie z oczekiwaniami nowa wartość właściwości jest teraz niezdefiniowana. Wiemy już, że możesz sprawdzić, kiedy zostały dodane usługi. Gdy zostały usunięte. Zasadniczo zbiór właściwości obiektu („nowy”, „usunięty”, „przekonfigurowany”) i zmiana jego prototypu (proto).
Jak w przypadku każdego systemu obserwacji, istnieje też metoda na zaprzestanie nasłuchiwania zmian. W tym przypadku jest to Object.unobserve()
, która ma tę samą sygnaturę co O.o(), ale może być wywoływana w ten sposób:
Object.unobserve(todoModel, observer);
Jak widać poniżej, żadne mutacje wprowadzone w obiekcie po wykonaniu tego kodu nie powodują już zwracania listy rekordów zmian.

Określanie zmian zainteresowań
W tym artykule omówiliśmy podstawy uzyskiwania listy zmian w obserwowanym obiekcie. Co zrobić, jeśli interesuje Cię tylko podzbiór zmian wprowadzonych w obiekcie, a nie wszystkie? Każdy potrzebuje filtra antyspamowego. Obserwatorzy mogą określić tylko te typy zmian, o których chcą otrzymywać powiadomienia za pomocą listy akceptacji. Można to określić za pomocą trzeciego argumentu funkcji O.o() w ten sposób:
Object.observe(obj, callback, optAcceptList)
Oto przykład zastosowania tej funkcji:
// 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
Jeśli jednak usuniemy etykietę, zauważysz, że tego typu zmiany są zgłaszane:
delete todoModel.label;
Jeśli nie podasz listy typów akceptacji do funkcji O.o(), domyślnie zostaną użyte „właściwe” typy zmian obiektu (add
, update
, delete
, reconfigure
, preventExtensions
(gdy nie można zaobserwować, że obiekt nie może być rozszerzany)).
Powiadomienia
O.o() zawiera też pojęcie powiadomień. Nie są one w ogóle podobne do tych irytujących reklam na telefonie, ale są przydatne. Powiadomienia są podobne do obserwatorów mutacji. Występują one na końcu mikrozadania. W kontekście przeglądarki prawie zawsze będzie to koniec bieżącego modułu obsługi zdarzeń.
Jest to dobry moment, ponieważ zazwyczaj kończy się wtedy jedna jednostka pracy, a obserwatorzy mogą przystąpić do działania. Jest to model przetwarzania oparty na kolejkach.
Proces korzystania z powiadomienia wygląda mniej więcej tak:

Zobaczmy, jak w praktyce można używać powiadomień do definiowania niestandardowych powiadomień w przypadku pobierania lub ustawiania właściwości obiektu. Obserwuj komentarze tutaj:
// 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);

Tutaj raportujemy, gdy zmienia się wartość właściwości danych („update”). wszystko inne, co implementacja obiektu chce raportować (notifier.notifyChange()
).
Nasze wieloletnie doświadczenie w korzystaniu z platformy internetowej nauczyło nas, że podejście synchroniczne jest tym, od czego zazwyczaj się zaczyna, ponieważ jest najprostsze do zrozumienia. Problem polega na tym, że tworzy on niebezpieczny model przetwarzania. Jeśli piszesz kod i np. aktualizujesz właściwość obiektu, nie chcesz, aby z powodu tej aktualizacji jakiś dowolny kod mógł robić, co chce. Nie jest to idealna sytuacja, gdy założenia są nieprawidłowe w trakcie wykonywania funkcji.
Jeśli jesteś obserwatorem, nie chcesz być prawdopodobnie wywoływany, gdy ktoś jest w trakcie jakiejś czynności. Nie chcesz, żeby proszono Cię o prace nad niespójnym stanem świata. W konsekwencji trzeba będzie wykonać znacznie więcej kontroli błędów. Próbuje tolerować znacznie więcej niekorzystnych sytuacji i ogółem jest to trudny model do pracy. Interfejs asynchroniczny jest trudniejszy w użyciu, ale w ogóle jest lepszym modelem.
Rozwiązaniem tego problemu są syntetyczne rekordy zmian.
Syntetyczne rekordy zmian
Jeśli chcesz mieć metody dostępu lub właściwości obliczeniowe, to Twoim zadaniem jest poinformowanie o zmianie tych wartości. To trochę dodatkowej pracy, ale jest to funkcja tego mechanizmu, która ma najwyższy priorytet. Powiadomienia te będą wysyłane razem z resztą powiadomień z podstawowych obiektów danych. Z właściwości danych.

Obserwowanie funkcji dostępu i obliczanych właściwości można rozwiązać za pomocą notifier.notify – kolejnej części O.o(). Większość systemów obserwacji wymaga pewnej formy obserwowania wartości pochodzenia. Możesz to zrobić na wiele sposobów. O.o nie ocenia, co jest „właściwe”. Obliczane właściwości powinny być metodami dostępu, które powiadamią o zmianach wewnętrznego (prywatnego) stanu.
Deweloperzy powinni oczekiwać, że biblioteki ułatwią im tworzenie powiadomień i różnych metod obliczania właściwości (oraz zmniejszą ilość kodu stałego).
Przygotujmy kolejny przykład, czyli klasę koła. Mamy tu koło i jego promień. W tym przypadku promień jest elementem dostępu, a gdy jego wartość się zmieni, sam powiadomi o tym, że wartość się zmieniła. Zostanie ona przesłana wraz ze wszystkimi innymi zmianami wprowadzonymi w tym obiekcie lub dowolnym innym obiekcie. Jeśli wdrażasz obiekt, który ma mieć właściwości syntetyczne lub obliczone, musisz wybrać strategię, która to umożliwi. Gdy to zrobisz, będzie to pasować do całego systemu.
Pomiń kod, aby zobaczyć, jak to działa w Narzędziach deweloperskich.
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);
})
}

Właściwości funkcji dostępu
Krótka uwaga na temat właściwości funkcji dostępu. Wspomniliśmy wcześniej, że w przypadku właściwości danych widoczne są tylko zmiany wartości. Nie dotyczy właściwości obliczeniowych ani metod dostępu. Dzieje się tak, ponieważ JavaScript nie ma pojęcia o zmianach wartości w metodach dostępu. Akcesor to tylko zbiór funkcji.
Jeśli przypiszesz do funkcji JavaScript tylko wywołuje ją, a z jej punktu widzenia nic się nie zmienia. Po prostu umożliwiło uruchomienie kodu.
Problem polega na tym, że semantycznie możemy spojrzeć na przypisanie powyżej do wartości - 5. Musimy wiedzieć, co się tutaj stało. To nierozwiązywalny problem. Przykład pokazuje, dlaczego tak jest. Żaden system nie jest w stanie określić, co oznacza ten kod, ponieważ może to być dowolny kod. W tym przypadku może zrobić wszystko, co chce. Wartość jest aktualizowana za każdym razem, gdy jest odczytywana, więc pytanie o to, czy się zmieniła, nie ma większego sensu.
Obserwowanie wielu obiektów za pomocą jednego wywołania zwrotnego
Innym wzorcem możliwym do zastosowania w przypadku funkcji O.o() jest pojedynczy obserwator wywołania zwrotnego. Dzięki temu można używać jednej funkcji wywołania zwrotnego jako „obserwatora” wielu różnych obiektów. Funkcja wywołania zwrotnego otrzyma pełny zestaw zmian dla wszystkich obserwowanych obiektów „pod koniec mikrozadania” (zauważ podobieństwo do obserwatorów mutacji).

Zmiany na dużą skalę
Może pracujesz nad naprawdę dużą aplikacją i regularnie musisz wprowadzać duże zmiany. Obiekty mogą opisywać większe zmiany semantyczne, które będą wpływać na wiele właściwości w bardziej zwięzłej formie (zamiast przesyłać mnóstwo zmian właściwości).
O.o() pomaga w tym zakresie dzięki dwóm specjalnym narzędziom: notifier.performChange()
i notifier.notify()
, które już przedstawiliśmy.

Przyjrzyjmy się temu na przykładzie opisywania zmian na dużą skalę, w którym definiujemy obiekt Thingy za pomocą kilku narzędzi matematycznych (multiply, increment, incrementAndMultiply). Każdorazowy użytek z utility informuje system, że zbiór prac obejmuje określony typ zmiany.
Na przykład: 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
});
}
}
Następnie definiujemy 2 obserwatorów dla naszego obiektu: jeden, który jest zbiorczym obserwatorem zmian, a drugi, który będzie raportować tylko o określonych zdefiniowanych przez nas typach akceptacji (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);
}
Możemy teraz zacząć grać z tym kodem. Zdefiniujmy nowe urządzenie:
var thingy = new Thingy(2, 4);
Obserwuj je, a następnie wprowadź zmiany. OMG, so fun. Tyle rzeczy!
// 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 }

Wszystko, co znajduje się w ramach funkcji „perform function”, jest uważane za działanie „big-change”. Obserwatorzy, którzy akceptują „big-change”, otrzymają tylko rekord „big-change”. Obserwatorzy nie otrzymają zmian wynikających z działania funkcji „perform function”.
Obserwowanie tablic
Rozmawialiśmy już o obserwowaniu zmian w obiektach, ale co z tablicami? Świetne pytanie. Gdy ktoś powie: „Świetkie pytanie”. Nigdy nie słyszę ich odpowiedzi, bo jestem zajęty gratulowaniem sobie za zadanie tak świetnego pytania, ale odbiegam od tematu. Mamy też nowe metody pracy z tablicami.
Array.observe()
to metoda, która traktuje duże zmiany w sobie – np. splice, unshift lub cokolwiek, co pośrednio zmienia długość – jako rekord zmiany „splice”. Wewnętrznie używa ona metody notifier.performChange("splice",...)
.
Oto przykład, w którym obserwujemy model „array” i w podobny sposób otrzymujemy listę zmian w przypadku jakichkolwiek zmian w podstawowych danych:
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';

Wyniki
O.o() można traktować jak pamięć podręczną do odczytu. Ogólnie rzecz biorąc, pamięć podręczna to świetny wybór, gdy (kolejność według ważności):
- Częstotliwość odczytu przeważa nad częstotliwością zapisu.
- Możesz utworzyć pamięć podręczną, która zamienia stałą ilość pracy wykonywanej podczas zapisu na lepszą wydajność algorytmiczną podczas odczytu.
- Stałe spowolnienie zapisu jest dopuszczalne.
Funkcja O.o() jest przeznaczona do takich zastosowań jak 1).
Sprawdzanie nieczystości wymaga przechowywania kopii wszystkich danych, które obserwujesz. Oznacza to, że ponosisz koszt strukturalny pamięci na potrzeby sprawdzania nieczystości, którego nie ma w przypadku funkcji O.o(). Sprawdzanie nieczystości to przyzwoite rozwiązanie tymczasowe, ale jest też podstawową nieszczelną abstrakcją, która może niepotrzebnie skomplikować aplikacje.
Dlaczego? Sprawdzanie niespójności musi być wykonywane za każdym razem, gdy dane mogą ulec zmianie. Nie ma prostego sposobu na to, a każde podejście ma istotne wady (np.sprawdzanie w interwale pollingu wiąże się z ryzykiem pojawienia się artefaktów wizualnych i warunków wyścigu między elementami kodu). Sprawdzanie nieczystości wymaga też globalnego rejestru obserwatorów, co stwarza ryzyko wycieku pamięci i wymaga kosztów, których można uniknąć, używając funkcji O.o().
Przyjrzyjmy się liczbom.
Testy porównawcze (dostępne na GitHub) umożliwiają porównanie sprawdzania zmian w funkcji O.o() z funkcją dirty(). Są one ustrukturyzowane jako wykresy zbioru obserwowanych obiektów w zależności od liczby mutacji. Ogólnie rzecz biorąc, wydajność sprawdzania nieczystości jest proporcjonalna do liczby obserwowanych obiektów, a wydajność funkcji O.o() jest proporcjonalna do liczby wprowadzonych mutacji.
Sprawdzanie niespójności

Chrome z włączoną metodą Object.observe()

Wypełnianie obiektu za pomocą funkcji Object.observe()
Świetnie – funkcji O.o() można używać w Chrome 36, ale co z innymi przeglądarkami? Chętnie Ci pomożemy. Observe-JS to biblioteka polyfill dla O.o(), która korzysta z wbudowanej implementacji, jeśli jest dostępna, a w przeciwnym razie tworzy ją i dodaje do niej przydatne funkcje. Udostępnia ona zbiorcze informacje o świecie, które podsumowują zmiany i tworzą raport o tym, co się zmieniło. 2 bardzo przydatne funkcje:
- Możesz obserwować ścieżki. Oznacza to, że możesz określić, że chcesz obserwować „foo.bar.baz” w danym obiekcie, a system poinformuje Cię, gdy wartość na tej ścieżce ulegnie zmianie. Jeśli ścieżka jest niedostępna, wartość jest uważana za nieokreśloną.
Przykład obserwacji wartości na ścieżce od danego obiektu:
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.
});
- Dowiesz się z niego o przecięciach tablic. Przecięcia tablic to w podstawie minimalny zestaw operacji przecięcia, które trzeba wykonać na tablicy, aby przekształcić starą wersję tablicy w nową. Jest to rodzaj przekształcenia lub inny widok tablicy. Jest to minimalna ilość pracy, jaką trzeba wykonać, aby przejść ze starego stanu do nowego.
Przykład raportowania zmian w tablicy jako minimalnego zestawu przecięć:
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.
});
});
Frameworki i Object.observe()
Jak już wspomnieliśmy, funkcja O.o() daje frameworkom i bibliotekom ogromne możliwości poprawy wydajności powiązań danych w przeglądarkach, które obsługują tę funkcję.
Yehuda Katz i Erik Bryn z Ember potwierdzili, że dodanie obsługi funkcji O.o() jest w najbliższej perspektywie planowane przez Ember. Misko Hervy z zespołu Angulara opracował dokument projektowy na temat ulepszonej funkcji wykrywania zmian w Angular 2.0. Ich długoterminowym podejściem będzie korzystanie z funkcji Object.observe(), gdy zostanie ona dodana do stabilnej wersji Chrome. Do tego czasu będą używać Watchtower.js, czyli własnego podejścia do wykrywania zmian. To bardzo ekscytujące.
Podsumowanie
O.o() to potężne narzędzie do platformy internetowej, z którego możesz korzystać już teraz.
Mamy nadzieję, że w przyszłości ta funkcja pojawi się w większej liczbie przeglądarek, co pozwoli frameworkom JavaScript zwiększyć wydajność dzięki dostępowi do natywnych funkcji obserwacji obiektów. Te, które są kierowane na Chrome, powinny być w stanie używać funkcji O.o() w Chrome 36 (i nowszych), a ta funkcja powinna być też dostępna w przyszłej wersji Opery.
Porozmawiaj z autorami frameworków JavaScript o Object.observe()
i o tym, jak planują go używać, aby poprawić wydajność powiązań danych w Twoich aplikacjach. Przed nami ekscytujące czasy!
Zasoby
- Funkcja Object.observe() w Harmony wiki>
- Połączenie danych za pomocą metody Object.observe() (Rick Waldron)
- Wszystko, co musisz wiedzieć o metodzie Object.observe() – JSConf
- Dlaczego Object.observe() to najlepsza funkcja ES7
Dziękujemy Rafaelowi Weinsteinowi, Jake’owi Archibaldowi, Ericowi Bidelmanowi, Paulowi Kinlanowi i Vivian Cromwell za ich opinie i recenzje.