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

परिचय

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

ठीक है. ठीक है. बिना देरी किए, हमें यह बताते हुए खुशी हो रही है कि Object.observe(), Chrome 36 के स्टेबल वर्शन में आ गया है. [WOOOO. THE CROWD GOES WILD].

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() का फ़ायदा लेती हैं.

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

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

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

हमें क्या देखना है?

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

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

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

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

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

ब्राउज़र में डेटा को नेटिव तरीके से देखने का तरीका उपलब्ध कराने से, हम 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 ऐसा कैसे करता है? दरअसल, यह बैकग्राउंड में 'गड़बड़ी की जांच' नाम की एक प्रोसेस करता है.

डर्टी चेकिंग

गड़बड़ी की जांच करने का बुनियादी सिद्धांत यह है कि डेटा कभी भी बदल सकता है. इसलिए, लाइब्रेरी को यह देखना होगा कि डाइजेस्ट या बदलाव के साइकल की मदद से डेटा में बदलाव हुआ है या नहीं. 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';

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

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

वाह! अलविदा, गलत डेटा की जांच! आपके कब्र के पत्थर पर कॉमिक सैन्स में लिखा होना चाहिए. चलिए, किसी दूसरी प्रॉपर्टी में बदलाव करते हैं. इस बार 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() का एक और हिस्सा है. ज़्यादातर निगरानी सिस्टम, व्युत्पन्न की गई वैल्यू को निगरानी करने के लिए किसी न किसी तरीके का इस्तेमाल करते हैं. ऐसा करने के कई तरीके हैं. 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() की परफ़ॉर्मेंस, किए गए बदलावों की संख्या के हिसाब से होती है.

ग़लत डेटा की जांच करना

गलत डेटा की जांच करने की परफ़ॉर्मेंस

Object.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 के Yehuda Katz और Erik Bryn ने पुष्टि की है कि O.o() के लिए सहायता जोड़ना, Ember के निकट-समय के रोडमैप में है. Angular के Misko Hervy ने Angular 2.0 में बदलाव का पता लगाने की बेहतर सुविधा के बारे में डिज़ाइन दस्तावेज़ लिखा है. लंबे समय तक, वे Object.observe() का फ़ायदा तब तक लेते रहेंगे, जब तक यह Chrome के स्टेबल वर्शन में नहीं आ जाता. इसके बाद, वे बदलाव का पता लगाने के लिए, Watchtower.js का इस्तेमाल करेंगे. बहुत ही दिलचस्प.

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

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

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

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

संसाधन

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