Object.observe() के साथ डेटा-बाइंडिंग रेवलूशन

Addy Osmani
Addy Osmani

परिचय

एक क्रांति आने वाली है. JavaScript में एक नया ऐड-ऑन जोड़ा गया है. इससे डेटा बाइंडिंग के बारे में उन सभी चीज़ों को बदला जा सकता है जो आपको पता हैं. इससे यह भी बदल जाएगा कि आपकी कितनी एमवीसी लाइब्रेरी, बदलावों और अपडेट के लिए मॉडल को मॉनिटर करने के तरीके का इस्तेमाल करती हैं. क्या आप प्रॉपर्टी के निगरानी की सुविधा का इस्तेमाल करने वाले ऐप्लिकेशन की परफ़ॉर्मेंस को बेहतर बनाने के लिए तैयार हैं?

ठीक है. ठीक है. और देरी किए बिना, मुझे यह बताते हुए खुशी हो रही है कि Object.observe() Chrome 36 स्टेबल वर्शन में उपलब्ध हो गया है. [WOOOO. द क्राउड गोज़ वाइल्ड].

Object.observe(), आने वाले समय में ECMAScript स्टैंडर्ड का हिस्सा होगा. यह JavaScript ऑब्जेक्ट में होने वाले बदलावों को असींक्रोनस तरीके से देखने का एक तरीका है. इसके लिए, अलग से किसी लाइब्रेरी की ज़रूरत नहीं होती. इससे ऑब्ज़र्वर को समय के हिसाब से बदलावों के रिकॉर्ड का क्रम मिलता है. इसमें, निगरानी में रखे गए ऑब्जेक्ट के सेट में हुए बदलावों के सेट के बारे में बताया जाता है.

// 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);
    });

});

जब भी कोई बदलाव किया जाता है, तो इसकी सूचना दी जाती है:

बदलाव की रिपोर्ट की गई.

Object.observe() (मुझे इसे O.o() या Oooooooo कहना पसंद है) की मदद से, किसी फ़्रेमवर्क के बिना, दो-तरफ़ा डेटा-बाइंडिंग लागू की जा सकती है.

इसका मतलब यह नहीं है कि आपको किसी एक का इस्तेमाल नहीं करना चाहिए. जटिल कारोबारी लॉजिक वाले बड़े प्रोजेक्ट के लिए, राय देने वाले फ़्रेमवर्क अनमोल होते हैं और आपको उनका इस्तेमाल जारी रखना चाहिए. इससे नए डेवलपर को आसानी से ऑरिएंटेशन मिलता है. साथ ही, कोड को मैनेज करने में कम समय लगता है और सामान्य टास्क को पूरा करने के तरीके के लिए पैटर्न लागू होते हैं. अगर आपको किसी लाइब्रेरी की ज़रूरत नहीं है, तो Polymer जैसी छोटी और ज़्यादा फ़ोकस वाली लाइब्रेरी का इस्तेमाल किया जा सकता है. ये लाइब्रेरी पहले से ही O.o() का फ़ायदा लेती हैं.

भले ही, आपको किसी फ़्रेमवर्क या संगीत वीडियो* लाइब्रेरी का बहुत ज़्यादा इस्तेमाल करना पसंद हो, तब भी O.o() बेहतर तरीके से परफ़ॉर्मेंस को बेहतर बना सकता है. साथ ही, एपीआई में बदलाव किए बिना ही उसे तेज़ी से और आसानी से लागू कर सकता है. उदाहरण के लिए, पिछले साल Angular ने पाया कि एक बेंचमार्क में, जहां किसी मॉडल में बदलाव किए जा रहे थे, गंदी जांच में हर अपडेट के लिए 40 मि॰से॰ और O.o() को हर अपडेट पर 1-2 मि॰से॰ लगते हैं (20-40 गुना ज़्यादा तेज़).

डेटा-बाइंडिंग के लिए, बहुत सारे जटिल कोड की ज़रूरत नहीं होती. इसका मतलब है कि आपको अब बदलावों के लिए पोल करने की ज़रूरत नहीं है. इससे बैटरी की लाइफ़ भी लंबी हो जाती है!

अगर आपका प्रॉडक्ट पहले से ही O.o() पर बेचा जा चुका है, तो सुविधा के बारे में शुरुआती जानकारी देखें. इसके अलावा, इसमें जो समस्याएं हल की जा सकती हैं उनके बारे में ज़्यादा जानने के लिए आगे पढ़ें.

हम किस चीज़ का ध्यान रखना चाहते हैं?

डेटा को मॉनिटर करने का मतलब, आम तौर पर कुछ खास तरह के बदलावों पर नज़र बनाए रखना होता है:

  • रॉ JavaScript ऑब्जेक्ट में किए गए बदलाव
  • प्रॉपर्टी जोड़ने, बदलने, और मिटाने पर
  • जब सरणियों में एलिमेंट जुड़े हुए हों और उनमें अंतर हो
  • ऑब्जेक्ट के प्रोटोटाइप में बदलाव होना

