Skip to content

Instantly share code, notes, and snippets.

@mailmindlin
Last active August 25, 2017 16:45
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mailmindlin/640e9d707ae3bd666d70 to your computer and use it in GitHub Desktop.
Save mailmindlin/640e9d707ae3bd666d70 to your computer and use it in GitHub Desktop.
Proxy Polyfill for major webbrowsers
/**
* Partial polyfill for Proxy. For details, see https://gist.github.com/mailmindlin/640e9d707ae3bd666d70
*/
function Proxy (target, handler, revocable) {
var self = this;//because life
//override Object.prototype properties
Object.defineProperty(this, '__lookupGetter__', {value: target.__lookupSetter__.bind(target)});
Object.defineProperty(this, '__lookupSetter__', {value: target.__lookupSetter__.bind(target)});
Object.defineProperty(this, '__defineGetter__', {value: target.__defineGetter__.bind(target)});
Object.defineProperty(this, '__defineSetter__', {value: target.__defineSetter__.bind(target)});
Object.defineProperty(this, 'toString', {value: target.toString.bind(target)});
Object.defineProperty(this, 'valueOf', {value: target.valueOf.bind(target)});
Object.defineProperty(this, '__proto__', {get: function(){
if ('getPrototypeOf' in handler)
return handler.getPrototypeOf(target);
else
return target.__proto__;
},set:function(val){
if('setPrototypeOf' in handler)
handler.setPrototypeOf(target, val);
else
target.__proto__ = val;
}
});//let's see if this is a bad idea
//for handler.apply
Object.defineProperty(this, 'apply',{value:function(){
if('apply' in handler)
return handler.apply.apply(handler, target, this==self?handler:this, arguments);
else
return target.apply.apply(this==self?target:this, arguments);
}});
//for handler.has (not a full implementation, but as good as I could do)
Object.defineProperty(this, 'hasOwnProperty', {value: function (property) {
if ('has' in handler) {
return handler.has(target, property);
} else {
return target.hasOwnProperty(property);
}
}});
//default getters and setters, to either update the target object or to call the handler's function
var get, set;
if ('get' in handler)
get = function (property) {
return handler.get(target, property, this);
};
else
get = function (property) {
return target[property];
};
if ('set' in handler)
set = function (property, value) {
handler.set(target, property, value, this);
};
else
set = function (property, value) {
targetObserver.doIgnore(property,'update');
target[property]=value;
};
//Functions to be used with Object.observe
var selfObserver = (function (changes) {
changes.forEach((function(change) {
if ((selfObserver.ignore[change.name]||[]).includes(change.type)) {
delete selfObserver.ignore[change.name];
return;
}
if (change.type == 'add') {
if ('defineProperty' in handler) {
handler.definePropery(target, change.name, Object.getOwnPropertyDescriptor(target, key));
} else {
targetObserver.doIgnore(change.name, 'add', 'reconfigure', 'update');
Object.defineProperty(target, change.name, Object.getOwnPropertyDescriptor(this, change.name));
bind(change.name);//update getters/setters
}
} else if (change.type == 'delete') {
if ('deleteProperty' in handler) {
targetObserver.doIgnore(change.name, 'delete');
if(!handler.deleteProperty(target, change.name)) {
targetObserver.dontIgnore(change.name, 'delete');
bind(change.name);
}
} else {
targetObserver.doIgnore(change.name, 'delete');
if(!(delete target[change.name])) {
targetObserver.dontIgnore(change.name, 'delete');//it wasn't deleted successfully
bind(change.name);
}
}
}
}).bind(this));
}).bind(this);
selfObserver.ignore = {};
selfObserver.doIgnore=function(){selfObserver.ignore[arguments[0]]=Array.prototype.slice.call(arguments,1)};
selfObserver.dontIgnore=function(){
var name = arguments[0];
if(!(name in selfObserver.ignore))
return;
var events=Array.prototype.slice(arguments,1);
for(var i in events)
if(selfObserver.ignore[name].includes(events[i]))
selfObserver.ignore[name].splice(selfObserver.ignore[name].indexOf(events[i]),1);
if (selfObserver.ignore[name].length==0)
delete selfObserver.ignore[name];
};
var targetObserver = (function (changes) {
changes.forEach(function(change) {
if ((targetObserver.ignore[change.name]||[]).includes(change.type)) {
delete targetObserver.ignore[change.name];
return;
}
if (change.type == 'add' || change.type == 'reconfigure' || change.type == 'update') {
bind(change.name);
} else if (change.type == 'delete') {
selfObserver.doIgnore(change.name, 'delete');
if(!(delete this[change.name]))
selfObserver.dontIgnore(change.name, 'delete');//TODO check why
}
});
targetObserver.ignore={};
}).bind(this);
targetObserver.ignore = {};
targetObserver.doIgnore=function(){targetObserver.ignore[arguments[0]]=Array.prototype.slice.call(arguments,1)};
targetObserver.dontIgnore=function(){
var name = arguments[0];
if(!(name in targetObserver.ignore))
return;
var events=Array.prototype.slice(arguments,1);
for(var i in events)
if(targetObserver.ignore[name].includes(events[i]))
targetObserver.ignore[name].splice(targetObserver.ignore[name].indexOf(events[i]),1);
if (targetObserver.ignore[name].length==0)
delete targetObserver.ignore[name];
};
//function to bind the getter and setter to the given object
var bind = (function(key) {
var descriptor = Object.getOwnPropertyDescriptor(target, key);
//Make sure that these updates won't be sent to the handler
selfObserver.doIgnore(key,'add','reconfigure');
//Set the property
Object.defineProperty(this, key, {
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
get: get.bind(this, key),
set: set.bind(this, key)
});
}).bind(this);
//bind to each existing property
for (var key in target)
bind(key);
//Use Object.observe if supported, for more functionality (mirror properties as they are added, delete operator).
//As of Sept. 2015, only Chrome 36+ and Opera 23+ have Object.observe supported, so if you are using IE or Safari,
//this won't help you (if you're using Firefox, Proxy is built in, so you don't have a problem).
if ('observe' in Object) {
Object.observe(this, selfObserver);
Object.observe(target, targetObserver);
} else {
selfObserver = undefined;
targetObserver = undefined;
}
//Add support for Proxy.revocable (kinda)
if (revocable == true)
this.revoke = (function() {
if('unobserve' in Object) {
Object.unobserve(this, selfObserver);
Object.unobserve(target, targetObserver);
}
selfObserver = undefined;
targetObserver = undefined;
bind = undefined;
get = undefined;
set = undefined;
for(var i in this)
delete this[i];
}).bind(this);
return this;
};
Proxy.prototype = Function.prototype;
Proxy.revocable = function(target, handler) {return new Proxy(target, handler, true)};
@mailmindlin
Copy link
Author

