From e001327730635014aea4731b20027c993b16b2b4 Mon Sep 17 00:00:00 2001 From: Andy Harrison Date: Thu, 21 May 2020 15:09:31 -0400 Subject: [PATCH 1/6] Implement JavaScript modules --- src/Class testing.js | 272 +++++++++++++++++++++++++++++++ src/Class.js | 210 ------------------------ src/Class.mjs.js | 185 +++++++++++++++++++++ src/examples.js | 64 ++++++++ src/privates.js | 64 ++++++++ test/Class testing.htm | 281 ++------------------------------ test/Privates using symbols.htm | 75 ++------- test/examples.htm | 79 ++------- 8 files changed, 622 insertions(+), 608 deletions(-) create mode 100644 src/Class testing.js delete mode 100644 src/Class.js create mode 100644 src/Class.mjs.js create mode 100644 src/examples.js create mode 100644 src/privates.js diff --git a/src/Class testing.js b/src/Class testing.js new file mode 100644 index 0000000..c775a86 --- /dev/null +++ b/src/Class testing.js @@ -0,0 +1,272 @@ +import Class from "../src/Class.mjs.js" + +//create a class without adding anything new to it +console.groupCollapsed("A - empty class"); + let Alpha, alpha1; + + console.group("Alpha class"); + Alpha = Class.extend(); + console.dir(Alpha); + console.assert(Alpha.name === "Class", Alpha.name); //function's .name property is the class name + console.assert(Alpha.extend === Class.extend, Alpha.extend); //.extend() is inherited (copied to new class) + console.assert(Alpha.toString() === "function Class() { [custom code] }", Alpha.toString()); //.toString() output + console.groupEnd(); + + console.group("Alpha instance"); + alpha1 = new Alpha(); + console.dir(alpha1); + console.assert(alpha1.constructor === Alpha, alpha1.constructor); //the Alpha class is the constructor of the instance + console.assert(alpha1.toString() === "[instance of Class]", alpha1.toString()); //.toString() output + console.groupEnd(); +console.groupEnd(); + +//create a class with a custom class name +console.groupCollapsed("B - class name"); + let Bravo, bravo1; + + console.group("Bravo class"); + Bravo = Class.extend({className:"Bravo"}); + console.dir(Bravo); + console.assert(Bravo.name === "Bravo", Bravo.name); //class name is 'Bravo' + console.assert(Bravo.toString() === "function Class() { [custom code] }", Bravo.toString()); //.toString() output + console.groupEnd(); + + console.group("Bravo instance"); + bravo1 = new Bravo(); + console.dir(bravo1); + console.assert(bravo1.toString() === "[instance of Bravo]", bravo1.toString()); //.toString() output + console.groupEnd(); +console.groupEnd(); + +//create a class, specifying an invalid class name +console.groupCollapsed("C - invalid class name"); + let Charlie, charlie1; + + console.group("Charlie class"); + Charlie = Class.extend({className:"5"}); + console.dir(Charlie); + console.assert(Charlie.name === "Class", Charlie.name); //class name is inherited (copied to new class) + console.groupEnd(); + + console.group("Charlie instance"); + charlie1 = new Charlie(); + console.dir(charlie1); + console.assert(charlie1.toString() === "[instance of Class]", charlie1.toString()); //.toString() output + console.groupEnd(); +console.groupEnd(); + +//specify returnFn() in case class is called without the 'new' keyword +console.groupCollapsed("D - return function"); + let Delta, delta1, delta2; + + console.group("Delta class"); + Delta = Class.extend({className:"Delta", + constructorFn:function (Super){ + Super(); + this.bar = function (){return "bar"}; + this.baz = function (){return this.bar()}; + }, + returnFn:function (){return "foo"} + }); + console.dir(Delta); + console.groupEnd(); + + console.group("Delta call"); + delta1 = Delta(); + console.dir(delta1); + console.assert(delta1 === "foo", delta1); //returns output of returnFn() + console.assert(delta1.bar === void 0, delta1.bar); //.bar() is not added to the instance + console.assert(delta1.baz === void 0, delta1.baz); //.baz() is not added to the instance + console.groupEnd(); + + console.group("Delta instance"); + delta2 = new Delta(); + console.dir(delta2); + console.assert(delta2.bar() === "bar", delta2.bar()); //.bar() returns 'bar' + console.assert(delta2.baz() === "bar", delta2.baz()); //.baz() returns 'bar' + console.groupEnd(); +console.groupEnd(); + +//use 'this' keyword inside of returnFn() +console.groupCollapsed("E - return function using 'this'"); + let Echo, echo1; + + console.group("Echo class"); + Echo = Class.extend({className:"Echo", + constructorFn:function (){ + this.foo = function (){return "foo"}; + }, + returnFn:function (){return this.foo} + }); + console.dir(Echo); + console.groupEnd(); + + console.group("Echo call"); + echo1 = Echo(); + console.dir(echo1); + console.assert(echo1 === window.foo, echo1); //'this' refers to the window, not an instance of the class + console.groupEnd(); +console.groupEnd(); + +//don't call Super() within the Golf class' constructorFn() +console.groupCollapsed("F,G - subclass constructor without 'Super()'"); + let Foxtrot, Golf, golf1, golf2; + + console.group("Foxtrot class"); + Foxtrot = Class.extend({className:"Foxtrot", + constructorFn:function(Super){Super();this.foo = "foo"} + }); + console.dir(Foxtrot); + console.groupEnd(); + + console.group("Golf class"); + Golf = Class.extend({className:"Golf", + constructorFn:function(Super){} + }); + console.dir(Golf); + console.groupEnd(); + + console.group("Golf instance 1"); + golf1 = new Golf(); //logs a warning to the console from the Golf constructor + console.info("\u2B11 should have gotten a warning about 'Super'"); + console.dir(golf1); + console.assert(golf1.foo === void 0, golf1.foo); //.foo was not added to the instance via the super class constructor + console.groupEnd(); + + console.group("Golf instance 2"); + console.info("\u2B10 should not get additional warnings"); + golf2 = new Golf(); //should not log a warning again + console.dir(golf2); + console.groupEnd(); +console.groupEnd(); + +//don't call Super() within the Hotel class' constructorFn() +console.groupCollapsed("H,I - super-class constructor without 'Super()'"); + let Hotel, India, india1, india2; + + console.group("Hotel class"); + Hotel = Class.extend({className:"Hotel", + constructorFn:function(Super){this.foo = "foo"} + }); + console.dir(Hotel); + console.groupEnd(); + + console.group("India class"); + India = Hotel.extend({className:"D", + constructorFn:function(Super){Super()} + }); + console.dir(India); + console.groupEnd(); + + console.group("India instance 1"); + india1 = new India(); //logs a warning to the console from the Hotel constructor + console.info("\u2B11 should have gotten a warning about 'Super'"); + console.dir(india1); + console.groupEnd(); + + console.group("India instance 2"); + console.info("\u2B10 should not get additional warnings"); + india2 = new India(); + console.dir(india2); + console.groupEnd(); +console.groupEnd(); + +//call Super() inside all the constructor functions +console.groupCollapsed("J,K - constructors with 'Super()'"); + let Juliet, Kilo, kilo1; + + console.group("Juliet class"); + Juliet = Class.extend({className:"Juliet", + constructorFn:function(Super){Super();this.foo = "foo"} + }); + console.dir(Juliet); + console.groupEnd(); + + console.group("Kilo class"); + Kilo = Juliet.extend({className:"Kilo", + constructorFn:function(Super){Super()} + }); + console.dir(Kilo); + console.groupEnd(); + + console.group("Kilo instance"); + kilo1 = new Kilo(); + console.dir(kilo1); + console.assert(kilo1.foo === "foo", kilo1.foo); //.foo was added to the instance via the super class constructor + console.groupEnd(); +console.groupEnd(); + +//protected properties +console.groupCollapsed("L,M,N - protected properties"); + let Lima, Mike, mike1, November, november1; + + console.group("Lima class"); + Lima = Class.extend({className:"Lima", + constructorFn:function(Super){ + Super(); + let foo="bar"; + this.bop = function (){return foo}; + Object.defineProperty(Super.protected, "foo", { + get:function (){return foo}, + set:function(v){foo=v}, + enumerable:true, configurable:true + }); //subclasses of Lima will have access to the 'foo' variable + Super.protected.foo = foo; + } + }); + console.dir(Lima); + console.groupEnd(); + + console.group("Mike class"); + Mike = Lima.extend({className:"Mike", + constructorFn:function(Super){ + Super(); + let $protected = Super.protected; + console.log('Super.protected', $protected); + console.assert($protected.foo === "bar", $protected.foo); //Mike constructor has access to the protected foo value + $protected.foo = "baz"; + console.assert($protected.foo === "baz", $protected.foo); //protected foo value can be changed via the Mike constructor + console.assert(this.bop() === "baz", this.bop()); //confirms that the value in the Lima constructor's variable is what changed + delete $protected.foo; //subclasses of Mike will not have access to the protected foo value + } + }); + console.dir(Mike); + console.groupEnd(); + + console.group("Mike instance"); + mike1 = new Mike(); + console.dir(mike1); + console.assert(mike1.foo === void 0, mike1.foo); //instance doesn't have access to the protected foo value + console.assert(mike1.bop() === "baz", mike1.bop()); //instance's constructor-created method does have access to the protected foo value + console.groupEnd(); + + console.group("November class"); + November = Mike.extend({className:"November", + constructorFn:function(Super){ + Super(); + console.assert(Super.protected.foo === void 0, Super.protected.foo); //class November doesn't have access to the protected foo value + console.assert(this.bop() === "baz", this.bop()); //inherited function still has access + } + }); + console.dir(November); + console.groupEnd(); + + console.group("November instance"); + november1 = new November(); + console.dir(november1); + console.assert(november1.bop() === "baz", november1.bop()); //inherited function still has access + console.groupEnd(); +console.groupEnd(); + +//Class.noConflict() +console.groupCollapsed("O - noConflict"); + let Oscar; + + console.dir(Class); + console.assert(Object.getOwnPropertyDescriptor(Class, "noConflict").enumerable === false, Object.getOwnPropertyDescriptor(Class, "noConflict").enumerable); + Oscar = Class.noConflict(); + console.assert(Class === void 0, Class); + console.dir(Oscar); + console.assert(Object.getOwnPropertyDescriptor(Oscar, "noConflict") === void 0, Object.getOwnPropertyDescriptor(Oscar, "noConflict")); + console.assert(Oscar.noConflict === void 0, Oscar.noConflict); +console.groupEnd(); diff --git a/src/Class.js b/src/Class.js deleted file mode 100644 index 3e07a2c..0000000 --- a/src/Class.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-env es6 */ -// https://github.com/wizard04wsu/Class - -(function (){ - - "use strict"; - - let _initializing = false; - - - /*** helper functions ***/ - - function defineProperty(object, propertyName, value, isWritable, isEnumerable, isConfigurable){ - Object.defineProperty(object, propertyName, { value:value, writable:isWritable, enumerable:isEnumerable, configurable:isConfigurable }); - } - function isPrimitive(o){ let t = typeof o; return (t !== "object" && t !== "function" ) || o === null; } - function warn(msg){ if(console) (console.warn || console.log)(msg); } - - function classNameIsValid(className){ - //checks if the specified classname is valid (note: this doesn't check for reserved words) - return className !== (void 0) && /^[a-z_$][a-z0-9_$]*$/i.test(className); - } - - - /*** shared functions ***/ - - function _constructorFn(Super){ Super.apply(null, [].slice.call(arguments, 1)); } - function _emptyFn(){} - let _classToString = function toString(){ return "function Class() { [custom code] }"; }; - let _instanceToString = function toString(){ - return "[instance of "+this.constructor.name+"]"; - }; - let _extendToString = function toString(){ return "function extend() { [custom code] }"; }; - - function _generateProtectedAccessorsForSubclass(protectedAccessors_parent = {}){ - let protectedAccessors_child = {}; - for(let key in protectedAccessors_parent){ - Object.defineProperty(protectedAccessors_child, key, { - get: ()=>protectedAccessors_parent[key], - set: value=>(protectedAccessors_parent[key] = value), - enumerable:true, configurable:true - }); - } - //protectedAccessors_child.foo = 'test'; - return protectedAccessors_child; - } - - - /*** base class ***/ - - //the base Class constructor; it will have two static methods, 'extend' and 'noConflict' - let _baseClass = function Class(){}; - - defineProperty(_baseClass.prototype, "toString", _instanceToString, true, false, true); - defineProperty(_baseClass, "toString", _classToString, true, false, true); - - - /** - * Creates a new class that inherits from the parent class. - * - * @param {object} [options] - * @param {string} [options.className] - Used as .name for the class function and in .toString() for instances of the class. - * @param {function} [options.constructorFn] - Initializes new instances of the class. A function is passed as the first argument, used to initialize the instance using the parent class' constructor; be sure to call it inside constructorFn (before using `this` or protected members). - * @param {function} [options.returnFn] - Returns a value when the constructor is called without using the 'new' keyword. - * @return {function} - The new class. - */ - let _extendFn = function extend(options){ - - if(options === void 0) options = {}; - else if(isPrimitive(options)) throw new TypeError("argument 'options' is not an object"); - - - /*** create the new constructor ***/ - - let $constructorFn = typeof(options.constructorFn) === "function" ? options.constructorFn : _constructorFn; - let $returnFn = typeof(options.returnFn) === "function" ? options.returnFn : _emptyFn; - let $warnedAboutSuper = false; - - let newClass = function Class(){ - - if(this && this instanceof newClass && (this.constructor === newClass.prototype.constructor || _initializing)){ - //A new instance is being created; initialize it. - //This condition will be true in these cases: - // 1) The 'new' operator was used to instantiate this class - // 2) The 'new' operator was used to instantiate a subclass, and the subclass' $constructorFn() calls its first argument (the bound superFn) - // 3) The 'new' operator was used to instantiate a subclass, and the subclass' $constructorFn() includes something like `MySuperClass.call(this)` - // 4) Possibly if the prototype chain has been manipulated - - let newInstance = this; - - if(newInstance.constructor === newClass.prototype.constructor){ - //this function is the constructor of the new instance (i.e., it's not a parent class' constructor) - - defineProperty(newInstance, "constructor", newClass, true, false, true); - } - - let protectedAccessors, - superFnCalled = false; - let superFn = function Super(){ - - if(superFnCalled) return; //don't initialize it more than once - superFnCalled = true; - - //initialize the instance using the parent class - protectedAccessors = newClass.prototype.constructor.apply(newInstance, arguments) || {}; - - //add protected value accessors to the Super function - Object.defineProperty(superFn, "protected", { - get: ()=>protectedAccessors, - enumerable:false, configurable:false - }); - - } - - //construct the new instance - _initializing = true; - //$constructorFn.bind(newInstance, superFn).apply(null, arguments); - $constructorFn.apply(newInstance, [superFn].concat([].slice.call(arguments))); //(This way it doesn't create another new function every time a constructor is run.) - - if(!superFnCalled && !$warnedAboutSuper){ - warn(newClass.name+" instance is not initialized by its parent class"); - $warnedAboutSuper = true; //prevent multiple warnings about the same issue - } - - if(newInstance.constructor === newClass){ - //this function is the constructor of the new instance - - _initializing = false; - } - else{ - //this function is the constructor of a super-class - - return _generateProtectedAccessorsForSubclass(protectedAccessors); - } - //else return this - - } - else{ - //the 'new' operator was not used; it was just called as a function - - return $returnFn.apply(null, arguments); - - } - - } - - //override .name - defineProperty(newClass, "name", - classNameIsValid(options.className) ? options.className : this.name /*parent class' name*/, false, false, true); - - //override .toString() - defineProperty(newClass, "toString", _classToString, true, false, true); - - if(this.extend === _extendFn){ - //the 'extend' method of the parent class was not modified - - //make extend() a static method of the new class - defineProperty(newClass, "extend", _extendFn, true, false, true); - } - - - /*** create the new prototype ***/ - - //An uninitialized instance of the parent class will be the prototype of the new class. - //To create an instance without initializing it, we'll temporarily use an empty function as the constructor. - let emptyFn = function (){}; - emptyFn.prototype = this.prototype; - let newPrototype = new emptyFn(); - emptyFn = null; - defineProperty(newPrototype, "constructor", this, true, false, true); - - //override .toString() - defineProperty(newPrototype, "toString", _instanceToString, true, false, true); - - defineProperty(newClass, "prototype", newPrototype, false, false, false); - - - return newClass; - - } - - defineProperty(_extendFn, "toString", _extendToString, true, false, true); - - //make extend() a static method of Class - defineProperty(_baseClass, "extend", _extendFn, true, false, true); - - - - let context = this, - oldClass = context.Class; - - /** - * Restores 'Class' to what it was before this script replaced it. - * - * @return {function} - The base Class constructor. - */ - function noConflict(){ - context.Class = oldClass; - context = oldClass = null; - delete _baseClass.noConflict; - return _baseClass; - } - //make noConflict() a static method of Class - defineProperty(_baseClass, "noConflict", noConflict, true, false, true); - - - - context.Class = _baseClass; - -}).call(this); diff --git a/src/Class.mjs.js b/src/Class.mjs.js new file mode 100644 index 0000000..7b0657a --- /dev/null +++ b/src/Class.mjs.js @@ -0,0 +1,185 @@ +/* eslint-env es6 */ +// https://github.com/wizard04wsu/Class + +let _initializing = false; + + +/*** helper functions ***/ + +function defineProperty(object, propertyName, value, isWritable, isEnumerable, isConfigurable){ + Object.defineProperty(object, propertyName, { value:value, writable:isWritable, enumerable:isEnumerable, configurable:isConfigurable }); +} +function isPrimitive(o){ let t = typeof o; return (t !== "object" && t !== "function" ) || o === null; } +function warn(msg){ if(console) (console.warn || console.log)(msg); } + +function classNameIsValid(className){ +//checks if the specified classname is valid (note: this doesn't check for reserved words) + return className !== (void 0) && /^[a-z_$][a-z0-9_$]*$/i.test(className); +} + + +/*** shared functions ***/ + +function _constructorFn(Super){ Super.apply(null, [].slice.call(arguments, 1)); } +function _emptyFn(){} +let _classToString = function toString(){ return "function Class() { [custom code] }"; }; +let _instanceToString = function toString(){ + return "[instance of "+this.constructor.name+"]"; +}; +let _extendToString = function toString(){ return "function extend() { [custom code] }"; }; + +function _generateProtectedAccessorsForSubclass(protectedAccessors_parent = {}){ + let protectedAccessors_child = {}; + for(let key in protectedAccessors_parent){ + Object.defineProperty(protectedAccessors_child, key, { + get: ()=>protectedAccessors_parent[key], + set: value=>(protectedAccessors_parent[key] = value), + enumerable:true, configurable:true + }); + } + //protectedAccessors_child.foo = 'test'; + return protectedAccessors_child; +} + + +/*** base class ***/ + +//the base Class constructor; it will have two static methods, 'extend' and 'noConflict' +let _baseClass = function Class(){}; + +defineProperty(_baseClass.prototype, "toString", _instanceToString, true, false, true); +defineProperty(_baseClass, "toString", _classToString, true, false, true); + + +/** + * Creates a new class that inherits from the parent class. + * + * @param {object} [options] + * @param {string} [options.className] - Used as .name for the class function and in .toString() for instances of the class. + * @param {function} [options.constructorFn] - Initializes new instances of the class. A function is passed as the first argument, used to initialize the instance using the parent class' constructor; be sure to call it inside constructorFn (before using `this` or protected members). + * @param {function} [options.returnFn] - Returns a value when the constructor is called without using the 'new' keyword. + * @return {function} - The new class. + */ +let _extendFn = function extend(options){ + + if(options === void 0) options = {}; + else if(isPrimitive(options)) throw new TypeError("argument 'options' is not an object"); + + + /*** create the new constructor ***/ + + let $constructorFn = typeof(options.constructorFn) === "function" ? options.constructorFn : _constructorFn; + let $returnFn = typeof(options.returnFn) === "function" ? options.returnFn : _emptyFn; + let $warnedAboutSuper = false; + + let newClass = function Class(){ + + if(this && this instanceof newClass && (this.constructor === newClass.prototype.constructor || _initializing)){ + //A new instance is being created; initialize it. + //This condition will be true in these cases: + // 1) The 'new' operator was used to instantiate this class + // 2) The 'new' operator was used to instantiate a subclass, and the subclass' $constructorFn() calls its first argument (the bound superFn) + // 3) The 'new' operator was used to instantiate a subclass, and the subclass' $constructorFn() includes something like `MySuperClass.call(this)` + // 4) Possibly if the prototype chain has been manipulated + + let newInstance = this; + + if(newInstance.constructor === newClass.prototype.constructor){ + //this function is the constructor of the new instance (i.e., it's not a parent class' constructor) + + defineProperty(newInstance, "constructor", newClass, true, false, true); + } + + let protectedAccessors, + superFnCalled = false; + let superFn = function Super(){ + + if(superFnCalled) return; //don't initialize it more than once + superFnCalled = true; + + //initialize the instance using the parent class + protectedAccessors = newClass.prototype.constructor.apply(newInstance, arguments) || {}; + + //add protected value accessors to the Super function + Object.defineProperty(superFn, "protected", { + get: ()=>protectedAccessors, + enumerable:false, configurable:false + }); + + } + + //construct the new instance + _initializing = true; + //$constructorFn.bind(newInstance, superFn).apply(null, arguments); + $constructorFn.apply(newInstance, [superFn].concat([].slice.call(arguments))); //(This way it doesn't create another new function every time a constructor is run.) + + if(!superFnCalled && !$warnedAboutSuper){ + warn(newClass.name+" instance is not initialized by its parent class"); + $warnedAboutSuper = true; //prevent multiple warnings about the same issue + } + + if(newInstance.constructor === newClass){ + //this function is the constructor of the new instance + + _initializing = false; + } + else{ + //this function is the constructor of a super-class + + return _generateProtectedAccessorsForSubclass(protectedAccessors); + } + //else return this + + } + else{ + //the 'new' operator was not used; it was just called as a function + + return $returnFn.apply(null, arguments); + + } + + } + + //override .name + defineProperty(newClass, "name", + classNameIsValid(options.className) ? options.className : this.name /*parent class' name*/, false, false, true); + + //override .toString() + defineProperty(newClass, "toString", _classToString, true, false, true); + + if(this.extend === _extendFn){ + //the 'extend' method of the parent class was not modified + + //make extend() a static method of the new class + defineProperty(newClass, "extend", _extendFn, true, false, true); + } + + + /*** create the new prototype ***/ + + //An uninitialized instance of the parent class will be the prototype of the new class. + //To create an instance without initializing it, we'll temporarily use an empty function as the constructor. + let emptyFn = function (){}; + emptyFn.prototype = this.prototype; + let newPrototype = new emptyFn(); + emptyFn = null; + defineProperty(newPrototype, "constructor", this, true, false, true); + + //override .toString() + defineProperty(newPrototype, "toString", _instanceToString, true, false, true); + + defineProperty(newClass, "prototype", newPrototype, false, false, false); + + + return newClass; + +} + +defineProperty(_extendFn, "toString", _extendToString, true, false, true); + +//make extend() a static method of Class +defineProperty(_baseClass, "extend", _extendFn, true, false, true); + + + +export { _baseClass as default }; diff --git a/src/examples.js b/src/examples.js new file mode 100644 index 0000000..f6ce87b --- /dev/null +++ b/src/examples.js @@ -0,0 +1,64 @@ +import Class from "./Class.mjs.js" + +console.group("Rectangle & Square"); + let Rectangle = Class.extend({ + className:"Rectangle", + constructorFn:function (Super, width, height){ + Super(); + this.width = width||0; + this.height = height||0; + Object.defineProperty(this, "area", { get:function (){ return Math.abs(this.width * this.height); }, enumerable:true, configurable:true }); + Object.defineProperty(this, "whatAmI", { get:function (){ return "I am a rectangle."; }, enumerable:true, configurable:true }); + }, + returnFn:function (width, height){ + return Math.abs((width||0) * (height||0)); + } + }); + + let Square = Rectangle.extend({ + className:"Square", + constructorFn:function (Super, width){ + Super(width, width); + Object.defineProperty(this, "height", { get:function (){ return this.width; }, set:function (val){ this.width = 1*val||0; }, enumerable:true, configurable:true }); + let iAm = [this.whatAmI, "I am a square."].join(" "); + Object.defineProperty(this, "whatAmI", { get:function (){ return iAm; }, enumerable:true, configurable:true }); + }, + returnFn:function (width){ + return Math.pow(width||0, 2); + } + }); + + let s = new Square(3); + + console.assert(s.toString() === "[instance of Square]", s.toString()); + console.assert(s.area === 9, s.area); + s.height = 4; + console.assert(s.area === 16, s.area); + console.assert(s.whatAmI === "I am a rectangle. I am a square.", s.whatAmI); +console.groupEnd(); + +console.group("Alpha & Bravo"); + let Alpha = Class.extend({ + className:"Alpha", + constructorFn:function (Super){ + Super(); + let randomInstanceID = Math.random(); + Object.defineProperty(Super.protected, "rando", { + get:function(){return randomInstanceID}, + enumerable:true, configurable:true + }); + } + }); + + let Bravo = Alpha.extend({ + className:"Bravo", + constructorFn:function (Super){ + Super(); + this.foo = "My ID is "+Super.protected.rando; + } + }); + + let b = new Bravo(); + + console.log(b.foo); //My ID is ... +console.groupEnd(); diff --git a/src/privates.js b/src/privates.js new file mode 100644 index 0000000..5157329 --- /dev/null +++ b/src/privates.js @@ -0,0 +1,64 @@ +import Class from "../src/Class.mjs.js" + +let Alpha = (function (){ + + const $private = Symbol("private members of Alpha instances"); + + function getMyVal(){ + return this[$private].val; + } + + return Class.extend({ + className: "Alpha", + constructorFn: function (Super, myVal){ + Super(); + this[$private] = {}; + this[$private].val = myVal; + Object.defineProperty(Super.protected, "myVal", { + get: ()=>this[$private].val, + enumerable:true, configurable:true + }); + this.getMyVal = getMyVal; + } + }); + +})(); + +let Bravo = (function (){ + + const $private = Symbol("private members of Bravo instances"); + + function viaPrivate(){ + return this[$private].getVal(); + } + function viaReferenceToProtected(){ + return this[$private].protected.myVal; //Super.protected.myVal + } + + return Alpha.extend({ + className: "Bravo", + constructorFn: function (Super, myVal){ + Super(myVal); + this.viaConstructor = function (){ return Super.protected.myVal; }; + this[$private] = {}; + this[$private].getVal = function (){ return Super.protected.myVal; }; + this[$private].protected = Super.protected; + this.viaPrivate = viaPrivate, + this.viaReferenceToProtected = viaReferenceToProtected + } + }); + +})(); + +console.group("Alpha instance"); +let a = new Alpha(5); +console.assert(a.getMyVal() === 5, "getMyVal()", a.getMyVal()); +console.groupEnd(); + +console.group("Bravo instance"); +let b = new Bravo(10); +console.assert(b.getMyVal() === 10, "getMyVal()", b.getMyVal()); //via inherited method +console.assert(b.viaConstructor() === 10, "viaConstructor()", b.viaConstructor()); +console.assert(b.viaPrivate() === 10, "viaPrivate()", b.viaPrivate()); +console.assert(b.viaReferenceToProtected() === 10, "viaReferenceToProtected()", b.viaReferenceToProtected()); +console.groupEnd(); diff --git a/test/Class testing.htm b/test/Class testing.htm index 7437b8b..f04cdb3 100644 --- a/test/Class testing.htm +++ b/test/Class testing.htm @@ -1,277 +1,18 @@ -Class testing - - //Class.noConflict() - console.groupCollapsed("O - noConflict"); - let Oscar; - - console.dir(Class); - console.assert(Object.getOwnPropertyDescriptor(Class, "noConflict").enumerable === false, Object.getOwnPropertyDescriptor(Class, "noConflict").enumerable); - Oscar = Class.noConflict(); - console.assert(Class === void 0, Class); - console.dir(Oscar); - console.assert(Object.getOwnPropertyDescriptor(Oscar, "noConflict") === void 0, Object.getOwnPropertyDescriptor(Oscar, "noConflict")); - console.assert(Oscar.noConflict === void 0, Oscar.noConflict); - console.groupEnd(); + + -

