4

I have a custom library with a bunch of functions that I load everytime I work with Javascript. The problem is that they work in global namespace and sometimes they get into conflict with other loaded libraries (ex. jquery and npm modules). My idea is to create custom prototypes and define each variable with that prototype. Something like this:

let obj = new MyObj ({name: 'John', lastname: 'Doe'});

I already created custom prototypes with objects and arrays and they work fine (I suppose because they work already as objects) but I got stuck with primitive elements like String and Number.

This is what I wrote till now, trying to create a custom String Prototype:

const myStr = function (string) {
    if (!(this instanceof myStr)) { return new myStr(); }
    return this; //or return string?
}
myStr.prototype = Object.create(String.prototype); 
myStr.prototype.constructor = myStr; //or = String?

//custom function assigned to Mystr prototype

Object.defineProperties(myStr.prototype, {
    upp: {
        value: function() {
            return this.toString().toUpperCase();
        }, enumerable: false, writable: false
    }
    //...other custom functions
});

let newString = new myStr('this is my own string');

console.log (newString); 
//it returns 'myStr {}' and not the string passed in function :-(

Is there a way to clone a native Prototype, add to it custom functions, and finally getting an element that behaves perfectly in the same way of original native one?

Custom prototype elements could exploit native functions plus custom ones. This should prevent global prototype pollution.

Thank you!

Edit

In the end I found a solution that, for what I know, is the best compromise to have custom functions inside primitive JS Prototypes, and not to get into conflicts with other modules or libraries. Basically I add a prefix 'my' (but it can be whatever) before every custom function. It's not the 'most beautiful thing in the world', but I can't figure out how to do it differently.

This is the code for String:

Object.defineProperties (String.prototype, {
    my: {
        get: function() {
            //it stores string value that is calling functions
            const thisString = this; 
            
            const myStringFunctions = {
                //use get if you want to return a value without parenthesis ()
                get len () {
                    return thisString.length;
                },
                upp: function () {
                    return thisString.toString().toUpperCase();
                }
                //... other custom functions
            };
            
            return myStringFunctions;
        }
    }
});

The I use

console.log ( 'John Doe'.my.upp() ); //JOHN DOE
console.log ( 'John Doe'.my.len ); //8

Of course I can chain functions together, but I have to repeat the 'custom key'

console.log ( 'John Doe'.my.upp().my.len ); //8

This method can be applied to every Js Prototype.

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • Some related ideas in the answers to [this question](https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice) – Cat Mar 19 '21 at 11:41

1 Answers1

2

A less painful way of achieving this task was to take advantage of the class syntax and all the initialization and binding which one gets for free especially in case one does practice sub-typing of a native class/type like String ...

class MyStr extends String {
  upp () {
    return this.toString().toUpperCase();
  }
}
const newString = new MyStr('this is my own string');

console.log(newString.upp());
console.log(newString+"");

console.log(newString.toString());
console.log(newString.valueOf());
.as-console-wrapper { min-height: 100%!important; top: 0; }

Edit

The provided example code seems to work with strings but shows a strange behavior with arrays and objects. What am I doing wrong here ...

Everything works as intended. One just needs to be always aware that especially for string and number types one does not deal anymore with primitive values but with sub-typed String and Number objects. Thus one of cause inherits the behavior from the latter.

As for the OP's sub-typed number example the next code snippet provides a parallel logging of the native and the custom number type ...

class MyNum extends Number {}

let customNumber = new MyNum(5);
let nativeNumber = new Number(5);

console.log('test for `valueOf` ... (customNumber + 0) ...', (customNumber + 0));
console.log('test for `valueOf` ... (nativeNumber + 0) ...', (nativeNumber + 0));
console.log('\n');

console.log('test for `toString` ... (customNumber + "0") ...', (customNumber + "0"));
console.log('test for `toString` ... (nativeNumber + "0") ...', (nativeNumber + "0"));
console.log('\n');

console.log('customNumber ...', customNumber, ' // MyNum {5} with straight console logging');
console.log('nativeNumber ...', nativeNumber, ' // Number {5} with straight console logging');
console.log('\n');

console.log('customNumber.valueOf() ...', customNumber.valueOf());
console.log('nativeNumber.valueOf() ...', nativeNumber.valueOf());
console.log('\n');