Feature Support
Proxy.revocable check image Slightly off
handler.apply() check image
handler.construct() cross image
handler.defineProperty() check image
handler.deleteProperty() check image
handler.enumerate() cross image
handler.get() check image
handler.getOwnPropertyDescriptor() cross image (May be possible if you overrode Object.getOwnPropertyDescriptor, I didn't want to mess with that)
handler.getPrototypeOf() Partial (via __proto__)
handler.has() check image
handler.isExtensible() cross image (May be possible in the future)
handler.ownKeys() cross image
handler.set() check image
handler.setPrototypeOf() Partial (via __proto__)
Property reflection check image
Property deletion check image
Property getter reflection check image
Property setter reflection check image
Object.prototype.__lookupGetter__ check image
Object.prototype.__lookupSetter__ check image
Object.prototype.__defineGetter__ check image
Object.prototype.__defineSetter__ check image
Object.prototype.toString check image
Object.prototype.valueOf check image

Browser compatibility (from jscc.info):

Browser Minimum Supported Version
Chrome 7
Firefox 4
IE/Edge 9
iOS Safari 3.2
Opera 12
Opera Mini 5
Safari 5

@philipp-schaerer-lambdait
Copy link

Could it be that there's a typo at line 102, shouldn't it be Array.prototype.slice.call?
Also at line 7, shouldn't it reference __lookupGetter__ and not __lookupSetter__

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment