सरणी परिवर्तन के लिए कैसे देखें?


106

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

मैं watch()स्पाइडरमोंकी में कार्यक्षमता के बारे में जानता हूं, लेकिन यह तभी काम करता है जब पूरा चर कुछ और पर सेट हो।

जवाबों:


170

कुछ विकल्प हैं ...

1. पुश विधि को ओवरराइड करें

त्वरित और गंदे मार्ग पर जाकर, आप push()अपने सरणी 1 के लिए विधि को ओवरराइड कर सकते हैं :

Object.defineProperty(myArray, "push", {
  enumerable: false, // hide from for...in
  configurable: false, // prevent further meddling...
  writable: false, // see above ^
  value: function () {
    for (var i = 0, n = this.length, l = arguments.length; i < l; i++, n++) {          
      RaiseMyEvent(this, n, this[n] = arguments[i]); // assign/raise your event
    }
    return n;
  }
});

1 वैकल्पिक रूप से, यदि आप सभी सरणियों को लक्षित करना चाहते हैं, तो आप ओवरराइड कर सकते हैं Array.prototype.push()। यद्यपि सावधानी बरतें; आपके वातावरण में अन्य कोड उस तरह के संशोधन को पसंद या अपेक्षा नहीं कर सकते हैं। फिर भी, यदि कोई आकर्षक-आकर्षक लग रहा है, तो बस के myArrayसाथ बदलें Array.prototype

अब, यह सिर्फ एक तरीका है और सरणी सामग्री को बदलने के बहुत सारे तरीके हैं। हम शायद कुछ अधिक व्यापक की जरूरत है ...

2. एक कस्टम अवलोकन सारणी बनाएं

ओवरराइड करने के तरीकों के बजाय, आप अपने स्वयं के अवलोकन योग्य सरणी बना सकते हैं। यह विशेष रूप से कार्यान्वयन प्रतियों में एक सरणी एक नई वस्तु सरणी की तरह और कस्टम प्रदान करता है push(), pop(), shift(), unshift(), slice(), और splice()तरीकों के साथ-साथ कस्टम सूचकांक accessors (बशर्ते कि सरणी आकार केवल ऊपर उल्लिखित तरीकों या में से एक के माध्यम से संशोधित किया गया है lengthसंपत्ति)।

