UNPKG

can-observe

Version:

Like can.Map, but without the .attr method.

125 lines (111 loc) 4.17 kB
"use strict"; // # -make-function.js // This module's `.observable` method proxies an function to make it an any instances // created by it observable. var canReflect = require("can-reflect"); var makeObject = require("./-make-object"); var makeObserve = require("./-make-observe"); var symbols = require("./-symbols"); var observableStore = require("./-observable-store"); var mapBindings = require("can-event-queue/map/map"); var typeBindings = require("can-event-queue/type/type"); var helpers = require("./-helpers"); // ## proxyKeys // A function's proxyKeys is a combination of: // - object's symbols (`.onKeyValue`) // - type event symbols (`.onInstancePatches`, `.onInstanceBound`) // - type definition symbols (`.defineInstanceKey`) var proxyKeys = helpers.assignEverything(Object.create(null), makeObject.proxyKeys()); typeBindings(proxyKeys); canReflect.assignSymbols(proxyKeys, { "can.defineInstanceKey": function(prop, value) { this[symbols.metaSymbol].definitions[prop] = value; } }); var makeFunction = { observable: function(object, options) { if(options.shouldRecordObservation === undefined) { options.shouldRecordObservation = makeObject.shouldRecordObservationOnOwnAndMissingKeys; } var proxyKeys = Object.create(makeFunction.proxyKeys()); var meta = { target: object, proxyKeys: proxyKeys, computedKeys: Object.create(null), options: options, definitions: {}, isClass: helpers.isClass(object), preventSideEffects: 0 }; proxyKeys[symbols.metaSymbol] = meta; meta.proxy = new Proxy(object, { get: makeObject.get.bind(meta), set: makeObject.set.bind(meta), ownKeys: makeObject.ownKeys.bind(meta), deleteProperty: makeObject.deleteProperty.bind(meta), construct: makeFunction.construct.bind(meta), apply: makeFunction.apply.bind(meta), meta: meta }); mapBindings.addHandlers(meta.proxy, meta); typeBindings.addHandlers(meta.proxy, meta); // Store the function and its proxy now, before we // convert the prototype and its constructor to proxies. observableStore.proxiedObjects.set(object, meta.proxy); observableStore.proxies.add(meta.proxy); // Change prototype and its constructor if (meta.target.prototype && meta.target.prototype.constructor === meta.target) { var newPrototype = makeObject.observable(meta.target.prototype, { getPrototypeOf: function(){ return meta.target.prototype; }, observe: makeObserve.observe }); observableStore.proxiedObjects.set(meta.target.prototype, newPrototype); observableStore.proxies.add(newPrototype); var prototype = meta.proxy.prototype; prototype.constructor = meta.proxy; } return meta.proxy; }, // `construct` is called when the `new` operator is used. // It needs to return an observable instance. construct: function(target, argumentsList, newTarget) { var instanceTarget, key; if (this.isClass) { // If the `target` a class, we can't call the function without new. We // can create the instance with `Reflect.construct`. instanceTarget = Reflect.construct(target, argumentsList, newTarget); // Support `can.defineInstanceKey`. for (key in this.definitions) { Object.defineProperty(instanceTarget, key, this.definitions[key]); } return this.options.observe(instanceTarget); } else { // Create an empty object that inherits from the constructor function. instanceTarget = Object.create(this.proxy.prototype); // Support `can.defineInstanceKey`. for (key in this.definitions) { Object.defineProperty(instanceTarget, key, this.definitions[key]); } var instance = this.options.observe(instanceTarget); instance[symbols.metaSymbol].preventSideEffects++; var res = target.apply(instance, argumentsList); instance[symbols.metaSymbol].preventSideEffects--; if (res) { return res; } else { return instance; } } }, // `apply` makes sure the function returns an observable. apply: function(target, thisArg, argumentsList) { var ret = this.target.apply(thisArg, argumentsList); return this.options.observe(ret); }, proxyKeys: function() { return proxyKeys; } }; module.exports = makeFunction;