See console for assertions.

- + + + diff --git a/test/Privates using symbols.htm b/test/Privates using symbols.htm index 28e5910..f551c66 100644 --- a/test/Privates using symbols.htm +++ b/test/Privates using symbols.htm @@ -1,69 +1,18 @@ -Class testing - + + + -

See console for assertions.

- + + + diff --git a/test/examples.htm b/test/examples.htm index 2a14339..e998876 100644 --- a/test/examples.htm +++ b/test/examples.htm @@ -1,69 +1,18 @@ -Class testing - + + + -

See console for assertions.

- + + + From 338d920af8181c12bd7a8f9c8cffa6620e8a9c25 Mon Sep 17 00:00:00 2001 From: Andy Harrison Date: Thu, 21 May 2020 15:38:39 -0400 Subject: [PATCH 2/6] update README.md --- README.md | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 3577553..787c1fb 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # JavaScript Class Implementation -This no longer works in IE 11. - This implementation allows for classes to be given protected access to items in a super-class. +This is a JavaScript module. The examples assume you are importing it using the name `Class`. + --- ## Creating subclasses @@ -131,12 +131,3 @@ let c = new Cuber(5); c.cube(); //125 ``` - - ---- - -## Avoid conflicts between scripts - -**Class.noConflict()** - -Restores `Class` to what it was before this script replaced it. The return value is this implementation of Class, so it can be assigned to another variable. From 09132c6f100d1faa168e22ff11c84a6ab00cff9f Mon Sep 17 00:00:00 2001 From: Andy Harrison Date: Thu, 21 May 2020 16:45:25 -0400 Subject: [PATCH 3/6] update README.md --- README.md | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 787c1fb..07bb59e 100644 --- a/README.md +++ b/README.md @@ -2,34 +2,30 @@ This implementation allows for classes to be given protected access to items in a super-class. -This is a JavaScript module. The examples assume you are importing it using the name `Class`. +This is a JavaScript module. --- ## Creating subclasses -***Class*.extend([*options*])** +***Class*.extend([*options*])** Creates a new class that inherits from the parent class. Parameters: -- *options* {object} +- ***options*** {object} This can include any of the following: - - className {string} - Used as `.name` for the class constructor and in `.toString()` for instances of the class. If not specified, it will be the same as the parent class. + - **className** {string} + Used for the `name` property of the class constructor, and in the `toString` method for instances of the class. If not specified, it will be the same as the parent class. - - constructorFn {function} - Initializes new instances of the class. A 'Super' function is passed as the first argument; see below. + - **constructorFn** {function} + Initializes new instances of the class.

