Giới thiệu
Một cuộc cách mạng sắp diễn ra. JavaScript có một tính năng mới sẽ thay đổi mọi thứ mà bạn nghĩ mình biết về liên kết dữ liệu. Điều này cũng sẽ thay đổi cách nhiều thư viện MVC tiếp cận các mô hình quan sát để chỉnh sửa và cập nhật. Bạn đã sẵn sàng để tăng hiệu suất cho các ứng dụng quan tâm đến việc quan sát tài sản chưa?
Được rồi. Được rồi. Không để bạn chờ lâu, tôi rất vui được thông báo rằng Object.observe()
đã ra mắt trong phiên bản ổn định Chrome 36. [WOOOO. ĐÁM ĐÔNG CỰC KỲ HỨNG THÚ].
Object.observe()
, một phần của tiêu chuẩn ECMAScript trong tương lai, là một phương thức để quan sát không đồng bộ các thay đổi đối với đối tượng JavaScript… mà không cần thư viện riêng. Lớp này cho phép đối tượng tiếp nhận dữ liệu nhận được một trình tự theo thứ tự thời gian của các bản ghi thay đổi mô tả tập hợp các thay đổi đã diễn ra đối với một tập hợp các đối tượng được quan sát.
// 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);
});
});
Bất cứ khi nào có thay đổi, hệ thống sẽ báo cáo:

Với Object.observe()
(tôi thích gọi là O.o() hoặc Oooooooo), bạn có thể triển khai tính năng liên kết dữ liệu hai chiều mà không cần khung.
Điều này không có nghĩa là bạn không nên sử dụng một trong hai. Đối với các dự án lớn có logic nghiệp vụ phức tạp, các khung có định hướng là vô giá và bạn nên tiếp tục sử dụng các khung đó. Các mẫu này đơn giản hoá hướng dẫn cho các nhà phát triển mới, yêu cầu ít bảo trì mã hơn và áp đặt các mẫu về cách thực hiện các nhiệm vụ phổ biến. Khi không cần, bạn có thể sử dụng các thư viện nhỏ hơn, tập trung hơn như Polymer (đã tận dụng O.o()).
Ngay cả khi bạn thấy mình sử dụng nhiều khung hoặc thư viện MV*, O.o() vẫn có thể cung cấp cho chúng một số điểm cải thiện hiệu suất lành mạnh, với cách triển khai nhanh hơn, đơn giản hơn trong khi vẫn giữ nguyên API. Ví dụ: năm ngoái, Angular đã phát hiện rằng trong một điểm chuẩn mà các thay đổi đang được thực hiện đối với một mô hình, việc kiểm tra trạng thái thay đổi mất 40 mili giây cho mỗi lần cập nhật và O.o() mất 1-2 mili giây cho mỗi lần cập nhật (tốc độ nhanh hơn 20 đến 40 lần).
Việc liên kết dữ liệu mà không cần đến hàng tấn mã phức tạp cũng có nghĩa là bạn không còn phải thăm dò ý kiến về các thay đổi, nhờ đó thời lượng pin sẽ lâu hơn!
Nếu bạn đã quyết định sử dụng O.o(), hãy chuyển đến phần giới thiệu tính năng này hoặc đọc tiếp để biết thêm về các vấn đề mà tính năng này giải quyết.
Chúng ta muốn quan sát điều gì?
Khi nói đến việc quan sát dữ liệu, chúng ta thường đề cập đến việc theo dõi một số loại thay đổi cụ thể:
- Thay đổi đối với đối tượng JavaScript thô
- Khi các thuộc tính được thêm, thay đổi, xoá
- Khi các mảng có các phần tử được nối vào và ra khỏi mảng
- Thay đổi đối với nguyên mẫu của đối tượng
Tầm quan trọng của tính năng liên kết dữ liệu
Tính năng liên kết dữ liệu bắt đầu trở nên quan trọng khi bạn quan tâm đến việc tách biệt chế độ điều khiển thành phần hiển thị-mô hình. HTML là một cơ chế khai báo tuyệt vời, nhưng hoàn toàn tĩnh. Tốt nhất là bạn chỉ cần khai báo mối quan hệ giữa dữ liệu và DOM, đồng thời cập nhật DOM. Điều này tạo ra đòn bẩy và giúp bạn tiết kiệm nhiều thời gian viết mã lặp đi lặp lại chỉ gửi dữ liệu đến và đi từ DOM giữa trạng thái nội bộ của ứng dụng hoặc máy chủ.
Liên kết dữ liệu đặc biệt hữu ích khi bạn có giao diện người dùng phức tạp, trong đó bạn cần kết nối mối quan hệ giữa nhiều thuộc tính trong mô hình dữ liệu với nhiều phần tử trong thành phần hiển thị. Điều này khá phổ biến trong các ứng dụng trang đơn mà chúng ta đang xây dựng.
Bằng cách tích hợp một cách để quan sát dữ liệu gốc trong trình duyệt, chúng tôi cung cấp cho các khung JavaScript (và thư viện tiện ích nhỏ mà bạn viết) một cách để quan sát các thay đổi đối với dữ liệu mô hình mà không cần dựa vào một số bản hack chậm mà thế giới sử dụng hiện nay.
Thế giới ngày nay
Kiểm tra không sạch
Bạn đã thấy tính năng liên kết dữ liệu ở đâu trước đây? Nếu sử dụng thư viện MV* hiện đại để xây dựng ứng dụng web (ví dụ: Angular, Knockout), có thể bạn đã quen với việc liên kết dữ liệu mô hình với DOM. Để ôn lại, sau đây là ví dụ về ứng dụng Danh sách điện thoại, trong đó chúng ta liên kết giá trị của từng điện thoại trong mảng phones
(được xác định trong JavaScript) với một mục danh sách để dữ liệu và giao diện người dùng luôn đồng bộ:
<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>
và JavaScript cho trình điều khiển:
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.'}
];
});
Bất cứ khi nào dữ liệu mô hình cơ bản thay đổi, danh sách của chúng ta trong DOM sẽ được cập nhật. Angular làm được điều này bằng cách nào? Ở hậu trường, lớp này đang thực hiện một thao tác gọi là kiểm tra trạng thái thay đổi.