डेटा-बाइंडिंग की अहमियत

मॉडल-व्यू कंट्रोल को अलग करने में, डेटा-बाइंडिंग की अहमियत बढ़ जाती है. एचटीएमएल, डेक्लेरेटिव मेकनिज़म का एक बेहतरीन उदाहरण है, लेकिन यह पूरी तरह से स्टैटिक है. आम तौर पर, आपको सिर्फ़ अपने डेटा और डीओएम के बीच के संबंध की जानकारी देनी होती है और डीओएम को अप-टू-डेट रखना होता है. इससे फ़ायदा होता है और बार-बार एक ही कोड लिखने में लगने वाला समय भी बचता है. यह आपके ऐप्लिकेशन की अंदरूनी स्थिति या सर्वर के बीच के डीओएम पर और उससे डेटा भेजता है.

डेटा-बाइंडिंग तब खास तौर पर काम की होती है, जब आपके पास एक ऐसा यूज़र-इंटरफ़ेस हो जिसमें आपको अपने व्यू में मौजूद कई एलिमेंट के साथ, डेटा मॉडल में मौजूद कई प्रॉपर्टी के बीच संबंध जोड़ने की ज़रूरत हो. यह आज के समय में, एक पेज वाले ऐप्लिकेशन में काफ़ी आम है.

ब्राउज़र में डेटा को नेटिव तरीके से देखने का तरीका उपलब्ध कराने से, हम JavaScript फ़्रेमवर्क (और आपकी लिखी गई छोटी-छोटी यूटिलिटी लाइब्रेरी) को मॉडल डेटा में होने वाले बदलावों को देखने का तरीका देते हैं. इसके लिए, उन्हें दुनिया में आज इस्तेमाल होने वाले कुछ धीमे हैक पर निर्भर नहीं रहना पड़ता.

आज दुनिया कैसी दिखती है

गंदे जांच करना

आपने डेटा-बाइंडिंग पहले कहां देखी है? अगर आपने वेबऐप्लिकेशन (जैसे, Angular, Knockout) बनाने के लिए, किसी आधुनिक MV* लाइब्रेरी का इस्तेमाल किया है, तो हो सकता है कि आपने मॉडल डेटा को DOM से बांधने का तरीका जाना हो. रीफ़्रेशर के तौर पर, यहां फ़ोन की सूची वाले ऐप्लिकेशन का उदाहरण दिया गया है. इसमें हम phones कलेक्शन (JavaScript में तय किया गया) में मौजूद हर फ़ोन की वैल्यू को किसी सूची आइटम से बाइंड कर रहे हैं, ताकि हमारा डेटा और यूज़र इंटरफ़ेस (यूआई) हमेशा सिंक रहे:

<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>

और कंट्रोलर के लिए JavaScript:

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.'}
  ];
});

जब भी मॉडल का डेटा बदलता है, तो DOM में मौजूद हमारी सूची अपडेट हो जाती है. Angular इसे कैसे पूरा करता है? दरअसल, यह बैकग्राउंड में 'गड़बड़ी की जांच' नाम की एक प्रोसेस करता है.

गड़बड़ी की जांच करना

गड़बड़ी की जांच करने का बुनियादी सिद्धांत यह है कि डेटा कभी भी बदल सकता है. इसलिए, लाइब्रेरी को यह जांच करनी होगी कि डाइजेस्ट या बदलाव के साइकल की मदद से डेटा में बदलाव हुआ है या नहीं. ऐंग्युलर के मामले में डाइजेस्ट साइकल, उन सभी एक्सप्रेशन की पहचान करती है जिन्हें देखा जाना चाहिए. इससे यह देखा जाता है कि कोई बदलाव हुआ है या नहीं. यह मॉडल की पिछली वैल्यू के बारे में जानता है. अगर वे बदल गई हैं, तो बदलाव इवेंट ट्रिगर होता है. डेवलपर के लिए, यहां मुख्य लाभ यह है कि आपको अपरिष्कृत JavaScript ऑब्जेक्ट डेटा का उपयोग करने की सुविधा मिलती है, जो उपयोग करने में अच्छा है और अच्छी तरह से लिखा गया है. इसकी कमी यह है कि इसका एल्गोरिदम काम नहीं करता है और यह बहुत महंगा हो सकता है.

डिवाइस के गंदे होने की जांच की जा रही है.

इस कार्रवाई पर होने वाला खर्च, मॉनिटर किए गए ऑब्जेक्ट की कुल संख्या के अनुपात में होता है. मुझे बहुत सारी गड़बड़ियों की जांच करनी पड़ सकती है. साथ ही, डेटा में बदलाव होने पर भी, गड़बड़ी की जांच करने का कोई तरीका चुनना पड़ सकता है. इसके लिए, फ़्रेमवर्क कई तरह की चालाकियां करते हैं. यह साफ़ तौर पर नहीं कहा जा सकता कि यह कभी सही होगा या नहीं.