+ ***options*.constructorFn(*Super*[, *arg1*[, ...]])**

+ *Super* is a function to be used inside `constructorFn` to initialize the class using its parent's constructor. Basically, it acts like the `super` keyword in ES6. It should be called as soon as possible inside the constructor, before using the `this` keyword, to ensure that the instance is properly initialized. Once called, it also provides access to protected members (see below). - - returnFn {function} - Returns a value when the constructor is called without using the 'new' keyword. - -### The 'Super' function - -The first argument of the 'constructorFn' option is a function required to instantiate the class using the parent class' constructor. Basically, it acts like the 'super' keyword in ES6. It should be called as soon as possible inside the constructor, before using the 'this' keyword, to ensure that the instance is properly initialized. - -***options*.constructorFn(*Super*[, *arg1*[, ...]])** + - **returnFn** {function} + Returns a value when the constructor is called without using the `new` keyword. #### Example @@ -71,9 +67,9 @@ s.whatAmI(); //I am a rectangle. I am a square. ``` -### Protected members +### Protected members -Additionally, a class can give its descendants protected access to its private variables. Once *Super*() is called within the constructor, the protected properties of its parent class are made available via *Super*.protected. This object will be available to child classes as well; any additions/deletions/overloads of its members that are made here in the constructor will be reflected in the class' descendants. +Additionally, a class can give its descendants protected access to its private variables. Once *Super* is called within the constructor, the protected properties of its parent class are made available via the *Super*.protected object. This object will be available to child classes as well; any additions/deletions/overloads of its members that are made here in the constructor will be reflected in the class' descendants. #### Example From 8781b8865c9015810b3b1caa1e7cc7aecf8e1f71 Mon Sep 17 00:00:00 2001 From: Andy Harrison Date: Thu, 21 May 2020 17:00:52 -0400 Subject: [PATCH 4/6] udpate README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07bb59e..b5a1e24 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,9 @@ This can include any of the following: - **constructorFn** {function} Initializes new instances of the class.