function ObservableArray(items) {
  var _self = this,
    _array = [],
    _handlers = {
      itemadded: [],
      itemremoved: [],
      itemset: []
    };

  function defineIndexProperty(index) {
    if (!(index in _self)) {
      Object.defineProperty(_self, index, {
        configurable: true,
        enumerable: true,
        get: function() {
          return _array[index];
        },
        set: function(v) {
          _array[index] = v;
          raiseEvent({
            type: "itemset",
            index: index,
            item: v
          });
        }
      });
    }
  }

  function raiseEvent(event) {
    _handlers[event.type].forEach(function(h) {
      h.call(_self, event);
    });
  }

  Object.defineProperty(_self, "addEventListener", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function(eventName, handler) {
      eventName = ("" + eventName).toLowerCase();
      if (!(eventName in _handlers)) throw new Error("Invalid event name.");
      if (typeof handler !== "function") throw new Error("Invalid handler.");
      _handlers[eventName].push(handler);
    }
  });

  Object.defineProperty(_self, "removeEventListener", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function(eventName, handler) {
      eventName = ("" + eventName).toLowerCase();
      if (!(eventName in _handlers)) throw new Error("Invalid event name.");
      if (typeof handler !== "function") throw new Error("Invalid handler.");
      var h = _handlers[eventName];
      var ln = h.length;
      while (--ln >= 0) {
        if (h[ln] === handler) {
          h.splice(ln, 1);
        }
      }
    }
  });

  Object.defineProperty(_self, "push", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function() {
      var index;
      for (var i = 0, ln = arguments.length; i < ln; i++) {
        index = _array.length;
        _array.push(arguments[i]);
        defineIndexProperty(index);
        raiseEvent({
          type: "itemadded",
          index: index,
          item: arguments[i]
        });
      }
      return _array.length;
    }
  });

  Object.defineProperty(_self, "pop", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function() {
      if (_array.length > -1) {
        var index = _array.length - 1,
          item = _array.pop();
        delete _self[index];
        raiseEvent({
          type: "itemremoved",
          index: index,
          item: item
        });
        return item;
      }
    }
  });

  Object.defineProperty(_self, "unshift", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function() {
      for (var i = 0, ln = arguments.length; i < ln; i++) {
        _array.splice(i, 0, arguments[i]);
        defineIndexProperty(_array.length - 1);
        raiseEvent({
          type: "itemadded",
          index: i,
          item: arguments[i]
        });
      }
      for (; i < _array.length; i++) {
        raiseEvent({
          type: "itemset",
          index: i,
          item: _array[i]
        });
      }
      return _array.length;
    }
  });

  Object.defineProperty(_self, "shift", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function() {
      if (_array.length > -1) {
        var item = _array.shift();
        delete _self[_array.length];
        raiseEvent({
          type: "itemremoved",
          index: 0,
          item: item
        });
        return item;
      }
    }
  });

  Object.defineProperty(_self, "splice", {
    configurable: false,
    enumerable: false,
    writable: false,
    value: function(index, howMany /*, element1, element2, ... */ ) {
      var removed = [],
          item,
          pos;

      index = index == null ? 0 : index < 0 ? _array.length + index : index;

      howMany = howMany == null ? _array.length - index : howMany > 0 ? howMany : 0;

      while (howMany--) {
        item = _array.splice(index, 1)[0];
        removed.push(item);
        delete _self[_array.length];
        raiseEvent({
          type: "itemremoved",
          index: index + removed.length - 1,
          item: item
        });
      }

      for (var i = 2, ln = arguments.length; i < ln; i++) {
        _array.splice(index, 0, arguments[i]);
        defineIndexProperty(_array.length - 1);
        raiseEvent({
          type: "itemadded",
          index: index,
          item: arguments[i]
        });
        index++;
      }

      return removed;
    }
  });

  Object.defineProperty(_self, "length", {
    configurable: false,
    enumerable: false,
    get: function() {
      return _array.length;
    },
    set: function(value) {
      var n = Number(value);
      var length = _array.length;
      if (n % 1 === 0 && n >= 0) {        
        if (n < length) {
          _self.splice(n);
        } else if (n > length) {
          _self.push.apply(_self, new Array(n - length));
        }
      } else {
        throw new RangeError("Invalid array length");
      }
      _array.length = n;
      return value;
    }
  });

  Object.getOwnPropertyNames(Array.prototype).forEach(function(name) {
    if (!(name in _self)) {
      Object.defineProperty(_self, name, {
        configurable: false,
        enumerable: false,
        writable: false,
        value: Array.prototype[name]
      });
    }
  });

  if (items instanceof Array) {
    _self.push.apply(_self, items);
  }
}

(function testing() {

  var x = new ObservableArray(["a", "b", "c", "d"]);

  console.log("original array: %o", x.slice());

  x.addEventListener("itemadded", function(e) {
    console.log("Added %o at index %d.", e.item, e.index);
  });

  x.addEventListener("itemset", function(e) {
    console.log("Set index %d to %o.", e.index, e.item);
  });

  x.addEventListener("itemremoved", function(e) {
    console.log("Removed %o at index %d.", e.item, e.index);
  });
 
  console.log("popping and unshifting...");
  x.unshift(x.pop());

  console.log("updated array: %o", x.slice());

  console.log("reversing array...");
  console.log("updated array: %o", x.reverse().slice());

  console.log("splicing...");
  x.splice(1, 2, "x");
  console.log("setting index 2...");
  x[2] = "foo";

  console.log("setting length to 10...");
  x.length = 10;
  console.log("updated array: %o", x.slice());

  console.log("setting length to 2...");
  x.length = 2;

  console.log("extracting first element via shift()");
  x.shift();

  console.log("updated array: %o", x.slice());

})();

संदर्भ के लिए देखें ।Object.defineProperty()

यह हमें करीब लाता है, लेकिन यह अभी भी बुलेट प्रूफ नहीं है ... जो हमें यहां लाता है:

3. समीपता