Ý tưởng cơ bản của việc kiểm tra trạng thái thay đổi là bất cứ khi nào dữ liệu có thể thay đổi, thư viện phải kiểm tra xem dữ liệu đó có thay đổi hay không thông qua một chu kỳ thay đổi hoặc tóm tắt. Trong trường hợp của Angular, một chu kỳ tóm tắt sẽ xác định tất cả biểu thức được đăng ký để theo dõi xem có thay đổi nào không. Phương thức này biết về các giá trị trước đó của mô hình và nếu các giá trị đó đã thay đổi, thì một sự kiện thay đổi sẽ được kích hoạt. Đối với nhà phát triển, lợi ích chính ở đây là bạn có thể sử dụng dữ liệu đối tượng JavaScript thô, dễ sử dụng và kết hợp khá tốt. Nhược điểm là thuật toán này có hành vi không tốt và có thể rất tốn kém.

Chi phí của thao tác này tỷ lệ thuận với tổng số đối tượng được quan sát. Tôi có thể cần phải kiểm tra nhiều lần. Ngoài ra, bạn cũng có thể cần một cách để kích hoạt tính năng kiểm tra dữ liệu không sạch khi dữ liệu có thể đã thay đổi. Có rất nhiều thủ thuật thông minh mà các khung sử dụng cho việc này. Không rõ liệu việc này có bao giờ hoàn hảo hay không.
Hệ sinh thái web phải có nhiều khả năng đổi mới và phát triển các cơ chế khai báo riêng, ví dụ:
- Hệ thống mô hình dựa trên quy tắc ràng buộc
- Hệ thống tự động duy trì (ví dụ: duy trì các thay đổi đối với IndexedDB hoặc localStorage)
- Đối tượng vùng chứa (Ember, Backbone)
Đối tượng Vùng chứa là nơi khung tạo các đối tượng bên trong chứa dữ liệu. Các thành phần này có phương thức truy cập vào dữ liệu và có thể ghi lại nội dung bạn đặt hoặc nhận và truyền nội bộ. Cách này hoạt động hiệu quả. Phương thức này tương đối hiệu quả và có hành vi thuật toán tốt. Bạn có thể xem ví dụ về đối tượng vùng chứa sử dụng Ember ở bên dưới:
// 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
Chi phí phát hiện những gì đã thay đổi ở đây tỷ lệ thuận với số lượng thay đổi. Một vấn đề khác là bạn đang sử dụng loại đối tượng khác. Nói chung, bạn phải chuyển đổi từ dữ liệu bạn nhận được từ máy chủ sang các đối tượng này để có thể quan sát được.
Mã này không kết hợp tốt với mã JS hiện có vì hầu hết mã đều giả định rằng mã này có thể hoạt động trên dữ liệu thô. Không dành cho những loại đối tượng chuyên biệt này.
Introducing Object.observe()
Lý tưởng nhất là chúng ta muốn có được cách tốt nhất của cả hai phương pháp – một cách để quan sát dữ liệu có hỗ trợ đối tượng dữ liệu thô (đối tượng JavaScript thông thường) nếu chúng ta chọn VÀ không cần phải kiểm tra mọi thứ mọi lúc. Một nội dung có hành vi thuật toán tốt. Một thành phần kết hợp tốt và được tích hợp vào nền tảng. Đây là điểm mạnh của Object.observe()
.
Công cụ này cho phép chúng ta quan sát một đối tượng, thay đổi các thuộc tính và xem báo cáo thay đổi về những gì đã thay đổi. Nhưng đã đủ lý thuyết, hãy xem một số mã!

