Paolo Cirasa
Paolo Cirasa

Reputation: 55

How to create a String prototype clone and assign custom function to it

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.

Upvotes: 4

Views: 1652

Answers (1)

Peter Seliger
Peter Seliger

Reputation: 13376

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

Upvotes: 2

Related Questions