प्रॉक्सी एक और समाधान प्रदान करते हैं ... आपको विधि कॉल, एक्सेसर्स इत्यादि को इंटरसेप्ट करने की अनुमति देता है। सबसे महत्वपूर्ण बात, आप ऐसा बिना किसी स्पष्ट संपत्ति के नाम के भी कर सकते हैं ... जो आपको एक अनियंत्रित, सूचकांक-आधारित पहुंच / परीक्षण के लिए अनुमति देगा। असाइनमेंट। आप संपत्ति हटाने को भी रोक सकते हैं। इस तथ्य के बाद परिवर्तन को संभालने के अलावा, निर्णय लेने से पहले प्रॉक्सी आपको प्रभावी ढंग से बदलाव का निरीक्षण करने की अनुमति देगा।

यहाँ एक नमूना नीचे छीन लिया गया है:

(function() {

  if (!("Proxy" in window)) {
    console.warn("Your browser doesn't support Proxies.");
    return;
  }

  // our backing array
  var array = ["a", "b", "c", "d"];

  // a proxy for our array
  var proxy = new Proxy(array, {
    apply: function(target, thisArg, argumentsList) {
      return thisArg[target].apply(this, argumentList);
    },
    deleteProperty: function(target, property) {
      console.log("Deleted %s", property);
      return true;
    },
    set: function(target, property, value, receiver) {      
      target[property] = value;
      console.log("Set %s to %o", property, value);
      return true;
    }
  });

  console.log("Set a specific index..");
  proxy[0] = "x";

  console.log("Add via push()...");
  proxy.push("z");

  console.log("Add/remove via splice()...");
  proxy.splice(1, 3, "y");

  console.log("Current state of array: %o", array);

})();


धन्यवाद! यह नियमित सरणी विधियों के लिए काम करता है। कैसे की तरह कुछ के लिए एक घटना को बढ़ाने के लिए पर कोई भी विचार "आगमन [2] =" foo "?
Sridatta Thatipamala

4
मुझे लगता है कि आप set(index)ऐरे के प्रोटोटाइप में एक विधि को लागू कर सकते हैं और कुछ ऐसा कर सकते हैं जैसे कि असामनता कहती है
पाब्लो फर्नांडीज

8
एरे को उप-करना बेहतर होगा। यह आमतौर पर ऐरे के प्रोटोटाइप को संशोधित करने के लिए एक अच्छा विचार नहीं है।
वेन

1
यहाँ बकाया जवाब। ऑब्जर्वेबलएरे की कक्षा उत्कृष्ट है। +1
डोबर्ट

1
"_array.length === 0 && हटाएं _ स्वयं [सूचकांक];" - क्या आप इस लाइन की व्याख्या कर सकते हैं?
छप

23

यहां सभी उत्तरों को पढ़ने से, मैंने एक सरलीकृत समाधान इकट्ठा किया है जिसमें किसी भी बाहरी पुस्तकालयों की आवश्यकता नहीं है।

यह दृष्टिकोण के लिए सामान्य विचार को बहुत बेहतर दिखाता है:

function processQ() {
   // ... this will be called on each .push
}

var myEventsQ = [];
myEventsQ.push = function() { Array.prototype.push.apply(this, arguments);  processQ();};

यह अच्छा विचार है, लेकिन क्या आपको नहीं लगता कि अगर मैं उदाहरण के लिए चार्ट js डेटा सरणियों में इसे लागू करना चाहता हूं, और मेरे पास 50 चार्ट हैं, जिसका मतलब है कि 50 सरणियां और प्रत्येक सरणी को हर सेकंड अपडेट किया जाएगा -> आकार के बारे में कल्पना करें दिन के अंत में 'myEventsQ' सरणी! मुझे लगता है कि जब इसे हर हाल में शिफ्ट करने की जरूरत है
याह्या

2
आप समाधान नहीं समझते हैं। myEventsQ सरणी है (आपके 50 सरणियों में से एक)। यह स्निपेट सरणी के आकार को नहीं बदलता है, और किसी भी अतिरिक्त सरणियों को नहीं जोड़ता है, यह केवल मौजूदा लोगों के प्रोटोटाइप को बदलता है।
साइक

1
mmmm मैं देख रहा हूं, हालांकि अधिक स्पष्टीकरण प्रदान किया जाना चाहिए था!
याह्या

