Last active
November 6, 2017 16:20
-
-
Save samelie/ff8e891914e9a4594507966a1b1ff9f3 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2016 Google Inc. All rights reserved. | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); you may not | |
* use this file except in compliance with the License. You may obtain a copy of | |
* the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
* License for the specific language governing permissions and limitations under | |
* the License. | |
*/ | |
'use strict'; | |
(function(scope) { | |
if (scope['Proxy']) { | |
return; | |
} | |
let lastRevokeFn = null; | |
/** | |
* @param {*} o | |
* @return {boolean} whether this is probably a (non-null) Object | |
*/ | |
function isObject(o) { | |
return o ? (typeof o == 'object' || typeof o == 'function') : false; | |
} | |
/** | |
* @constructor | |
* @param {!Object} target | |
* @param {{apply, construct, get, set}} handler | |
*/ | |
scope.Proxy = function(target, handler) { | |
if (!isObject(target) || !isObject(handler)) { | |
throw new TypeError('Cannot create proxy with a non-object as target or handler'); | |
} | |
// Construct revoke function, and set lastRevokeFn so that Proxy.revocable can steal it. | |
// The caller might get the wrong revoke function if a user replaces or wraps scope.Proxy | |
// to call itself, but that seems unlikely especially when using the polyfill. | |
let throwRevoked = function() {}; | |
lastRevokeFn = function() { | |
throwRevoked = function(trap) { | |
throw new TypeError(`Cannot perform '${trap}' on a proxy that has been revoked`); | |
}; | |
}; | |
// Fail on unsupported traps: Chrome doesn't do this, but ensure that users of the polyfill | |
// are a bit more careful. Copy the internal parts of handler to prevent user changes. | |
let unsafeHandler = handler; | |
handler = {'get': null, 'set': null, 'apply': null, 'construct': null}; | |
for (let k in unsafeHandler) { | |
if (!(k in handler)) { | |
throw new TypeError(`Proxy polyfill does not support trap '${k}'`); | |
} | |
handler[k] = unsafeHandler[k]; | |
} | |
if (typeof unsafeHandler == 'function') { | |
// Allow handler to be a function (which has an 'apply' method). This matches what is | |
// probably a bug in native versions. It treats the apply call as a trap to be configured. | |
handler.apply = unsafeHandler.apply.bind(unsafeHandler); | |
} | |
// Define proxy as this, or a Function (if either it's callable, or apply is set). | |
// TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it. | |
let proxy = this; | |
let isMethod = false; | |
let targetIsFunction = typeof target == 'function'; | |
if (handler.apply || handler['construct'] || targetIsFunction) { | |
proxy = function Proxy() { | |
let usingNew = (this && this.constructor === proxy); | |
throwRevoked(usingNew ? 'construct' : 'apply'); | |
if (usingNew && handler['construct']) { | |
return handler['construct'].call(this, target, arguments); | |
} else if (!usingNew && handler.apply) { | |
return handler.apply(target, this, arguments); | |
} else if (targetIsFunction) { | |
// since the target was a function, fallback to calling it directly. | |
if (usingNew) { | |
// inspired by answers to https://stackoverflow.com/q/1606797 | |
let all = Array.prototype.slice.call(arguments); | |
all.unshift(target); // pass class as first arg to constructor, although irrelevant | |
// nb. cast to convince Closure compiler that this is a constructor | |
let f = /** @type {!Function} */ (target.bind.apply(target, all)); | |
return new f(); | |
} | |
return target.apply(this, arguments); | |
} | |
throw new TypeError(usingNew ? 'not a constructor' : 'not a function'); | |
}; | |
isMethod = true; | |
} | |
// Create default getters/setters. Create different code paths as handler.get/handler.set can't | |
// change after creation. | |
let getter = handler.get ? function(prop) { | |
throwRevoked('get'); | |
return handler.get(this, prop, proxy); | |
} : function(prop) { | |
throwRevoked('get'); | |
return this[prop]; | |
}; | |
let setter = handler.set ? function(prop, value) { | |
throwRevoked('set'); | |
let status = handler.set(this, prop, value, proxy); | |
if (!status) { | |
// TODO(samthor): If the calling code is in strict mode, throw TypeError. | |
// It's (sometimes) possible to work this out, if this code isn't strict- try to load the | |
// callee, and if it's available, that code is non-strict. However, this isn't exhaustive. | |
} | |
} : function(prop, value) { | |
throwRevoked('set'); | |
this[prop] = value; | |
}; | |
// Clone direct properties (i.e., not part of a prototype). | |
let propertyNames = Object.getOwnPropertyNames(target); | |
let propertyMap = {}; | |
propertyNames.forEach(function(prop) { | |
if (isMethod && prop in proxy) { | |
return; // ignore properties already here, e.g. 'bind', 'prototype' etc | |
} | |
let real = Object.getOwnPropertyDescriptor(target, prop); | |
let desc = { | |
enumerable: !!real.enumerable, | |
get: getter.bind(target, prop), | |
set: setter.bind(target, prop), | |
}; | |
Object.defineProperty(proxy, prop, desc); | |
propertyMap[prop] = true; | |
}); | |
// Set the prototype, or clone all prototype methods (always required if a getter is provided). | |
// TODO(samthor): We don't allow prototype methods to be set. It's (even more) awkward. | |
// An alternative here would be to _just_ clone methods to keep behavior consistent. | |
let prototypeOk = true; | |
if (Object.setPrototypeOf) { | |
Object.setPrototypeOf(proxy, Object.getPrototypeOf(target)); | |
} else if (proxy.__proto__) { | |
proxy.__proto__ = target.__proto__; | |
} else { | |
prototypeOk = false; | |
} | |
if (handler.get || !prototypeOk) { | |
for (let k in target) { | |
if (propertyMap[k]) { | |
continue; | |
} | |
Object.defineProperty(proxy, k, {get: getter.bind(target, k)}); | |
} | |
} | |
// The Proxy polyfill cannot handle adding new properties. Seal the target and proxy. | |
Object.seal(target); | |
Object.seal(proxy); | |
return proxy; // nb. if isMethod is true, proxy != this | |
}; | |
scope.Proxy.revocable = function(target, handler) { | |
let p = new scope.Proxy(target, handler); | |
return {'proxy': p, 'revoke': lastRevokeFn}; | |
}; | |
scope.Proxy['revocable'] = scope.Proxy.revocable; | |
scope['Proxy'] = scope.Proxy; | |
})(window); | |
export default class SoundProxy { | |
constructor(options, callback) { | |
this._options = options | |
this._p = this._createProxy(this._options,callback) | |
} | |
get p(){ | |
return this._p | |
} | |
_createProxy(o, fn) { | |
return new Proxy(o, { | |
set(target, property, value) { | |
fn(property, value); | |
target[property] = value; | |
return true | |
}, | |
}) | |
} | |
destroy(){ | |
this._p = null | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
On the changed callback, it returns a string
use this to search an object by string
Utils.ObjectbyString = function(o, s) {
s = s.replace(/[(\w+)]/g, '.$1'); // convert indexes to properties
s = s.replace(/^./, ''); // strip a leading dot
var a = s.split('.');
for (var i = 0, n = a.length; i < n; ++i) {
var k = a[i];
if (k in o) {
o = o[k];
} else {
return;
}
}
return o;
}