Object.observe() và Object.unobserve()
Hãy tưởng tượng rằng chúng ta có một đối tượng JavaScript đơn giản đại diện cho một mô hình:
// A model can be a simple vanilla object
var todoModel = {
label: 'Default',
completed: false
};
Sau đó, chúng ta có thể chỉ định một lệnh gọi lại cho bất cứ khi nào có đột biến (thay đổi) đối với đối tượng:
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
});
}
Sau đó, chúng ta có thể quan sát những thay đổi này bằng cách sử dụng O.o(), truyền đối tượng làm đối số đầu tiên và lệnh gọi lại làm đối số thứ hai:
Object.observe(todoModel, observer);
Hãy bắt đầu thực hiện một số thay đổi đối với đối tượng mô hình Todos:
todoModel.label = 'Buy some more milk';
Khi xem bảng điều khiển, chúng ta sẽ nhận được một số thông tin hữu ích! Chúng ta biết thuộc tính nào đã thay đổi, cách thay đổi và giá trị mới là gì.

Tuyệt vời! Tạm biệt tính năng kiểm tra trạng thái thay đổi! Bạn phải khắc bia mộ bằng phông chữ Comic Sans. Hãy thay đổi một thuộc tính khác. Lần này completeBy
:
todoModel.completeBy = '01/01/2014';
Như chúng ta có thể thấy, chúng ta đã nhận lại được báo cáo thay đổi một lần nữa:

Vậy thì tuyệt quá! Nếu bây giờ chúng ta quyết định xoá thuộc tính "completed" khỏi đối tượng:
delete todoModel.completed;

Như chúng ta có thể thấy, báo cáo về các thay đổi được trả về bao gồm thông tin về việc xoá. Như dự kiến, giá trị mới của thuộc tính hiện không xác định. Như vậy, chúng ta đã biết rằng bạn có thể tìm hiểu thời điểm các tài sản được thêm. Khi chúng đã bị xoá. Về cơ bản, nhóm các thuộc tính trên một đối tượng ("mới", "đã xoá", "đã định cấu hình lại") và nguyên mẫu đang thay đổi (proto).
Giống như trong mọi hệ thống quan sát, cũng có một phương thức để ngừng theo dõi các thay đổi. Trong trường hợp này, đó là Object.unobserve()
, có cùng chữ ký với O.o() nhưng có thể được gọi như sau:
Object.unobserve(todoModel, observer);
Như chúng ta có thể thấy ở bên dưới, mọi thay đổi đối với đối tượng sau khi chạy mã này sẽ không còn trả về danh sách bản ghi thay đổi nữa.