3
pushlengthसरणी का रिटर्न । तो, आप Array.prototype.push.applyएक चर द्वारा लौटाया गया मान प्राप्त कर सकते हैं और इसे कस्टम pushफ़ंक्शन से वापस कर सकते हैं ।
adiga

12

मैंने निम्नलिखित पाया जो इसे पूरा करने के लिए लगता है: https://github.com/mennovanslooten/Observable-Arays

अवलोकनीय-सारणी अंडरस्कोर निकालती है और इसका उपयोग निम्नानुसार किया जा सकता है: (उस पृष्ठ से)

// For example, take any array:
var a = ['zero', 'one', 'two', 'trhee'];

// Add a generic observer function to that array:
_.observe(a, function() {
    alert('something happened');
});

13
यह बहुत अच्छा है, लेकिन एक महत्वपूर्ण चेतावनी है: जब किसी सरणी को संशोधित किया जाता है arr[2] = "foo", तो परिवर्तन अधिसूचना अतुल्यकालिक होती है । चूंकि JS ऐसे परिवर्तनों को देखने के लिए कोई रास्ता नहीं प्रदान करता है, इसलिए यह लाइब्रेरी एक टाइमआउट पर निर्भर करती है जो प्रत्येक 250 ms पर चलती है और यह देखने के लिए जांचती है कि क्या सरणी बिल्कुल बदल गई है - इसलिए आपको अगले तक बदलाव की सूचना नहीं मिलेगी टाइमआउट चलता है। अन्य परिवर्तन जैसे push()कि तुरंत अधिसूचित हो जाते हैं (समकालिक रूप से), हालाँकि।
पेट्रिग्नन

6
इसके अलावा, मुझे लगता है कि 250 अंतराल आपकी साइट के प्रदर्शन को प्रभावित करेगा यदि सरणी बड़ी है।
टॉम ज़ातो - मोनिका को बहाल करें

बस इसी का इस्तेमाल किया, एक आकर्षण की तरह काम करता है। हमारे नोड-आधारित दोस्तों के लिए मैंने एक वादा के साथ इस भस्म का इस्तेमाल किया। (टिप्पणियों में प्रारूप एक दर्द है ...) _ = आवश्यकता ('लॉश'); आवश्यकता है ("अंडरस्कोर-अवलोकन") ( ); वादा = आवश्यकता ("ब्लूबर्ड"); नया वादा (फ़ंक्शन (रिज़ॉल्यूशन, रिजेक्ट)) {वापसी _.oberve (कतार, 'डिलीट', फंक्शन () {if ( .isEmpty (कतार)) {रिटर्न सॉल्यूशन (एक्शन);}}};);
लेइफ

6

किसी सरणी में परिवर्तन सुनने के लिए मैंने निम्न कोड का उपयोग किया।

/* @arr array you want to listen to
   @callback function that will be called on any change inside array
 */
function listenChangesinArray(arr,callback){
     // Add more methods here if you want to listen to them
    ['pop','push','reverse','shift','unshift','splice','sort'].forEach((m)=>{
        arr[m] = function(){
                     var res = Array.prototype[m].apply(arr, arguments);  // call normal behaviour
                     callback.apply(arr, arguments);  // finally call the callback supplied
                     return res;
                 }
    });
}

आशा है कि यह उपयोगी था :)


5

@Canon द्वारा सबसे ज्यादा उखाड़ी गई ओवरराइड पुश मेथड सॉल्यूशन के कुछ दुष्परिणाम हैं जो मेरे मामले में असुविधाजनक थे:

  • यह पुश प्रॉपर्टी डिस्क्रिप्टर को अलग बनाता है ( writableऔर इसके बजाय configurableसेट किया जाना चाहिए ), जो बाद के बिंदु में अपवाद का कारण बनता है।truefalse

  • यह कई बार घटना को उठाता है जब push()एक बार कई तर्कों (जैसे myArray.push("a", "b")) के साथ बुलाया जाता है , जो कि मेरे मामले में प्रदर्शन के लिए अनावश्यक और खराब था।

तो यह सबसे अच्छा समाधान है जो मुझे मिल सकता है जो पिछले मुद्दों को ठीक करता है और मेरी राय में क्लीनर / सरल / समझने में आसान है।