वेब नेटवर्क के पास, जानकारी देने वाले अपने तरीकों में कुछ नया करने और उन्हें बेहतर बनाने की ज़्यादा क्षमता होनी चाहिए. उदाहरण के लिए,

  • कंस्ट्रेंट-आधारित मॉडल सिस्टम
  • ऑटो-परसिस्टेंस सिस्टम (उदाहरण के लिए, IndexedDB या localStorage में लगातार होने वाले बदलाव)
  • कंटेनर ऑब्जेक्ट (Ember, Backbone)

कंटेनर ऑब्जेक्ट वे होते हैं जहां फ़्रेमवर्क से ऑब्जेक्ट बनाए जाते हैं. ये ऑब्जेक्ट, कंटेनर के अंदर मौजूद होते हैं. उनके पास डेटा के ऐक्सेसर होते हैं. वे आपके सेट किए गए या पाए गए कॉन्टेंट और इंटरनल ब्रॉडकास्ट को कैप्चर कर सकते हैं. यह काफ़ी कारगर है. यह अपेक्षाकृत बेहतर परफ़ॉर्म करता है और एल्गोरिदम के हिसाब से अच्छा काम करता है. 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

यहां क्या बदलाव हुआ है, यह पता लगाने में लगने वाला समय, बदलाव की संख्या के हिसाब से तय होता है. एक और समस्या यह है कि अब आपने इस तरह के अलग ऑब्जेक्ट का इस्तेमाल किया है. आम तौर पर, आपको सर्वर से मिलने वाले डेटा को इन ऑब्जेक्ट में बदलना होगा, ताकि उनका निगरानी किया जा सके.

यह मौजूदा JS कोड के साथ बहुत अच्छी तरह से काम नहीं करता है, क्योंकि ज़्यादातर कोड यह मानते हैं कि यह रॉ डेटा का इस्तेमाल कर सकता है. ये खास तरह की चीज़ों के लिए नहीं है.

Introducing Object.observe()

हमारा मकसद, दोनों तरीकों का फ़ायदा लेना है. अगर हम चाहें, तो रॉ डेटा ऑब्जेक्ट (सामान्य JavaScript ऑब्जेक्ट) के साथ डेटा को देखने का तरीका और हर समय हर चीज़ की गड़बड़ी की जांच करने की ज़रूरत नहीं है. एल्गोरिदम के हिसाब से अच्छा परफ़ॉर्म करने वाला कोई पेज. ऐसा कॉन्टेंट जो अच्छी तरह से कंपोज हो और प्लैटफ़ॉर्म में पहले से मौजूद हो. Object.observe() की खास बात यह है कि यह आपको इन सभी सुविधाओं का फ़ायदा एक ही जगह पर देता है.

इसकी मदद से, हम किसी ऑब्जेक्ट को निगरानी में रख सकते हैं, प्रॉपर्टी में बदलाव कर सकते हैं, और बदलाव की रिपोर्ट देख सकते हैं. हालांकि, इस थ्योरी के बारे में इतना ही जानने के लिए, आइए कुछ कोड के बारे में बात करें!

Object.observe()

Object.observe() और Object.unobserve()

मान लें कि हमारे पास एक मॉडल दिखाने वाला आसान वेनिला JavaScript ऑब्जेक्ट है:

// A model can be a simple vanilla object
var todoModel = {
  label: 'Default',
  completed: false
};

इसके बाद, ऑब्जेक्ट में म्यूटेशन (बदलाव) होने पर, हम कॉलबैक तय कर सकते हैं:

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
  });
}

इसके बाद, O.o() का इस्तेमाल करके इन बदलावों को देखा जा सकता है. इसके लिए, पहले आर्ग्युमेंट के तौर पर ऑब्जेक्ट और दूसरे आर्ग्युमेंट के तौर पर कॉलबैक को पास करें:

Object.observe(todoModel, observer);

चलिए, अपने Todos मॉडल ऑब्जेक्ट में कुछ बदलाव करना शुरू करते हैं:

todoModel.label = 'Buy some more milk';

कंसोल देखने पर, हमें कुछ काम की जानकारी मिलती है! हमें पता है कि प्रॉपर्टी में क्या बदलाव हुआ है, उसमें कैसे बदलाव हुआ है, और नई वैल्यू क्या है.

कंसोल रिपोर्ट

वाह! अलविदा, गंदे चेकिंग! आपके कब्रिस्तान की जानकारी Comic Sans फ़ॉन्ट में होनी चाहिए. चलिए, दूसरी प्रॉपर्टी बदलते हैं. इस बार completeBy:

todoModel.completeBy = '01/01/2014';

जैसा कि हम देख सकते हैं कि हमें फिर से बदलाव की रिपोर्ट मिल गई है:

रिपोर्ट बदलें.

बढ़िया. अगर अब हम अपने ऑब्जेक्ट से 'पूरा हो गया' प्रॉपर्टी को मिटाने का फ़ैसला लेते हैं, तो क्या होगा:

delete todoModel.completed;
पूरा हुआ

जैसा कि हम देख सकते हैं, दिखाए गए बदलावों की रिपोर्ट में, मिटाए जाने के बारे में जानकारी शामिल होती है. उम्मीद के मुताबिक, प्रॉपर्टी की नई वैल्यू अब तय नहीं है. हमें पता है कि प्रॉपर्टी जोड़ने पर यह पता किया जा सकता है. जब उन्हें मिटा दिया गया हो. असल में, किसी ऑब्जेक्ट पर प्रॉपर्टी का सेट ("नया", "मिटाया गया", "फिर से कॉन्फ़िगर किया गया") और इसका प्रोटोटाइप बदलना (proto) है.

किसी भी निगरानी सिस्टम की तरह, बदलावों को सुनने की सुविधा को बंद करने का तरीका भी मौजूद है. इस मामले में, यह Object.unobserve() है, जिसमें O.o() के हस्ताक्षर एक जैसे होते हैं. हालांकि, इसे इस तरह कॉल किया जा सकता है:

Object.unobserve(todoModel, observer);

जैसा कि हम नीचे देख सकते हैं, उसके चलने के बाद ऑब्जेक्ट में कोई भी बदलाव करने पर, बदलाव के रिकॉर्ड की सूची नहीं मिलेगी.

म्यूटेशन

दिलचस्पी में बदलावों के बारे में बताना

इसलिए, हमने निगरानी में रखे गए ऑब्जेक्ट में हुए बदलावों की सूची वापस पाने के बुनियादी तरीकों के बारे में बताया है. अगर आपको किसी ऑब्जेक्ट में किए गए सभी बदलावों के बजाय, सिर्फ़ कुछ बदलावों में दिलचस्पी है, तो क्या होगा?. हर किसी को स्पैम फ़िल्टर की ज़रूरत होती है. ऑब्ज़र्वर सिर्फ़ ऐसे बदलावों के बारे में बता सकते हैं जिन्हें वे स्वीकार करने की सूची के ज़रिए सुनना चाहते हैं. इसे O.o() पर तीसरे तर्क का इस्तेमाल करके, इस तरह से तय किया जा सकता है:

Object.observe(obj, callback, optAcceptList)

आइए, अब एक उदाहरण के ज़रिए इस बारे में जानते हैं कि इसका इस्तेमाल कैसे किया जा सकता है:

// 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

हालांकि, अगर अब हम लेबल मिटा देते हैं, तो ध्यान दें कि इस तरह के बदलाव की रिपोर्ट की जाती है:

delete todoModel.label;

अगर आपने O.o() में स्वीकार करने के तरीकों की सूची नहीं दी है, तो यह डिफ़ॉल्ट रूप से "इंट्रिंसिस" ऑब्जेक्ट में होने वाले बदलाव के टाइप पर सेट होती है (add, update, delete, reconfigure, और preventExtensions). ऐसा तब होता है, जब किसी ऑब्जेक्ट का एक्सटेंसिबल नहीं बनाया जा सकता और उसकी जांच नहीं की जा सकती.

सूचनाएं

O.o() में सूचनाओं का इस्तेमाल भी किया जाता है. ये सूचनाएं, फ़ोन पर मिलने वाली उन परेशान करने वाली सूचनाओं से अलग होती हैं. ये सूचनाएं आपके लिए मददगार होती हैं. सूचनाएं, म्यूटेशन ऑब्ज़र्वर की तरह होती हैं. ये माइक्रो-टास्क के आखिर में होते हैं. ब्राउज़र के संदर्भ में, इस इवेंट हैंडलर के आखिर में हमेशा ऐसा होता रहेगा.

यह समय अच्छा होता है, क्योंकि आम तौर पर एक यूनिट का काम पूरा हो जाता है और अब ऑब्जर्वर अपना काम कर पाते हैं. यह एक अच्छा टर्न-आधारित प्रोसेसिंग मॉडल है.

नोटिफ़ायर का इस्तेमाल करने का वर्कफ़्लो कुछ ऐसा दिखता है:

सूचनाएं

आइए, इस बात का एक उदाहरण देखते हैं कि किसी ऑब्जेक्ट की प्रॉपर्टी मिलने या सेट करने के बाद, पसंद के मुताबिक सूचनाएं तय करने के लिए सूचना देने वाली इकाइयों का इस्तेमाल कैसे किया जा सकता है. यहां दी गई टिप्पणियों पर नज़र रखें:

// 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);
सूचना कंसोल

यहां हम डेटा प्रॉपर्टी की वैल्यू में बदलाव होने पर ("अपडेट") रिपोर्ट करते हैं. ऑब्जेक्ट के लागू होने की जानकारी देने वाली कोई भी अन्य चीज़ (notifier.notifyChange()).

