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:
- Prerequisite: Uniform rewrite
localStorage.setItem = 2
(Enter at the command line) - 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