Object.defineProperty(myArray, "push", {
    configurable: true,
    enumerable: false,
    writable: true, // Previous values based on Object.getOwnPropertyDescriptor(Array.prototype, "push")
    value: function (...args)
    {
        let result = Array.prototype.push.apply(this, args); // Original push() implementation based on https://github.com/vuejs/vue/blob/f2b476d4f4f685d84b4957e6c805740597945cde/src/core/observer/array.js and https://github.com/vuejs/vue/blob/daed1e73557d57df244ad8d46c9afff7208c9a2d/src/core/util/lang.js

        RaiseMyEvent();

        return result; // Original push() implementation
    }
});

कृपया मेरे स्रोतों के लिए और धक्का के अलावा अन्य उत्परिवर्तन कार्यों को लागू करने के तरीके के लिए टिप्पणी देखें: 'पॉप', 'शिफ्ट', 'अनशिफ्ट', 'स्प्लिस', 'सॉर्ट', 'रिवर्स'।


@ मेरे पास उपलब्ध प्रॉक्सी हैं, लेकिन मैं उनका उपयोग नहीं कर सकता क्योंकि सरणी बाहरी रूप से संशोधित है, और मैं किसी भी तरीके से बाहरी कॉलर्स (जो समय-समय पर मेरे नियंत्रण के बिना परिवर्तन के अलावा) को बलपूर्वक प्रॉक्सी का उपयोग करने के लिए मजबूर नहीं कर सकता ।
cccrack

@ डकॉन और वैसे, आपकी टिप्पणी ने मुझे एक गलत धारणा बना दी, जो यह है कि मैं प्रसार ऑपरेटर का उपयोग कर रहा हूं, जब वास्तव में मैं नहीं हूं। तो नहीं, मैं प्रसार ऑपरेटर का लाभ नहीं उठा रहा हूँ। मैं जो उपयोग कर रहा हूं वह बाकी पैरामीटर है जिसमें समान ...सिंटैक्स है, और जिसे argumentsकीवर्ड के उपयोग के साथ आसानी से बदला जा सकता है ।
cccrack


0
if (!Array.prototype.forEach)
{
    Object.defineProperty(Array.prototype, 'forEach',
    {
        enumerable: false,
        value: function(callback)
        {
            for(var index = 0; index != this.length; index++) { callback(this[index], index, this); }
        }
    });
}

if(Object.observe)
{
    Object.defineProperty(Array.prototype, 'Observe',
    {
        set: function(callback)
        {
            Object.observe(this, function(changes)
            {
                changes.forEach(function(change)
                {
                    if(change.type == 'update') { callback(); }
                });
            });
        }
    });
}
else
{
    Object.defineProperties(Array.prototype,
    { 
        onchange: { enumerable: false, writable: true, value: function() { } },
        Observe:
        {
            set: function(callback)
            {
                Object.defineProperty(this, 'onchange', { enumerable: false, writable: true, value: callback }); 
            }
        }
    });

    var names = ['push', 'pop', 'reverse', 'shift', 'unshift'];
    names.forEach(function(name)
    {
        if(!(name in Array.prototype)) { return; }
        var pointer = Array.prototype[name];
        Array.prototype[name] = function()
        {
            pointer.apply(this, arguments); 
            this.onchange();
        }
    });
}

var a = [1, 2, 3];
a.Observe = function() { console.log("Array changed!"); };
a.push(8);

1
लगता है Object.observe()और Array.observe()कल्पना से वापस ले लिया गया। क्रोम से पहले ही समर्थन खींच लिया गया है। : /
कैनन

0

निश्चित नहीं है कि यह पूरी तरह से सब कुछ कवर करता है, लेकिन मैं कुछ इस तरह का उपयोग करता हूं (विशेषकर जब डीबगिंग) यह पता लगाने के लिए कि किसी सरणी में कोई तत्व जोड़ा गया है:

var array = [1,2,3,4];
array = new Proxy(array, {
    set: function(target, key, value) {
        if (Number.isInteger(Number(key)) || key === 'length') {
            debugger; //or other code
        }
        target[key] = value;
        return true;
    }
});

-1

एक दिलचस्प संग्रह पुस्तकालय https://github.com/mgesmundo/smart-collection है । आपको ऐरे देखने और उनके साथ ही विचार जोड़ने की अनुमति देता है। प्रदर्शन के बारे में निश्चित नहीं है क्योंकि मैं खुद इसका परीक्षण कर रहा हूं। जल्द ही इस पोस्ट को अपडेट करेंगे।


-1

मैं इधर-उधर फुदकने लगा। विचार यह है कि ऑब्जेक्ट में सभी Array.prototype विधियां परिभाषित हैं, लेकिन एक अलग सरणी ऑब्जेक्ट पर उन्हें निष्पादित करता है। यह शिफ्ट (), पॉप () आदि जैसे तरीकों का पालन करने की क्षमता देता है, हालांकि कुछ तरीकों जैसे कि कॉनैट () OArray ऑब्जेक्ट को वापस नहीं करेगा। यदि एक्सेसर्स का उपयोग किया जाता है तो उन तरीकों को ओवरलोड करने से ऑब्जेक्ट ऑब्जर्वेबल नहीं होगा। उत्तरार्द्ध को प्राप्त करने के लिए, दिए गए क्षमता के भीतर एक्सेसर्स को प्रत्येक सूचकांक के लिए परिभाषित किया गया है।

प्रदर्शन वार ... सादे सरणी वस्तु की तुलना में OArray लगभग 10-25 गुना धीमा है। 1 - 100 की सीमा में कैपेसिटी के लिए अंतर 1x-3x है।

class OArray {
    constructor(capacity, observer) {

        var Obj = {};
        var Ref = []; // reference object to hold values and apply array methods

        if (!observer) observer = function noop() {};

        var propertyDescriptors = Object.getOwnPropertyDescriptors(Array.prototype);

        Object.keys(propertyDescriptors).forEach(function(property) {
            // the property will be binded to Obj, but applied on Ref!

            var descriptor = propertyDescriptors[property];
            var attributes = {
                configurable: descriptor.configurable,
                enumerable: descriptor.enumerable,
                writable: descriptor.writable,
                value: function() {
                    observer.call({});
                    return descriptor.value.apply(Ref, arguments);
                }
            };
            // exception to length
            if (property === 'length') {
                delete attributes.value;
                delete attributes.writable;
                attributes.get = function() {
                    return Ref.length
                };
                attributes.set = function(length) {
                    Ref.length = length;
                };
            }

            Object.defineProperty(Obj, property, attributes);
        });

        var indexerProperties = {};
        for (var k = 0; k < capacity; k++) {

            indexerProperties[k] = {
                configurable: true,
                get: (function() {
                    var _i = k;
                    return function() {
                        return Ref[_i];
                    }
                })(),
                set: (function() {
                    var _i = k;
                    return function(value) {
                        Ref[_i] = value;
                        observer.call({});
                        return true;
                    }
                })()
            };
        }
        Object.defineProperties(Obj, indexerProperties);

        return Obj;
    }
}

जब यह मौजूदा तत्वों पर काम करता है, तो यह काम नहीं करता है जब एक तत्व सरणी [new_index] = मान के साथ जोड़ा जाता है। केवल प्रॉक्सी ही ऐसा कर सकते हैं।
mpm

-5

मैं आपको देशी प्रोटोटाइप का विस्तार करने की सलाह नहीं दूंगा। इसके बजाय, आप नई-सूची जैसी लाइब्रेरी का उपयोग कर सकते हैं; https://github.com/azer/new-list

यह एक देशी जावास्क्रिप्ट सरणी बनाता है और आपको किसी भी परिवर्तन की सदस्यता लेने देता है। यह अपडेट को बैच देता है और आपको अंतिम रूप प्रदान करता है;

List = require('new-list')
todo = List('Buy milk', 'Take shower')

todo.pop()
todo.push('Cook Dinner')
todo.splice(0, 1, 'Buy Milk And Bread')

todo.subscribe(function(update){ // or todo.subscribe.once

  update.add
  // => { 0: 'Buy Milk And Bread', 1: 'Cook Dinner' }

  update.remove
  // => [0, 1]

})
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.