वेब प्लैटफ़ॉर्म पर सालों के अनुभव से हमें पता चला है कि सबसे पहले, सिंक्रोनस (सिंक्रोनस) तरीका अपनाया जाता है, क्योंकि इसकी मदद से अपनी दिमागी कसरत को काफ़ी आसान बनाया जा सकता है. समस्या यह है कि इससे प्रॉसेसिंग का एक खतरनाक मॉडल बनता है. अगर कोड लिखते समय, किसी ऑब्जेक्ट की प्रॉपर्टी को अपडेट करने के लिए कहा जाता है, तो आपको यह नहीं चाहिए कि उस ऑब्जेक्ट की प्रॉपर्टी को अपडेट करने के लिए, कोई ऐसा कोड इस्तेमाल किया जाए जो अपनी मर्ज़ी से कुछ भी कर सके. जब आप फ़ंक्शन के बीच में दौड़ रहे हों, तब अपनी मान्यताओं का अमान्य होना सही नहीं है.

अगर आप एक ऑब्ज़र्वर हैं, तो अगर कोई व्यक्ति किसी चीज़ के बीच में है, तो आप खुद कॉल में शामिल नहीं होना चाहेंगे. आपको दुनिया के किसी ऐसे देश में काम करने के लिए नहीं भेजा जाए जहां आपके काम की ज़रूरत न हो. गड़बड़ी की ज़्यादा जांच करनी पड़ती है. बहुत ज़्यादा खराब स्थितियों को बर्दाश्त करने की कोशिश करना. आम तौर पर, इस मॉडल के साथ काम करना मुश्किल होता है. एसिंक्रोनस से निपटने में मुश्किल होती है, लेकिन दिन के आखिर में यह एक बेहतर मॉडल है.

सिंथेटिक बदलाव रिकॉर्ड करके इस समस्या का समाधान किया जा सकता है.

सिंथेटिक बदलाव रिकॉर्ड

इसका मतलब है कि अगर आपको ऐक्सेसर या कंप्यूट की गई प्रॉपर्टी चाहिए, तो इन वैल्यू में बदलाव होने पर आपको सूचना देना आपकी ज़िम्मेदारी है. इसमें थोड़ी और मेहनत करनी पड़ती है, लेकिन इसे इस तरीके की बेहतरीन सुविधा के तौर पर डिज़ाइन किया गया है और ये सूचनाएं, मौजूदा डेटा ऑब्जेक्ट की बाकी सूचनाओं के साथ डिलीवर की जाएंगी. डेटा प्रॉपर्टी से.

सिंथेटिक बदलाव रिकॉर्ड

ऐक्सेस करने वाले फ़ंक्शन और कैलकुलेट की गई प्रॉपर्टी को निगरानी करने की समस्या को notifier.notify की मदद से हल किया जा सकता है. यह O.o() का एक और हिस्सा है. ज़्यादातर निगरानी सिस्टम, व्युत्पन्न की गई वैल्यू को निगरानी करने के लिए किसी न किसी तरीके का इस्तेमाल करते हैं. ऐसा करने के कई तरीके हैं. ओ.ओ "सही" तरीके के बारे में कोई फ़ैसला नहीं करते. कैलकुलेट की गई प्रॉपर्टी, ऐसे ऐक्सेसर होनी चाहिए जो इंटरनल (निजी) स्टेटस में बदलाव होने पर सूचना दें.

एक बार फिर, वेबडेवलपर को लाइब्रेरी से यह उम्मीद करनी चाहिए कि वे कंप्यूट की गई प्रॉपर्टी के बारे में सूचनाएं देने और अलग-अलग तरीकों को आसान बनाने में मदद करें. साथ ही, बॉयलरप्लेट को कम करें.

आइए, अगला उदाहरण सेट अप करें. यह एक सर्कल क्लास है. मकसद यह है कि हमारे पास यह सर्कल है और इसमें एक खास दायरे की प्रॉपर्टी है. इस मामले में, रेडियस एक ऐक्सेसर होता है और जब इसकी वैल्यू में बदलाव होता है, तो अपने-आप में यह सूचना मिलती है कि वैल्यू बदल गई है. इसे इस ऑब्जेक्ट या किसी दूसरे ऑब्जेक्ट में किए गए सभी बदलावों के साथ डिलीवर किया जाएगा. दरअसल, अगर आपको किसी ऑब्जेक्ट को लागू करना है, तो आपको सिंथेटिक या कंप्यूट की गई प्रॉपर्टी की ज़रूरत होगी. इसके अलावा, आपको इसके काम करने के लिए रणनीति भी चुननी होगी. ऐसा करने के बाद, यह आपके पूरे सिस्टम में फ़िट हो जाएगा.

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);
  })
}
सिंथेटिक बदलाव रिकॉर्ड कंसोल

ऐक्सेसर प्रॉपर्टी

