/*! * Microsoft Dynamic Proto Utility, 1.1.9 * Copyright (c) Microsoft and contributors. All rights reserved. */ define((function () { 'use strict'; var _a; var UNDEFINED = "undefined"; /** * Constant string defined to support minimization * @ignore */ var Constructor = 'constructor'; /** * Constant string defined to support minimization * @ignore */ var Prototype = 'prototype'; /** * Constant string defined to support minimization * @ignore */ var strFunction = 'function'; /** * Used to define the name of the instance function lookup table * @ignore */ var DynInstFuncTable = '_dynInstFuncs'; /** * Name used to tag the dynamic prototype function * @ignore */ var DynProxyTag = '_isDynProxy'; /** * Name added to a prototype to define the dynamic prototype "class" name used to lookup the function table * @ignore */ var DynClassName = '_dynClass'; /** * Prefix added to the classname to avoid any name clashes with other instance level properties * @ignore */ var DynClassNamePrefix = '_dynCls$'; /** * A tag which is used to check if we have already to attempted to set the instance function if one is not present * @ignore */ var DynInstChkTag = '_dynInstChk'; /** * A tag which is used to check if we are allows to try and set an instance function is one is not present. Using the same * tag name as the function level but a different const name for readability only. */ var DynAllowInstChkTag = DynInstChkTag; /** * The global (imported) instances where the global performance options are stored */ var DynProtoDefaultOptions = '_dfOpts'; /** * Value used as the name of a class when it cannot be determined * @ignore */ var UnknownValue = '_unknown_'; /** * Constant string defined to support minimization * @ignore */ var str__Proto = "__proto__"; /** * The polyfill version of __proto__ so that it doesn't cause issues for anyone not expecting it to exist */ var DynProtoBaseProto = "_dyn" + str__Proto; /** * Runtime Global holder for dynamicProto settings */ var DynProtoGlobalSettings = "__dynProto$Gbl"; /** * Track the current prototype for IE8 as you can't look back to get the prototype */ var DynProtoCurrent = "_dynInstProto"; /** * Constant string defined to support minimization * @ignore */ var strUseBaseInst = 'useBaseInst'; /** * Constant string defined to support minimization * @ignore */ var strSetInstFuncs = 'setInstFuncs'; var Obj = Object; /** * Pre-lookup to check if we are running on a modern browser (i.e. not IE8) * @ignore */ var _objGetPrototypeOf = Obj["getPrototypeOf"]; /** * Pre-lookup to check for the existence of this function */ var _objGetOwnProps = Obj["getOwnPropertyNames"]; /** * Gets the runtime global reference * @returns */ function _getGlobal() { var result; if (typeof globalThis !== UNDEFINED) { result = globalThis; } if (!result && typeof self !== UNDEFINED) { result = self; } if (!result && typeof window !== UNDEFINED) { result = window; } if (!result && typeof global !== UNDEFINED) { result = global; } return result || {}; } // Since 1.1.7 moving these to the runtime global to work around mixed version and module issues // See Issue https://github.com/microsoft/DynamicProto-JS/issues/57 for details var _gbl = _getGlobal(); var _gblInst = _gbl[DynProtoGlobalSettings] || (_gbl[DynProtoGlobalSettings] = { o: (_a = {}, _a[strSetInstFuncs] = true, _a[strUseBaseInst] = true, _a), n: 1000 // Start new global index @ 1000 so we "fix" some cases when mixed with 1.1.6 or earlier }); /** * Helper to check if the object contains a property of the name * @ignore */ function _hasOwnProperty(obj, prop) { return obj && Obj[Prototype].hasOwnProperty.call(obj, prop); } /** * Helper used to check whether the target is an Object prototype or Array prototype * @ignore */ function _isObjectOrArrayPrototype(target) { return target && (target === Obj[Prototype] || target === Array[Prototype]); } /** * Helper used to check whether the target is an Object prototype, Array prototype or Function prototype * @ignore */ function _isObjectArrayOrFunctionPrototype(target) { return _isObjectOrArrayPrototype(target) || target === Function[Prototype]; } /** * Helper used to get the prototype of the target object as getPrototypeOf is not available in an ES3 environment. * @ignore */ function _getObjProto(target) { var newProto; if (target) { // This method doesn't exist in older browsers (e.g. IE8) if (_objGetPrototypeOf) { return _objGetPrototypeOf(target); } var curProto = target[str__Proto] || target[Prototype] || (target[Constructor] ? target[Constructor][Prototype] : null); // Using the pre-calculated value as IE8 doesn't support looking up the prototype of a prototype and thus fails for more than 1 base class newProto = target[DynProtoBaseProto] || curProto; if (!_hasOwnProperty(target, DynProtoBaseProto)) { // As this prototype doesn't have this property then this is from an inherited class so newProto is the base to return so save it // so we can look it up value (which for a multiple hierarchy dynamicProto will be the base class) delete target[DynProtoCurrent]; // Delete any current value allocated to this instance so we pick up the value from prototype hierarchy newProto = target[DynProtoBaseProto] = target[DynProtoCurrent] || target[DynProtoBaseProto]; target[DynProtoCurrent] = curProto; } } return newProto; } /** * Helper to get the properties of an object, including none enumerable ones as functions on a prototype in ES6 * are not enumerable. * @param target */ function _forEachProp(target, func) { var props = []; if (_objGetOwnProps) { props = _objGetOwnProps(target); } else { for (var name_1 in target) { if (typeof name_1 === "string" && _hasOwnProperty(target, name_1)) { props.push(name_1); } } } if (props && props.length > 0) { for (var lp = 0; lp < props.length; lp++) { func(props[lp]); } } } /** * Helper function to check whether the provided function name is a potential candidate for dynamic * callback and prototype generation. * @param target The target object, may be a prototype or class object * @param funcName The function name * @param skipOwn Skips the check for own property * @ignore */ function _isDynamicCandidate(target, funcName, skipOwn) { return (funcName !== Constructor && typeof target[funcName] === strFunction && (skipOwn || _hasOwnProperty(target, funcName))); } /** * Helper to throw a TypeError exception * @param message the message * @ignore */ function _throwTypeError(message) { throw new TypeError("DynamicProto: " + message); } /** * Returns a collection of the instance functions that are defined directly on the thisTarget object, it does * not return any inherited functions * @param thisTarget The object to get the instance functions from * @ignore */ function _getInstanceFuncs(thisTarget) { // Get the base proto var instFuncs = {}; // Save any existing instance functions _forEachProp(thisTarget, function (name) { // Don't include any dynamic prototype instances - as we only want the real functions if (!instFuncs[name] && _isDynamicCandidate(thisTarget, name, false)) { // Create an instance callback for passing the base function to the caller instFuncs[name] = thisTarget[name]; } }); return instFuncs; } /** * Returns whether the value is included in the array * @param values The array of values * @param value The value */ function _hasVisited(values, value) { for (var lp = values.length - 1; lp >= 0; lp--) { if (values[lp] === value) { return true; } } return false; } /** * Returns an object that contains callback functions for all "base/super" functions, this is used to "save" * enabling calling super.xxx() functions without requiring that the base "class" has defined a prototype references * @param target The current instance * @ignore */ function _getBaseFuncs(classProto, thisTarget, instFuncs, useBaseInst) { function _instFuncProxy(target, funcHost, funcName) { var theFunc = funcHost[funcName]; if (theFunc[DynProxyTag] && useBaseInst) { // grab and reuse the hosted looking function (if available) otherwise the original passed function var instFuncTable = target[DynInstFuncTable] || {}; if (instFuncTable[DynAllowInstChkTag] !== false) { theFunc = (instFuncTable[funcHost[DynClassName]] || {})[funcName] || theFunc; } } return function () { // eslint-disable-next-line prefer-rest-params return theFunc.apply(target, arguments); }; } // Start creating a new baseFuncs by creating proxies for the instance functions (as they may get replaced) var baseFuncs = {}; _forEachProp(instFuncs, function (name) { // Create an instance callback for passing the base function to the caller baseFuncs[name] = _instFuncProxy(thisTarget, instFuncs, name); }); // Get the base prototype functions var baseProto = _getObjProto(classProto); var visited = []; // Don't include base object functions for Object, Array or Function while (baseProto && !_isObjectArrayOrFunctionPrototype(baseProto) && !_hasVisited(visited, baseProto)) { // look for prototype functions _forEachProp(baseProto, function (name) { // Don't include any dynamic prototype instances - as we only want the real functions // For IE 7/8 the prototype lookup doesn't provide the full chain so we need to bypass the // hasOwnProperty check we get all of the methods, main difference is that IE7/8 doesn't return // the Object prototype methods while bypassing the check if (!baseFuncs[name] && _isDynamicCandidate(baseProto, name, !_objGetPrototypeOf)) { // Create an instance callback for passing the base function to the caller baseFuncs[name] = _instFuncProxy(thisTarget, baseProto, name); } }); // We need to find all possible functions that might be overloaded by walking the entire prototype chain // This avoids the caller from needing to check whether it's direct base class implements the function or not // by walking the entire chain it simplifies the usage and issues from upgrading any of the base classes. visited.push(baseProto); baseProto = _getObjProto(baseProto); } return baseFuncs; } function _getInstFunc(target, funcName, proto, currentDynProtoProxy) { var instFunc = null; // We need to check whether the class name is defined directly on this prototype otherwise // it will walk the proto chain and return any parent proto classname. if (target && _hasOwnProperty(proto, DynClassName)) { var instFuncTable = target[DynInstFuncTable] || {}; instFunc = (instFuncTable[proto[DynClassName]] || {})[funcName]; if (!instFunc) { // Avoid stack overflow from recursive calling the same function _throwTypeError("Missing [" + funcName + "] " + strFunction); } // We have the instance function, lets check it we can speed up further calls // by adding the instance function back directly on the instance (avoiding the dynamic func lookup) if (!instFunc[DynInstChkTag] && instFuncTable[DynAllowInstChkTag] !== false) { // If the instance already has an instance function we can't replace it var canAddInst = !_hasOwnProperty(target, funcName); // Get current prototype var objProto = _getObjProto(target); var visited = []; // Lookup the function starting at the top (instance level prototype) and traverse down, if the first matching function // if nothing is found or if the first hit is a dynamic proto instance then we can safely add an instance shortcut while (canAddInst && objProto && !_isObjectArrayOrFunctionPrototype(objProto) && !_hasVisited(visited, objProto)) { var protoFunc = objProto[funcName]; if (protoFunc) { canAddInst = (protoFunc === currentDynProtoProxy); break; } // We need to find all possible initial functions to ensure that we don't bypass a valid override function visited.push(objProto); objProto = _getObjProto(objProto); } try { if (canAddInst) { // This instance doesn't have an instance func and the class hierarchy does have a higher level prototype version // so it's safe to directly assign for any subsequent calls (for better performance) target[funcName] = instFunc; } // Block further attempts to set the instance function for any instFunc[DynInstChkTag] = 1; } catch (e) { // Don't crash if the object is readonly or the runtime doesn't allow changing this // And set a flag so we don't try again for any function instFuncTable[DynAllowInstChkTag] = false; } } } return instFunc; } function _getProtoFunc(funcName, proto, currentDynProtoProxy) { var protoFunc = proto[funcName]; // Check that the prototype function is not a self reference -- try to avoid stack overflow! if (protoFunc === currentDynProtoProxy) { // It is so lookup the base prototype protoFunc = _getObjProto(proto)[funcName]; } if (typeof protoFunc !== strFunction) { _throwTypeError("[" + funcName + "] is not a " + strFunction); } return protoFunc; } /** * Add the required dynamic prototype methods to the the class prototype * @param proto - The class prototype * @param className - The instance classname * @param target - The target instance * @param baseInstFuncs - The base instance functions * @param setInstanceFunc - Flag to allow prototype function to reset the instance function if one does not exist * @ignore */ function _populatePrototype(proto, className, target, baseInstFuncs, setInstanceFunc) { function _createDynamicPrototype(proto, funcName) { var dynProtoProxy = function () { // Use the instance or prototype function var instFunc = _getInstFunc(this, funcName, proto, dynProtoProxy) || _getProtoFunc(funcName, proto, dynProtoProxy); // eslint-disable-next-line prefer-rest-params return instFunc.apply(this, arguments); }; // Tag this function as a proxy to support replacing dynamic proxy elements (primary use case is for unit testing // via which can dynamically replace the prototype function reference) dynProtoProxy[DynProxyTag] = 1; return dynProtoProxy; } if (!_isObjectOrArrayPrototype(proto)) { var instFuncTable = target[DynInstFuncTable] = target[DynInstFuncTable] || {}; var instFuncs_1 = instFuncTable[className] = (instFuncTable[className] || {}); // fetch and assign if as it may not exist yet // Set whether we are allow to lookup instances, if someone has set to false then do not re-enable if (instFuncTable[DynAllowInstChkTag] !== false) { instFuncTable[DynAllowInstChkTag] = !!setInstanceFunc; } _forEachProp(target, function (name) { // Only add overridden functions if (_isDynamicCandidate(target, name, false) && target[name] !== baseInstFuncs[name]) { // Save the instance Function to the lookup table and remove it from the instance as it's not a dynamic proto function instFuncs_1[name] = target[name]; delete target[name]; // Add a dynamic proto if one doesn't exist or if a prototype function exists and it's not a dynamic one if (!_hasOwnProperty(proto, name) || (proto[name] && !proto[name][DynProxyTag])) { proto[name] = _createDynamicPrototype(proto, name); } } }); } } /** * Checks whether the passed prototype object appears to be correct by walking the prototype hierarchy of the instance * @param classProto The class prototype instance * @param thisTarget The current instance that will be checked whether the passed prototype instance is in the hierarchy * @ignore */ function _checkPrototype(classProto, thisTarget) { // This method doesn't existing in older browsers (e.g. IE8) if (_objGetPrototypeOf) { // As this is primarily a coding time check, don't bother checking if running in IE8 or lower var visited = []; var thisProto = _getObjProto(thisTarget); while (thisProto && !_isObjectArrayOrFunctionPrototype(thisProto) && !_hasVisited(visited, thisProto)) { if (thisProto === classProto) { return true; } // This avoids the caller from needing to check whether it's direct base class implements the function or not // by walking the entire chain it simplifies the usage and issues from upgrading any of the base classes. visited.push(thisProto); thisProto = _getObjProto(thisProto); } return false; } // If objGetPrototypeOf doesn't exist then just assume everything is ok. return true; } /** * Gets the current prototype name using the ES6 name if available otherwise falling back to a use unknown as the name. * It's not critical for this to return a name, it's used to decorate the generated unique name for easier debugging only. * @param target * @param unknownValue * @ignore */ function _getObjName(target, unknownValue) { if (_hasOwnProperty(target, Prototype)) { // Look like a prototype return target.name || unknownValue || UnknownValue; } return (((target || {})[Constructor]) || {}).name || unknownValue || UnknownValue; } /** * Helper function when creating dynamic (inline) functions for classes, this helper performs the following tasks :- * - Saves references to all defined base class functions * - Calls the delegateFunc with the current target (this) and a base object reference that can be used to call all "super" functions. * - Will populate the class prototype for all overridden functions to support class extension that call the prototype instance. * Callers should use this helper when declaring all function within the constructor of a class, as mentioned above the delegateFunc is * passed both the target "this" and an object that can be used to call any base (super) functions, using this based object in place of * super.XXX() (which gets expanded to _super.prototype.XXX()) provides a better minification outcome and also ensures the correct "this" * context is maintained as TypeScript creates incorrect references using super.XXXX() for dynamically defined functions i.e. Functions * defined in the constructor or some other function (rather than declared as complete typescript functions). * ### Usage * ```typescript * import dynamicProto from "@microsoft/dynamicproto-js"; * class ExampleClass extends BaseClass { * constructor() { * dynamicProto(ExampleClass, this, (_self, base) => { * // This will define a function that will be converted to a prototype function * _self.newFunc = () => { * // Access any "this" instance property * if (_self.someProperty) { * ... * } * } * // This will define a function that will be converted to a prototype function * _self.myFunction = () => { * // Access any "this" instance property * if (_self.someProperty) { * // Call the base version of the function that we are overriding * base.myFunction(); * } * ... * } * _self.initialize = () => { * ... * } * // Warnings: While the following will work as _self is simply a reference to * // this, if anyone overrides myFunction() the overridden will be called first * // as the normal JavaScript method resolution will occur and the defined * // _self.initialize() function is actually gets removed from the instance and * // a proxy prototype version is created to reference the created method. * _self.initialize(); * }); * } * } * ``` * @typeparam DPType This is the generic type of the class, used to keep intellisense valid * @typeparam DPCls The type that contains the prototype of the current class * @param theClass - This is the current class instance which contains the prototype for the current class * @param target - The current "this" (target) reference, when the class has been extended this.prototype will not be the 'theClass' value. * @param delegateFunc - The callback function (closure) that will create the dynamic function * @param options - Additional options to configure how the dynamic prototype operates */ function dynamicProto(theClass, target, delegateFunc, options) { // Make sure that the passed theClass argument looks correct if (!_hasOwnProperty(theClass, Prototype)) { _throwTypeError("theClass is an invalid class definition."); } // Quick check to make sure that the passed theClass argument looks correct (this is a common copy/paste error) var classProto = theClass[Prototype]; if (!_checkPrototype(classProto, target)) { _throwTypeError("[" + _getObjName(theClass) + "] not in hierarchy of [" + _getObjName(target) + "]"); } var className = null; if (_hasOwnProperty(classProto, DynClassName)) { // Only grab the class name if it's defined on this prototype (i.e. don't walk the prototype chain) className = classProto[DynClassName]; } else { // As not all browser support name on the prototype creating a unique dynamic one if we have not already // assigned one, so we can use a simple string as the lookup rather than an object for the dynamic instance // function table lookup. className = DynClassNamePrefix + _getObjName(theClass, "_") + "$" + _gblInst.n; _gblInst.n++; classProto[DynClassName] = className; } var perfOptions = dynamicProto[DynProtoDefaultOptions]; var useBaseInst = !!perfOptions[strUseBaseInst]; if (useBaseInst && options && options[strUseBaseInst] !== undefined) { useBaseInst = !!options[strUseBaseInst]; } // Get the current instance functions var instFuncs = _getInstanceFuncs(target); // Get all of the functions for any base instance (before they are potentially overridden) var baseFuncs = _getBaseFuncs(classProto, target, instFuncs, useBaseInst); // Execute the delegate passing in both the current target "this" and "base" function references // Note casting the same type as we don't actually have the base class here and this will provide some intellisense support delegateFunc(target, baseFuncs); // Don't allow setting instance functions for older IE instances var setInstanceFunc = !!_objGetPrototypeOf && !!perfOptions[strSetInstFuncs]; if (setInstanceFunc && options) { setInstanceFunc = !!options[strSetInstFuncs]; } // Populate the Prototype for any overridden instance functions _populatePrototype(classProto, className, target, instFuncs, setInstanceFunc !== false); } /** * Exposes the default global options to allow global configuration, if the global values are disabled these will override * any passed values. This is primarily exposed to support unit-testing without the need for individual classes to expose * their internal usage of dynamic proto. */ dynamicProto[DynProtoDefaultOptions] = _gblInst.o; return dynamicProto; }));