Chỉ định các thay đổi về mối quan tâm
Vì vậy, chúng ta đã xem xét các kiến thức cơ bản về cách lấy lại danh sách thay đổi đối với một đối tượng được quan sát. Nếu bạn chỉ quan tâm đến một số thay đổi đã thực hiện đối với một đối tượng thay vì tất cả thay đổi thì sao? Mọi người đều cần có bộ lọc thư rác. Người quan sát chỉ có thể chỉ định những loại thay đổi mà họ muốn biết thông qua danh sách chấp nhận. Bạn có thể chỉ định điều này bằng cách sử dụng đối số thứ ba cho O.o() như sau:
Object.observe(obj, callback, optAcceptList)
Hãy xem ví dụ về cách sử dụng tính năng này:
// 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
Tuy nhiên, nếu chúng ta xoá nhãn này, hãy lưu ý rằng loại thay đổi này sẽ được báo cáo:
delete todoModel.label;
Nếu bạn không chỉ định danh sách các loại chấp nhận cho O.o(), thì danh sách này sẽ mặc định là các loại thay đổi đối tượng "bản chất" (add
, update
, delete
, reconfigure
, preventExtensions
(đối với trường hợp không thể quan sát được đối tượng không thể mở rộng)).
Thông báo
O.o() cũng đi kèm với khái niệm thông báo. Chúng không giống như những thông báo phiền toái mà bạn nhận được trên điện thoại, mà rất hữu ích. Thông báo tương tự như Trình quan sát đột biến. Các sự kiện này xảy ra khi kết thúc tác vụ vi mô. Trong ngữ cảnh trình duyệt, phần này hầu như luôn nằm ở cuối trình xử lý sự kiện hiện tại.
Thời gian này rất phù hợp vì thường thì một đơn vị công việc đã hoàn tất và giờ đây, trình quan sát có thể thực hiện công việc của mình. Đây là một mô hình xử lý theo lượt hay.
Quy trình làm việc để sử dụng trình thông báo có dạng như sau:

Hãy xem ví dụ về cách sử dụng trình thông báo trong thực tế để xác định thông báo tuỳ chỉnh cho thời điểm các thuộc tính trên đối tượng được lấy hoặc đặt. Hãy theo dõi các bình luận tại đây:
// 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);

Tại đây, chúng ta báo cáo thời điểm giá trị của các thuộc tính dữ liệu thay đổi ("cập nhật"). Mọi thứ khác mà quá trình triển khai của đối tượng chọn báo cáo (notifier.notifyChange()
).
Kinh nghiệm nhiều năm trên nền tảng web đã cho chúng tôi biết rằng phương pháp đồng bộ là điều đầu tiên bạn nên thử vì đây là phương pháp dễ hiểu nhất. Vấn đề là việc này tạo ra một mô hình xử lý nguy hiểm về cơ bản. Nếu đang viết mã và nói rằng cập nhật thuộc tính của một đối tượng, bạn không thực sự muốn tình huống cập nhật thuộc tính của đối tượng đó có thể đã mời một số mã tuỳ ý thực hiện bất cứ điều gì nó muốn. Bạn không nên vô hiệu hoá các giả định khi đang chạy giữa một hàm.
Nếu là đối tượng tiếp nhận dữ liệu, bạn không nên được gọi nếu người khác đang làm việc gì đó. Bạn không muốn được yêu cầu thực hiện công việc trên một trạng thái không nhất quán của thế giới. Cuối cùng, bạn sẽ phải kiểm tra lỗi nhiều hơn. Cố gắng chấp nhận nhiều tình huống xấu hơn và nói chung, đây là một mô hình khó làm việc. Async khó xử lý hơn nhưng về lâu dài thì đây là mô hình tốt hơn.
Giải pháp cho vấn đề này là bản ghi thay đổi tổng hợp.
Bản ghi thay đổi tổng hợp
Về cơ bản, nếu muốn có phương thức truy cập hoặc thuộc tính được tính toán, bạn có trách nhiệm thông báo khi các giá trị này thay đổi. Đây là một chút công việc bổ sung nhưng được thiết kế như một loại tính năng hạng nhất của cơ chế này và các thông báo này sẽ được phân phối cùng với các thông báo còn lại từ các đối tượng dữ liệu cơ bản. Từ các thuộc tính dữ liệu.

