Skip to content

Instantly share code, notes, and snippets.

@samelie
Last active November 6, 2017 16:20
Show Gist options
  • Save samelie/ff8e891914e9a4594507966a1b1ff9f3 to your computer and use it in GitHub Desktop.
Save samelie/ff8e891914e9a4594507966a1b1ff9f3 to your computer and use it in GitHub Desktop.
/*
* 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
}
}
@samelie
Copy link
Author

samelie commented Jul 20, 2016

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;
}

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