preface

While maintaining the project at work, I encountered a setItem rewrite problem as shown in the following code (simplified) :

/** * main implementation of setItem rewrite, implementation of localStorage data changes monitor */
 var signSetItem  = localStorage.setItem;
 localStorage.setItem = function (key, newValue) {
   var setItemEvent = new Event("setItemEvent");
   setItemEvent.key = key;
   var oldValue = localStorage.getItem(key);
   if(! isEqual(oldValue, newValue)) {// Deep judgment of old and new values, send listening events
     setItemEvent.newValue = newValue;
     setItemEvent.oldValue = oldValue;
     window.dispatchEvent(setItemEvent);
     signSetItem.apply(this.arguments); }};// Listen globally for the localStorage setItemEvent event
window.addEventListener('setItemEvent'.function (e) {
  console.log(e.oldValue, e.newValue, e.key )
})
Copy the code

But the above approach runs into a problem with Firefox, which is that the listener doesn’t work!

Why does listening fail?

Compare firefox with Google’s Storage object and you’ll see. The practice is as follows:

  1. Prerequisite: Uniform rewritelocalStorage.setItem = 2(Enter at the command line)
  2. Observe the difference:
differences chrome Firefox
Object Storage
The local store

It is not difficult to see that the essence of Google Browser rewriting setItem is to add instance attributes to Storage. When we call setItem, the setItem attribute in the instance will be searched first, while the prototype attribute setItem has not changed.

Firefox, however, doesn’t see it that way. It screens out setItem and doesn’t save it in the instance property, just stores it as a local value.

The author believes that this is because different browsers have different get and set mechanisms for Storage objects.

Method to rewrite Storage with Object.defineProperty

As mentioned above, setItem cannot be overwritten by the usual assignment method, so Object. DefineProperty is used here.

Note that Storage’s methods are overridden, otherwise calling its object prototype methods will return an error.

 // javascript,ES5 
 var localStorageMock = (function(win) {
    var storage = win.localStorage; // Use closures to implement the local object storage
    return {
        setItem: function(key, value) {
          var setItemEvent = new Event("setItemEvent");
          var oldValue = storage[key];
          setItemEvent.key = key;
          if(! isEqual(oldValue, value)) {// Deep judgment of old and new values, send listening events
              setItemEvent.newValue = value;
              setItemEvent.oldValue = oldValue;
              win.dispatchEvent(setItemEvent);
              storage[key] = value;
              return true;
          }
          return false;
        },
        getItem: function(key) {
          return storage[key];
        },
        removeItem: function(key) {
          storage[key] = null;
          return true;
        },
        clear: function() {
            storage.clear();
            return true; },key: function (index) {
            return storage.key(index);
        }
    };
}(window));

Object.defineProperty(window.'localStorage', { value: localStorageMock, writable: true });
Copy the code

Advanced! Object defineProperty and Proxy to rewrite Storage methods

Why do different browsers have different get and set mechanisms for Storage objects?

This is also my Proxy Proxy to rewrite the Storage set and get method to understand, interested comrades can also rewrite the following code, together with the message discussion and progress.

Here, too, the authors recommend the proxy approach.

The proxy is more like middleware, and you don’t need to change the way it works, you just need to add a sentinel to watch what it does.

  // typescript
  interface SEventType {
      newValue: any;
      oldValue: any;
  }

  const setItem = function (key: string, value: any) {
    return Reflect.set(storageProxy, key, value);
  };
  const getItem = function (key: string) {
    return Reflect.get(storageProxy, key);
  };
  const removeItem = function (key: string) {
    return storageProxy.removeItem(key);
  };
  const key = function (key: number) {
    return storageProxy.key(key);
  };
  const clear = function () {
    return storageProxy.clear();
  };
  const storageProxy = new Proxy(window.localStorage, {
    set: function (ls, key, newValue) {
      if (typeof key === "string") {
        var oldValue = storage[key];
        if(! isEqual(oldValue, newValue)) {// The new value is updated
          var setItemEvent: CustomEvent<SEventType> = new CustomEvent(
            "setItemEvent",
            {
              detail: {
                newValue,
                oldValue,
              },
            }
          );
          window.dispatchEvent(setItemEvent);
          return Reflect.set(ls, key, newValue); }}return false;
    },
    get: function (ls, prop) {
      // An error occurs when executing localstorage.setItem
      // Reflect. Get (ls, prop); You can't do
      // So each method is individually rewritten
      if (typeof prop === "string" && prop === "setItem") {
        return setItem;
      } else if (prop === "getItem") {
        return getItem;
      } else if (prop === "key") {
        return key;
      } else if (prop === "clear") {
        return clear;
      } else if (prop === "removeItem") {
        return removeItem;
      }
      return Reflect.get(ls, prop); }});Object.defineProperty(window."localStorage", {
    configurable: true.enumerable: true.value: storageProxy,
  });
Copy the code