- ***options*.constructorFn(*Super*[, *arg1*[, ...]])**

- *Super* is a function to be used inside `constructorFn` to initialize the class using its parent's constructor. Basically, it acts like the `super` keyword in ES6. It should be called as soon as possible inside the constructor, before using the `this` keyword, to ensure that the instance is properly initialized. Once called, it also provides access to protected members (see below). + ***options*.constructorFn(*Super*[, ...])**

+ ***Super*** {function} is to be called from inside `constructorFn` to initialize the class using its parent's constructor. It should be called as soon as possible inside the constructor, before using the `this` keyword, to ensure that the instance is properly initialized.

+ Additionally, *Super* provides access to protected members (see below). - **returnFn** {function} Returns a value when the constructor is called without using the `new` keyword. From 4c31f9ecadde516355b7806eacf2a8502d80a1aa Mon Sep 17 00:00:00 2001 From: Andy Harrison Date: Thu, 21 May 2020 17:07:02 -0400 Subject: [PATCH 5/6] update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b5a1e24..ab6fe02 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ This can include any of the following: - **constructorFn** {function} Initializes new instances of the class.

***options*.constructorFn(*Super*[, ...])**

- ***Super*** {function} is to be called from inside `constructorFn` to initialize the class using its parent's constructor. It should be called as soon as possible inside the constructor, before using the `this` keyword, to ensure that the instance is properly initialized.