console.log('(customNumber.valueOf() === customNumber) ?..', (customNumber.valueOf() === customNumber));
console.log('(nativeNumber.valueOf() === nativeNumber) ?..', (nativeNumber.valueOf() === nativeNumber));
console.log('\n');

console.log('(customNumber.valueOf() === (customNumber + 0)) ?..', (customNumber.valueOf() === (customNumber + 0)));
console.log('(nativeNumber.valueOf() === (nativeNumber + 0)) ?..', (nativeNumber.valueOf() === (nativeNumber + 0)));
console.log('\n');

console.log('(customNumber.valueOf() === (nativeNumber + 0)) ?..', (customNumber.valueOf() === (nativeNumber + 0)));
console.log('(nativeNumber.valueOf() === (customNumber + 0)) ?..', (nativeNumber.valueOf() === (customNumber + 0)));
.as-console-wrapper { min-height: 100%!important; top: 0; }

As for JavaScript objects, like the Array type and sub-typed versions there is an example too, which logs both types in order to prove that there is nothing wrong with the behavior of either type ...

class MyArr extends Array {
  first () {
    return this[0];
  }
  last () {
    return this[this.length - 1];
  }
}
let customArray = new MyArr(10, 20, 30, 40);
let nativeArray = new Array(10, 20, 30, 40);

let customArray2 = new MyArr([40, 30, 20, 10]);
let nativeArray2 = new Array([40, 30, 20, 10]);

console.log('test for `valueOf` ... customArray ...', customArray);
console.log('test for `valueOf` ... nativeArray ...', nativeArray);
console.log('\n');
console.log('test for `valueOf` ... customArray2 ...', customArray2);
console.log('test for `valueOf` ... nativeArray2 ...', nativeArray2);
console.log('\n');

console.log('customArray.first() ...', customArray.first());
console.log('customArray.last() ...', customArray.last());
console.log('\n');
console.log('customArray2.first() ...', customArray2.first());
console.log('customArray2.last() ...', customArray2.last());
console.log('\n');

console.log('(customArray + "") ...', (customArray + ""));
console.log('(customArray.toString() === "10,20,30,40") ?..', (customArray.toString() === "10,20,30,40"));
console.log('(customArray.valueOf() === customArray) ?..', (customArray.valueOf() === customArray));
.as-console-wrapper { min-height: 100%!important; top: 0; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • I appreciate a lot your answer, but it seems that in Javascript there's no way to create a full copy of original native type to extend or modify, according to your needs, that responds exactly like the original one, until you go to change the native prototype at a global level, with all the appropriate consequences. Is that right? – Paolo Cirasa Mar 20 '21 at 15:04
  • @PaoloCirasa ... I don't know what you are exactly referring to or try to achieve, but if it was about a return value of one of your sub-type method's, then you might have a look at [`Symbol.species`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species) where you are in control of e.g. returning either your own or the parent constructor's type. – Peter Seliger Mar 20 '21 at 15:27
  • simply I have about 4000 rows of custom functions to make my Javascript code more readable and sugar sintax. For example, I created a function 'has' that behaves like indexOf but without having to verify if the result is > -1, even with strings and arrays. So, you simply type `if ( string,has(char) )...` and not `if (string.IndexOf (char) > -1)` I also created a 'List' type that is not present in Javascript but it's very useful when you work with Lists ecc. I worked for 4 years overriding global types with no problem, but now importing npm modules in node, I get into conflicts. – Paolo Cirasa Mar 20 '21 at 15:51
  • @PaoloCirasa ...In this case you might just stick to your long term approach and provide the prototypal extension to the native types like that... `Object.defineProperty(String.prototype, 'has', { value: hasValueInString });` ... `Object.defineProperty(Array.prototype, 'has', { value: hasItemInArray });` ...btw. are you aware of the standardized prototypal `includes` for both [`String`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes) and [`Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)? – Peter Seliger Mar 20 '21 at 18:08
  • @PaoloCirasa ... there is also [Stack Exchange - Code Review](https://codereview.stackexchange.com/). This might be the better place for discussing an approach which introduces fundamental changes to an existing vast code base. – Peter Seliger Mar 20 '21 at 18:27