Bạn có thể giải quyết việc quan sát phương thức truy cập và các thuộc tính được tính toán bằng notifier.notify – một phần khác của O.o(). Hầu hết các hệ thống quan sát đều muốn có một số hình thức quan sát các giá trị phái sinh. Có nhiều cách để làm việc này. O.o không đánh giá cách nào là "đúng". Thuộc tính được tính toán phải là phương thức truy cập thông báo khi trạng thái nội bộ (riêng tư) thay đổi.
Xin nhắc lại, các nhà phát triển web nên mong đợi thư viện giúp dễ dàng thông báo và áp dụng nhiều phương pháp cho các thuộc tính được tính toán (và giảm mã nguyên mẫu).
Hãy thiết lập ví dụ tiếp theo, đó là một lớp hình tròn. Ý tưởng ở đây là chúng ta có một vòng tròn và có một thuộc tính bán kính. Trong trường hợp này, bán kính là một phương thức truy cập và khi giá trị của bán kính thay đổi, giá trị này sẽ tự thông báo rằng giá trị đã thay đổi. Nội dung này sẽ được phân phối cùng với tất cả các thay đổi khác đối với đối tượng này hoặc bất kỳ đối tượng nào khác. Về cơ bản, nếu bạn đang triển khai một đối tượng mà bạn muốn có các thuộc tính tổng hợp hoặc được tính toán, thì bạn phải chọn một chiến lược để đối tượng này hoạt động. Sau khi bạn thực hiện, toàn bộ hệ thống sẽ phù hợp với ứng dụng.
Bỏ qua mã để xem cách hoạt động này trong 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);
})
}

Thuộc tính phương thức truy cập
Lưu ý nhanh về thuộc tính phương thức truy cập. Như đã đề cập trước đó, bạn chỉ có thể quan sát được các thay đổi về giá trị đối với các thuộc tính dữ liệu. Không dành cho các thuộc tính được tính toán hoặc phương thức truy cập. Lý do là JavaScript không thực sự có khái niệm về thay đổi giá trị đối với phương thức truy cập. Phương thức truy cập chỉ là một tập hợp các hàm.
Nếu bạn chỉ định cho một phương thức truy cập, JavaScript sẽ gọi hàm đó và theo quan điểm của hàm, không có gì thay đổi. Hàm này chỉ cho phép một số mã chạy.
Vấn đề là về mặt ngữ nghĩa, chúng ta có thể xem xét việc gán giá trị - 5 cho giá trị trên. Chúng ta phải biết được điều gì đã xảy ra ở đây. Đây thực sự là một vấn đề không thể giải quyết. Ví dụ sau đây minh hoạ lý do. Không có hệ thống nào thực sự biết ý nghĩa của mã này vì đây có thể là mã tuỳ ý. Trong trường hợp này, lớp này có thể làm bất cứ điều gì nó muốn. Hàm này sẽ cập nhật giá trị mỗi khi được truy cập, vì vậy, việc hỏi xem giá trị đó có thay đổi hay không là không hợp lý.
Quan sát nhiều đối tượng bằng một lệnh gọi lại
Một mẫu khác có thể dùng với O.o() là khái niệm về một trình quan sát lệnh gọi lại duy nhất. Điều này cho phép một lệnh gọi lại được dùng làm "trình quan sát" cho nhiều đối tượng khác nhau. Lệnh gọi lại sẽ được phân phối toàn bộ thay đổi cho tất cả đối tượng mà lệnh gọi lại quan sát được ở "cuối tác vụ vi mô" (Lưu ý sự tương đồng với Đối tượng tiếp nhận dữ liệu về đột biến).

Thay đổi trên quy mô lớn
Có thể bạn đang làm việc trên một ứng dụng thực sự lớn và thường xuyên phải xử lý các thay đổi trên quy mô lớn. Các đối tượng có thể mô tả các thay đổi ngữ nghĩa lớn hơn sẽ ảnh hưởng đến nhiều thuộc tính theo cách ngắn gọn hơn (thay vì truyền tải hàng tấn thay đổi thuộc tính).
O.o() giúp giải quyết vấn đề này dưới dạng hai tiện ích cụ thể: notifier.performChange()
và notifier.notify()
mà chúng tôi đã giới thiệu.

Hãy xem ví dụ này về cách mô tả các thay đổi trên quy mô lớn khi chúng ta xác định đối tượng Thingy bằng một số tiện ích toán học (multiply, increment, incrementAndMultiply). Bất cứ khi nào một tiện ích được sử dụng, tiện ích đó sẽ cho hệ thống biết rằng một tập hợp công việc bao gồm một loại thay đổi cụ thể.
Ví dụ: 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
});
}
}
Sau đó, chúng ta xác định hai đối tượng tiếp nhận dữ liệu cho đối tượng của mình: một đối tượng tiếp nhận dữ liệu cho tất cả các thay đổi và một đối tượng tiếp nhận dữ liệu chỉ báo cáo lại về các loại chấp nhận cụ thể mà chúng ta đã xác định (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);
}
Bây giờ, chúng ta có thể bắt đầu chơi với mã này. Hãy xác định một Thingy mới:
var thingy = new Thingy(2, 4);
Quan sát rồi thực hiện một số thay đổi. Ôi, thật vui. Có quá nhiều thứ!
// 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 }

Mọi thứ bên trong "hàm thực thi" được coi là công việc của "thay đổi lớn". Những đối tượng tiếp nhận "thay đổi lớn" sẽ chỉ nhận được bản ghi "thay đổi lớn". Những đối tượng tiếp nhận dữ liệu không có thuộc tính này sẽ nhận được các thay đổi cơ bản do công việc mà "hàm thực thi" đã thực hiện.
Quan sát mảng
Chúng ta đã nói về việc quan sát các thay đổi đối với đối tượng, nhưng còn mảng thì sao?! Câu hỏi hay đó! Khi ai đó nói với tôi: "Câu hỏi hay đấy". Tôi chưa bao giờ nghe câu trả lời của họ vì tôi đang bận tự chúc mừng mình vì đã đặt một câu hỏi hay như vậy. Nhưng tôi đã lạc đề. Chúng tôi cũng có các phương thức mới để xử lý mảng!
Array.observe()
là một phương thức xử lý các thay đổi trên quy mô lớn đối với chính nó (ví dụ: nối, chuyển đổi hoặc bất kỳ nội dung nào ngầm thay đổi độ dài của nó) dưới dạng bản ghi thay đổi "nối". Về nội bộ, lớp này sử dụng notifier.performChange("splice",...)
.
Dưới đây là ví dụ về việc chúng ta quan sát "mảng" mô hình và tương tự như vậy, nhận lại danh sách thay đổi khi có bất kỳ thay đổi nào đối với dữ liệu cơ bản:
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';

Hiệu suất
Cách để suy nghĩ về tác động của O.o() đối với hiệu suất tính toán là coi nó như một bộ nhớ đệm đọc. Nói chung, bộ nhớ đệm là một lựa chọn tuyệt vời khi (theo thứ tự quan trọng):
- Tần suất đọc chiếm ưu thế so với tần suất ghi.
- Bạn có thể tạo một bộ nhớ đệm để trao đổi lượng công việc không đổi liên quan trong quá trình ghi để có hiệu suất tốt hơn về mặt thuật toán trong quá trình đọc.
- Bạn có thể chấp nhận việc thời gian ghi bị chậm lại một cách liên tục.
O.o() được thiết kế cho các trường hợp sử dụng như 1).
Tính năng kiểm tra thay đổi yêu cầu giữ lại bản sao của tất cả dữ liệu mà bạn đang quan sát. Điều này có nghĩa là bạn phải chịu chi phí bộ nhớ cấu trúc để kiểm tra trạng thái thay đổi mà bạn không nhận được với O.o(). Mặc dù là một giải pháp tạm thời hiệu quả, nhưng việc kiểm tra trạng thái thay đổi cũng là một phương thức trừu tượng bị rò rỉ về cơ bản, có thể tạo ra sự phức tạp không cần thiết cho các ứng dụng.
Tại sao? Chạy quy trình kiểm tra dữ liệu bất cứ khi nào dữ liệu có thể đã thay đổi. Không có cách nào hiệu quả để thực hiện việc này và mọi phương pháp đều có những nhược điểm đáng kể (ví dụ: việc kiểm tra khoảng thời gian thăm dò ý kiến có thể dẫn đến các cấu phần phần mềm ảo và tình trạng tương tranh giữa các vấn đề về mã). Tính năng kiểm tra thay đổi cũng yêu cầu một sổ đăng ký toàn cầu của trình quan sát, tạo ra các mối nguy hiểm rò rỉ bộ nhớ và chi phí tháo dỡ mà O.o() tránh được.
Hãy cùng xem xét một số con số.
Các bài kiểm thử điểm chuẩn dưới đây (có trên GitHub) cho phép chúng ta so sánh tính năng kiểm tra thay đổi với O.o(). Các bài kiểm thử này được cấu trúc dưới dạng biểu đồ của Kích thước-tập-hợp-đối-tượng-đã-quan-sát so với Số-lượng-sự-thay-đổi. Kết quả chung là hiệu suất kiểm tra thay đổi tỷ lệ thuận với số lượng đối tượng được quan sát theo thuật toán, trong khi hiệu suất O.o() tỷ lệ thuận với số lượng đột biến đã thực hiện.
Kiểm tra không sạch

