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

एडी ओस्मान
एडी उस्मान

शुरुआती जानकारी

एक क्रांति आने वाली है. 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() का फ़ायदा लिया जा सकता है.

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

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

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

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

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

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

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

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

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

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

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

गंदगी की जांच

आपने पहले डेटा-बाइंडिंग कहां देखी है? खैर, अगर आपने वेब ऐप्लिकेशन (जैसे कि एंगुलर, नॉकआउट) बनाने के लिए मॉडर्न MV* लाइब्रेरी का इस्तेमाल किया है, तो हो सकता है कि आपको मॉडल डेटा को डीओएम से बाइंड करने के लिए इस्तेमाल किया जाए. रीफ़्रेशर के तौर पर, यहां फ़ोन लिस्ट ऐप्लिकेशन का एक उदाहरण दिया गया है. इसमें हम 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.'}
  ];
});

जब भी बुनियादी मॉडल के डेटा में बदलाव होता है, तो डीओएम में हमारी सूची अपडेट हो जाती है. Angular इसे कैसे हासिल करता है? दरअसल, यह गंदे चेकिंग जैसा कुछ कर रहा है.

गंदगी की जांच

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

गंदे चेकिंग.

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

वेब नेटवर्क के पास अपनी खुद की डिक्लेरेटिव टोन को बनाने और उसे बेहतर बनाने की क्षमता होनी चाहिए. जैसे,

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

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

चलिए, हमारे 'काम की सूची' मॉडल ऑब्जेक्ट में कुछ बदलाव करते हैं:

todoModel.label = 'Buy some more milk';

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

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

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

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

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

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

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

delete todoModel.completed;
पिछले गेम

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

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

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

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

"परफ़ॉर्मेंस फ़ंक्शन" के अंदर की हर चीज़ को "बड़ा-बदलाव" माना जाता है. "बड़े बदलाव" को स्वीकार करने वाले ऑब्ज़र्वर को सिर्फ़ "बड़ा बदलाव" रिकॉर्ड ही मिलेगा. जिन लोगों को “फ़ंक्शन परफ़ॉर्म” करने वाले काम के नतीजे नहीं मिलते हैं उन्हें इन बदलावों की जानकारी नहीं मिलेगी.

कैटगरी पर नज़र रखना

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

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() को पॉलीफ़िल करना

बहुत बढ़िया - तो Chrome 36 में O.o() का इस्तेमाल किया जा सकता है, लेकिन अन्य ब्राउज़र में इसे इस्तेमाल करने के बारे में क्या ख्याल है? हम आपको पूरी जानकारी देंगे. Polymer का Overview-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() की मदद से फ़्रेमवर्क और लाइब्रेरी को उन ब्राउज़र में डेटा-बाइंडिंग की परफ़ॉर्मेंस को बेहतर बनाने का बड़ा मौका मिलेगा जो इस सुविधा के साथ काम करते हैं.

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

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

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

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

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

रिसॉर्स

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