ऐक्सेस करने वाली प्रॉपर्टी के बारे में खास जानकारी. हमने पहले बताया था कि डेटा प्रॉपर्टी के लिए, सिर्फ़ वैल्यू में हुए बदलावों को देखा जा सकता है. यह कंप्यूट की गई प्रॉपर्टी या ऐक्सेसर के लिए नहीं है. इसकी वजह यह है कि JavaScript में, ऐक्सेसर की वैल्यू में बदलाव करने की कोई जानकारी नहीं है. ऐक्सेसर, फ़ंक्शन का सिर्फ़ एक कलेक्शन होता है.

अगर किसी ऐक्सेसर को असाइन किया जाता है, तो JavaScript सिर्फ़ उस फ़ंक्शन को वहां पर लागू करता है. इसके हिसाब से, कुछ भी नहीं बदला है. इसने सिर्फ़ कुछ कोड को चलने का मौका दिया.

समस्या यह है कि हम इसके लिए ऊपर दिए गए असाइनमेंट में - 5 की वैल्यू पर नज़र डाल सकते हैं. हमें पता होना चाहिए कि यहां क्या हुआ. यह समस्या हल नहीं की जा सकती. उदाहरण में इसकी वजह बताई गई है. किसी भी सिस्टम के पास इसका मतलब जानने का कोई तरीका नहीं है, क्योंकि यह आर्बिट्रेरी कोड हो सकता है. इस मामले में वह जो भी चाहे, कर सकता है. हर बार ऐक्सेस करने पर, यह वैल्यू अपडेट होती रहती है. इसलिए, यह पूछना कि इसमें बदलाव हुआ है या नहीं, कोई मायने नहीं रखता.

एक कॉलबैक की मदद से कई ऑब्जेक्ट को मॉनिटर करना

O.o() के साथ एक और पैटर्न, एक कॉलबैक ऑब्ज़र्वर का नोशन है. इसकी मदद से, एक कॉलबैक को कई अलग-अलग ऑब्जेक्ट के लिए "ऑब्ज़र्वर" के तौर पर इस्तेमाल किया जा सकता है. कॉलबैक को "माइक्रोटास्क के आखिर" में दिखने वाले सभी ऑब्जेक्ट में बदलावों का पूरा सेट डिलीवर किया जाएगा. ध्यान दें कि यह म्यूटेशन ऑब्ज़र्वर से मिलता-जुलता है.

एक कॉलबैक की मदद से कई ऑब्जेक्ट को मॉनिटर करना

बड़े पैमाने पर बदलाव

हो सकता है कि आप किसी बहुत बड़े ऐप्लिकेशन पर काम कर रहे हों और आपको नियमित तौर पर बड़े पैमाने पर बदलाव करने पड़ते हों. ऐसा हो सकता है कि ऑब्जेक्ट बड़े सिमैंटिक बदलावों के बारे में बताना चाहें. इससे, कई प्रॉपर्टी पर ज़्यादा असर पड़ सकता है. ऐसा, प्रॉपर्टी में कई तरह के बदलाव करने के बजाय होता है.

O.o(), दो खास यूटिलिटी के तौर पर इसमें मदद करता है: notifier.performChange() और notifier.notify(), जिन्हें हमने पहले ही शामिल कर दिया है.

बड़े पैमाने पर बदलाव

आइए, बड़े पैमाने पर बदलावों के बारे में बताने के तरीके के एक उदाहरण पर नज़र डालें. इसमें हमने कुछ मैथ यूटिलिटी (गुणा, बढ़ाएं, और बढ़ाएं और गुणा करें) के साथ Thingy ऑब्जेक्ट तय किया है. जब भी किसी सुविधा का इस्तेमाल किया जाता है, तो इससे सिस्टम को पता चलता है कि काम के कलेक्शन में खास तरह का बदलाव शामिल है.

उदाहरण के लिए: 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
    });
  }
}

इसके बाद, हम अपने ऑब्जेक्ट के लिए दो ऑब्ज़र्वर तय करते हैं: एक जो बदलावों के लिए कैच-ऑल है और दूसरा जो सिर्फ़ हमारे तय किए गए खास तरह के स्वीकार के बारे में रिपोर्ट करेगा (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);
}

अब हम इस कोड का इस्तेमाल करके गेम खेलना शुरू कर सकते हैं. चलिए, एक नई Thingy बनाते हैं:

var thingy = new Thingy(2, 4);

इसे देखें और फिर कुछ बदलाव करें. अरे वाह, कितना मज़ेदार है. कई सारी चीज़ें!

// 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 }
बड़े पैमाने पर बदलाव

"perform function" में मौजूद हर चीज़ को “big-change” का काम माना जाता है. “big-change” को स्वीकार करने वाले ऑब्ज़र्वर को सिर्फ़ “big-change” रिकॉर्ड मिलेगा. ऐसे ऑब्ज़र्वर जिन्हें “परफ़ॉर्म फ़ंक्शन” वाले काम के नतीजे में बुनियादी बदलाव नहीं मिलेंगे.

अरे को ऑब्ज़र्व करना