- Additionally, *Super* provides access to protected members (see below). + ***Super*** {function} is to be called from inside `constructorFn` to initialize the class, using the class's parent's constructor. It should be called as soon as possible, before using the `this` keyword, to ensure that the instance is properly initialized.

+ Additionally, *Super* provides access to protected members (see below). - **returnFn** {function} Returns a value when the constructor is called without using the `new` keyword. @@ -68,9 +68,9 @@ s.whatAmI(); //I am a rectangle. I am a square. ``` -### Protected members +### Protected members -Additionally, a class can give its descendants protected access to its private variables. Once *Super* is called within the constructor, the protected properties of its parent class are made available via the *Super*.protected object. This object will be available to child classes as well; any additions/deletions/overloads of its members that are made here in the constructor will be reflected in the class' descendants. +A class can give its descendants protected access to its private variables. Once *Super* is called within the constructor, the protected properties of its parent class are made available via ***Super*.protected**. This object will be available to child classes as well; any additions/deletions/overloads of its members that are made here in the constructor will be reflected in the class' descendants. #### Example From 5e2a796fb479a594bd7c23739275212569741953 Mon Sep 17 00:00:00 2001 From: Andy Harrison Date: Thu, 21 May 2020 17:11:34 -0400 Subject: [PATCH 6/6] clean up test files --- test/Class testing.htm | 2 +- {src => test}/Class testing.js | 0 test/Privates using symbols.htm | 2 +- test/examples.htm | 2 +- {src => test}/examples.js | 0 {src => test}/privates.js | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename {src => test}/Class testing.js (100%) rename {src => test}/examples.js (100%) rename {src => test}/privates.js (100%) diff --git a/test/Class testing.htm b/test/Class testing.htm index f04cdb3..0fbe431 100644 --- a/test/Class testing.htm +++ b/test/Class testing.htm @@ -7,7 +7,7 @@ Class testing - + diff --git a/src/Class testing.js b/test/Class testing.js similarity index 100% rename from src/Class testing.js rename to test/Class testing.js diff --git a/test/Privates using symbols.htm b/test/Privates using symbols.htm index f551c66..b9b994e 100644 --- a/test/Privates using symbols.htm +++ b/test/Privates using symbols.htm @@ -7,7 +7,7 @@ Class testing - + diff --git a/test/examples.htm b/test/examples.htm index e998876..5b03b1c 100644 --- a/test/examples.htm +++ b/test/examples.htm @@ -7,7 +7,7 @@ Class testing - + diff --git a/src/examples.js b/test/examples.js similarity index 100% rename from src/examples.js rename to test/examples.js diff --git a/src/privates.js b/test/privates.js similarity index 100% rename from src/privates.js rename to test/privates.js