Поскольку вы задавали академический вопрос, я полагаю, проблема совместимости браузера не является проблемой. Если это действительно не так, я бы хотел представить для этого прокси-серверы гармонии. onerror
не очень хорошая практика, так как это просто событие, возникающее, если где-то происходит ошибка. Следует, если вообще, использовать только в качестве крайней меры. (Я знаю, что вы сказали, что не используете его в любом случае, но onerror
просто не очень удобен для разработчиков.)
По сути, прокси-серверы позволяют вам перехватывать большинство основных операций в JavaScript - в первую очередь получить любое свойство, которое здесь полезно. В этом случае вы можете перехватить процесс получения .slice
.
Обратите внимание, что прокси по умолчанию являются "черными дырами". Они не соответствуют никаким объектам (например, установка свойства на прокси-сервере просто вызывает ловушку set
(перехватчик); фактическое хранение вы должны сделать самостоятельно). Но есть «обработчик пересылки», который направляет все через обычный объект (или, конечно, экземпляр), так что прокси-сервер ведет себя как обычный объект. Расширяя обработчик (в данном случае, часть get
), вы можете довольно легко перенаправить методы Array.prototype
следующим образом.
Таким образом, всякий раз, когда любое свойство (с именем name
) выбирается, путь к коду выглядит следующим образом:
- Попробуйте вернуть
inst[name]
.
- В противном случае попробуйте вернуть функцию, которая применяет
Array.prototype[name]
к экземпляру с заданными аргументами для этой функции.
- В противном случае просто верните
undefined
.
Если вы хотите поиграть с прокси, вы можете использовать последнюю версию V8, например, в ночной сборке Chromium (убедитесь, что она работает как chrome --js-flags="--harmony"
). Опять же, прокси не доступны для «обычного» использования, потому что они относительно новые, меняют многие фундаментальные части JavaScript и фактически еще не определены официально (все еще в черновиках).
Это простая диаграмма того, как это происходит (inst
на самом деле прокси, в который был обёрнут экземпляр). Обратите внимание, что это только иллюстрирует получение свойства; все остальные операции просто передаются через прокси из-за неизмененного обработчика переадресации.
Код прокси может быть следующим:
function Test(a, b, c) {
this[0] = a;
this[1] = b;
this[2] = c;
this.length = 3; // needed for .slice to work
}
Test.prototype.foo = "bar";
Test = (function(old) { // replace function with another function
// that returns an interceptor proxy instead
// of the actual instance
return function() {
var bind = Function.prototype.bind,
slice = Array.prototype.slice,
args = slice.call(arguments),
// to pass all arguments along with a new call:
inst = new(bind.apply(old, [null].concat(args))),
// ^ is ignored because of `new`
// which forces `this`
handler = new Proxy.Handler(inst); // create a forwarding handler
// for the instance
handler.get = function(receiver, name) { // overwrite `get` handler
if(name in inst) { // just return a property on the instance
return inst[name];
}
if(name in Array.prototype) { // otherwise try returning a function
// that calls the appropriate method
// on the instance
return function() {
return Array.prototype[name].apply(inst, arguments);
};
}
};
return Proxy.create(handler, Test.prototype);
};
})(Test);
var test = new Test(123, 456, 789),
sliced = test.slice(1);
console.log(sliced); // [456, 789]
console.log("2" in test); // true
console.log("2" in sliced); // false
console.log(test instanceof Test); // true
// (due to second argument to Proxy.create)
console.log(test.foo); // "bar"
Обработчик переадресации доступен на официальной вики гармонии .
Proxy.Handler = function(target) {
this.target = target;
};
Proxy.Handler.prototype = {
// Object.getOwnPropertyDescriptor(proxy, name) -> pd | undefined
getOwnPropertyDescriptor: function(name) {
var desc = Object.getOwnPropertyDescriptor(this.target, name);
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
// Object.getPropertyDescriptor(proxy, name) -> pd | undefined
getPropertyDescriptor: function(name) {
var desc = Object.getPropertyDescriptor(this.target, name);
if (desc !== undefined) { desc.configurable = true; }
return desc;
},
// Object.getOwnPropertyNames(proxy) -> [ string ]
getOwnPropertyNames: function() {
return Object.getOwnPropertyNames(this.target);
},
// Object.getPropertyNames(proxy) -> [ string ]
getPropertyNames: function() {
return Object.getPropertyNames(this.target);
},
// Object.defineProperty(proxy, name, pd) -> undefined
defineProperty: function(name, desc) {
return Object.defineProperty(this.target, name, desc);
},
// delete proxy[name] -> boolean
delete: function(name) { return delete this.target[name]; },
// Object.{freeze|seal|preventExtensions}(proxy) -> proxy
fix: function() {
// As long as target is not frozen, the proxy won't allow itself to be fixed
if (!Object.isFrozen(this.target)) {
return undefined;
}
var props = {};
Object.getOwnPropertyNames(this.target).forEach(function(name) {
props[name] = Object.getOwnPropertyDescriptor(this.target, name);
}.bind(this));
return props;
},
// == derived traps ==
// name in proxy -> boolean
has: function(name) { return name in this.target; },
// ({}).hasOwnProperty.call(proxy, name) -> boolean
hasOwn: function(name) { return ({}).hasOwnProperty.call(this.target, name); },
// proxy[name] -> any
get: function(receiver, name) { return this.target[name]; },
// proxy[name] = value
set: function(receiver, name, value) {
this.target[name] = value;
return true;
},
// for (var name in proxy) { ... }
enumerate: function() {
var result = [];
for (var name in this.target) { result.push(name); };
return result;
},
// Object.keys(proxy) -> [ string ]
keys: function() { return Object.keys(this.target); }
};