हमने ऑब्जेक्ट में होने वाले बदलावों को मॉनिटर करने के बारे में बात की है, लेकिन ऐरे के बारे में क्या?! यह एक अच्छा सवाल है. जब कोई व्यक्ति मुझसे कहे, "बहुत अच्छा सवाल." मुझे उनका जवाब सुनाई नहीं दिया, क्योंकि मैं इतना बढ़िया सवाल पूछने के लिए खुद को बधाई देने में व्यस्त हूं. हालांकि, मैंने हिचकिचाहट नहीं की. हमारे पास अरे के साथ काम करने के नए तरीके भी हैं!

Array.observe() एक ऐसा तरीका है जो बड़े पैमाने पर होने वाले बदलावों को "स्प्लिस" बदलाव रिकॉर्ड के तौर पर ट्रीट करता है. जैसे, स्प्लिस, अनशफ़्ट या ऐसा कोई भी बदलाव जिससे सीधे तौर पर इसकी लंबाई में बदलाव होता है. यह अंदरूनी तौर पर notifier.performChange("splice",...) का इस्तेमाल करता है.

यहां एक उदाहरण दिया गया है, जिसमें हमें मॉडल "कलेक्शन" दिखता है. इसी तरह, दिए गए डेटा में कोई बदलाव होने पर, बदलावों की सूची दिखती है:

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';
अरे पर नज़र रखी जा रही है

परफ़ॉर्मेंस

O.o() के कंप्यूटेशनल परफ़ॉर्मेंस के असर के बारे में सोचने का तरीका यह है कि आप इसे एक रीड कैश मेमोरी की तरह मानें. आम तौर पर, कैश मेमोरी का इस्तेमाल तब किया जा सकता है, जब (ज़रूरत के हिसाब से):

  1. डेटा पढ़ने की फ़्रीक्वेंसी, डेटा लिखने की फ़्रीक्वेंसी से ज़्यादा होती है.
  2. आपके पास एक कैश बनाने का विकल्प होता है. यह कैश, डेटा को लिखने के दौरान किए जाने वाले काम को कम करता है, ताकि डेटा को पढ़ने के दौरान एल्गोरिदम की परफ़ॉर्मेंस बेहतर हो.
  3. लेखन का स्थायी समय धीमा होना स्वीकार है.

O.o() को 1) जैसे इस्तेमाल के उदाहरणों के लिए डिज़ाइन किया गया है.

गड़बड़ी की जांच करने के लिए, उस डेटा की कॉपी सेव रखना ज़रूरी है जिसकी निगरानी की जा रही है. इसका मतलब है कि गड़बड़ी की जांच करने के लिए, आपको स्ट्रक्चरल मेमोरी का खर्च करना पड़ता है, जो O.o() में नहीं होता. गड़बड़ी की जांच, एक अच्छा और तुरंत काम करने वाला समाधान है. हालांकि, यह मूल रूप से एक ऐसा एब्स्ट्रैक्शन है जिसमें डेटा में बदलाव होने पर, डेटा को अपडेट नहीं किया जाता. इससे ऐप्लिकेशन के लिए ग़ैर-ज़रूरी जटिलताएं पैदा हो सकती हैं.

क्यों? खैर, डेटा बदलकर हो सकता है कि किसी भी समय गंदी जांच को चलाना हो. ऐसा करने का कोई बहुत पक्का तरीका नहीं है और इसके किसी भी तरीके में कुछ बड़ी कमियां हैं.उदाहरण के लिए, पोलिंग के अंतराल की जांच करने से विज़ुअल आर्टफ़ैक्ट और कोड से जुड़ी समस्याओं के बीच रेस कंडीशन का खतरा हो सकता है. गंदे जांच के लिए ऑब्ज़र्वर की ग्लोबल रजिस्ट्री की भी ज़रूरत होती है, जिससे मेमोरी लीक होने का खतरा पैदा होता है और O.o() से बचा जा सकता है.

चलिए, कुछ आंकड़ों पर नज़र डालते हैं.

यहां दिए गए मानदंड वाले टेस्ट (GitHub पर उपलब्ध हैं), हमें गड़बड़ी की जांच करने की सुविधा बनाम O.o() की तुलना करने की अनुमति देते हैं. इन्हें, निगरानी किए गए ऑब्जेक्ट के सेट के साइज़ बनाम म्यूटेशन की संख्या के ग्राफ़ के तौर पर बनाया गया है. सामान्य नतीजा यह है कि गड़बड़ी की जांच करने की परफ़ॉर्मेंस, ऑब्ज़र्व किए गए ऑब्जेक्ट की संख्या के एल्गोरिदम के अनुपात में होती है. वहीं, O.o() की परफ़ॉर्मेंस, किए गए म्यूटेशन की संख्या के अनुपात में होती है.

गंदे जांच करना

गड़बड़ी की जांच की जा रही है

ऑब्जेक्ट.observe() वाला Chrome चालू है

परफ़ॉर्मेंस पर नज़र रखना

पॉलीफ़िलिंग Object.observe()

बहुत बढ़िया - तो O.o() का इस्तेमाल Chrome 36 में किया जा सकता है, लेकिन दूसरे ब्राउज़र में इसका इस्तेमाल करने पर क्या होगा? हम आपको पूरी जानकारी देंगे. Polymer का Observe-JS, O.o() के लिए एक पॉलीफ़िल है. यह मौजूद होने पर, नेटिव इंप्लिमेंटेशन का इस्तेमाल करता है. हालांकि, इसे पॉलीफ़िल के तौर पर इस्तेमाल किया जाता है. साथ ही, इसके ऊपर कुछ उपयोगी शुगरिंग शामिल होती है. यह दुनिया का पूरा व्यू दिखाता है, जिसमें सभी बदलावों को शामिल किया जाता है. साथ ही, यह एक रिपोर्ट भी देता है जिसमें यह बताया जाता है कि क्या बदलाव हुए हैं. इस रिपोर्ट से दो अहम बातें पता चलती हैं:

  1. पाथ देखे जा सकते हैं. इसका मतलब है कि मुझे किसी दिए गए ऑब्जेक्ट से "foo.bar.baz" को देखना है और उस पाथ की वैल्यू बदलने पर, वे आपको बताएंगे. अगर पाथ तक नहीं पहुंचा जा सकता, तो वैल्यू को 'तय नहीं है' माना जाता है.

किसी दिए गए ऑब्जेक्ट के पाथ पर वैल्यू देखने का उदाहरण:

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.
});
  1. इससे आपको अरे स्प्लिस के बारे में जानकारी मिलेगी. अरे स्प्लिस, मूल रूप से स्प्लिस ऑपरेशन का कम से कम सेट है. आपको किसी अरे के पुराने वर्शन को अरे के नए वर्शन में बदलने के लिए, किसी अरे पर कार्रवाई करनी होगी. यह अरे का एक तरह का ट्रांसफ़ॉर्म या अलग व्यू है. यह पुरानी स्थिति से नई स्थिति में जाने के लिए ज़रूरी कम से कम काम है.

किसी ऐरे में हुए बदलावों को स्प्लिस के कम से कम सेट के तौर पर रिपोर्ट करने का उदाहरण:

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.
  });
});

फ़्रेमवर्क और Object.observe()

जैसा कि बताया गया है, O.o() फ़ंक्शन की मदद से फ़्रेमवर्क और लाइब्रेरी, इस सुविधा के साथ काम करने वाले ब्राउज़र में अपनी डेटा-बाइंडिंग की परफ़ॉर्मेंस को बेहतर बना सकती हैं.

एंबर के येहुदा काट्ज़ और एरिक ब्रिन ने पुष्टि की है कि Ember के आने वाले समय में O.o() को इस्तेमाल करने का फ़ैसला लिया जा सकता है. Angular के Misko Hervy ने Angular 2.0 में बदलाव का पता लगाने की बेहतर सुविधा के बारे में डिज़ाइन दस्तावेज़ लिखा है. कंपनी लंबे समय के लिए यह तरीका अपनाना होगा कि Object.observe() का इस्तेमाल किया जाएगा. ऐसा तब होगा, जब यह Chrome के स्टेबल वर्शन में काम करेगा. साथ ही, Watchtower.js को ही अपनाएंगे. यह तब तक बदलाव का पता लगाने के अपने तरीके का इस्तेमाल करेगा. शानदार.

मीटिंग में सामने आए नतीजे

O.o() वेब प्लैटफ़ॉर्म में एक बेहतरीन सुविधा है. इसका इस्तेमाल आज ही किया जा सकता है.

हमें उम्मीद है कि समय के साथ यह सुविधा ज़्यादा ब्राउज़र में उपलब्ध होगी. इससे JavaScript फ़्रेमवर्क को नेटिव ऑब्जेक्ट की निगरानी करने की सुविधा ऐक्सेस करने से, परफ़ॉर्मेंस बेहतर करने में मदद मिलेगी. Chrome को टारगेट करने वाले लोग, Chrome 36 (और इसके बाद के वर्शन) में O.o() का इस्तेमाल कर सकते हैं. साथ ही, यह सुविधा Opera के आने वाले वर्शन में भी उपलब्ध होगी.

इसलिए, Object.observe() के बारे में JavaScript फ़्रेमवर्क के लेखकों से बात करें और जानें कि वे आपके ऐप्लिकेशन में डेटा-बाइंडिंग की परफ़ॉर्मेंस को बेहतर बनाने के लिए, इसका इस्तेमाल कैसे करने वाले हैं. आगे का समय ज़रूर रोमांचक होगा!

संसाधन

अपने सुझाव और राय देने के लिए, राफ़ेल वाइंस्टीन, जैक आर्किबाल्ड, एरिक बिडलमैन, पॉल किलन, और विवियन क्रॉमवेल का धन्यवाद.