Chrome đã bật Object.observe()

Thêm đối tượng polyfill vào Object.observe()
Tuyệt vời – vậy là bạn có thể sử dụng O.o() trong Chrome 36, nhưng còn việc sử dụng hàm này trong các trình duyệt khác thì sao? Chúng tôi đã chuẩn bị sẵn thông tin cho bạn. Observe-JS của Polymer là một polyfill cho O.o() sẽ sử dụng phương thức triển khai gốc nếu có, nhưng nếu không thì sẽ polyfill và thêm một số tính năng hữu ích lên trên. Tính năng này cung cấp thông tin tổng hợp về thế giới, tóm tắt các thay đổi và đưa ra báo cáo về những thay đổi đó. Có hai tính năng rất mạnh mẽ mà công cụ này hiển thị là:
- Bạn có thể quan sát các đường dẫn. Điều này có nghĩa là bạn có thể nói rằng tôi muốn quan sát "foo.bar.baz" từ một đối tượng nhất định và chúng sẽ cho bạn biết thời điểm giá trị tại đường dẫn đó thay đổi. Nếu không thể truy cập đường dẫn, thì giá trị này sẽ được coi là không xác định.
Ví dụ về việc quan sát một giá trị tại một đường dẫn từ một đối tượng nhất định:
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.
});
- Nó sẽ cho bạn biết về các đoạn mảng. Về cơ bản, các hoạt động nối mảng là tập hợp tối thiểu các hoạt động nối mà bạn sẽ phải thực hiện trên một mảng để chuyển đổi phiên bản cũ của mảng thành phiên bản mới của mảng. Đây là một loại phép biến đổi hoặc chế độ xem khác của mảng. Đây là lượng công việc tối thiểu bạn cần làm để chuyển từ trạng thái cũ sang trạng thái mới.
Ví dụ về việc báo cáo các thay đổi đối với một mảng dưới dạng một tập hợp các đoạn nối tối thiểu:
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.
});
});
Khung và Object.observe()
Như đã đề cập, O.o() sẽ mang đến cho các khung và thư viện một cơ hội lớn để cải thiện hiệu suất liên kết dữ liệu trong các trình duyệt hỗ trợ tính năng này.
Yehuda Katz và Erik Bryn của Ember xác nhận rằng việc thêm tính năng hỗ trợ cho O.o() nằm trong lộ trình sắp tới của Ember. Misko Hervy của Angular đã viết một tài liệu thiết kế về tính năng phát hiện thay đổi được cải thiện của Angular 2.0. Phương pháp lâu dài của họ sẽ là tận dụng Object.observe() khi tính năng này ra mắt trong Chrome phiên bản ổn định, chọn Watchtower.js, phương pháp phát hiện thay đổi của riêng họ cho đến thời điểm đó. Thật sự rất thú vị.
Kết luận
O.o() là một tính năng bổ sung mạnh mẽ cho nền tảng web mà bạn có thể sử dụng ngay hôm nay.
Chúng tôi hy vọng rằng theo thời gian, tính năng này sẽ xuất hiện trên nhiều trình duyệt hơn, cho phép các khung JavaScript tăng hiệu suất nhờ khả năng truy cập vào các tính năng quan sát đối tượng gốc. Những người nhắm đến Chrome có thể sử dụng O.o() trong Chrome 36 (trở lên) và tính năng này cũng sẽ có trong bản phát hành Opera trong tương lai.
Vì vậy, hãy tiếp tục và trò chuyện với các tác giả của khung JavaScript về Object.observe()
và cách họ dự định sử dụng Object.observe()
để cải thiện hiệu suất liên kết dữ liệu trong ứng dụng của bạn. Chắc chắn chúng ta sẽ có những khoảnh khắc thú vị phía trước!
Tài nguyên
- Object.observe() trên trang wiki của Harmony>
- Databinding với Object.observe() của Rick Waldron
- Mọi điều bạn muốn biết về Object.observe() – JSConf
- Tại sao Object.observe() là tính năng ES7 tốt nhất
Cảm ơn Rafael Weinstein, Jake Archibald, Eric Bidelman, Paul Kinlan và Vivian Cromwell đã đóng góp ý kiến và bài đánh giá.