James Wilkinson
James Wilkinson

Reputation: 37

Best practices for modifying JavaScript primitive types

I am trying to find a way to call a custom method on a string primitive without modifying the native String object.

String.prototype.addOne = function() { return this.length + 1};

let str = new String('abc')
str.addOne() // 4

The problem with the above code is that new strings will also have the new method, and the above method creation may override other primitive modifications made inside libraries, or other areas of the code base, for example:

String.prototype.addTwo = function() {return  this.addOne + 1}
str.addTwo() // 5

String.prototype.addOne = function() { return this.length + 10};
str.addOne() // 13
str.addTwo() // 14, oops

Are there any solutions that allow me to create a clone of a String Object where i can safely modify its prototype without having to mutate the native global String object?

EDIT:

I am specifically asking if there is a way to do this allows me to add methods to a Primitive String some have suggested extending the String class, adding a method and then returning a String Object. However, this is not the same as a primitive string Object. I would basically like to be able to call custom methods like this :

class CustomString extends String{
  someMethod(){
    return 'someValue'
  }
}

let str = '123'
str.someMethod() // '123'                                             <<< this

let str2 = new CustomString('123')
str2.someMethod() // CustomString { [Iterator] 0: '4', 1:'5'. 2: '6'} <<< not this

This works, but then the original problem persists, in that we have mutated the global String object

String.prototype.someMethod = function() {return 'someValue'}

let str3 = '123'
str3.someMethod() // someValue

Upvotes: 0

Views: 483

Answers (3)

FZs
FZs

Reputation: 18619

Just create a subclass:

class CustomString extends String{
  addOne(){...}
}

And then simply:

new CustomString("asd").addOne()

This class will be very similar to String, but you can create additional properties without conflicting with the primitive strings and the native String class.
Even instanceof will work with it as well:

new CustomString() instanceof String //true

But note that you won't be able to create primitive strings with this constructor:

String(1)       //"1"
CustomString(1) //Error

EDIT: According to the edit to the question:

After a lot of messing around, I've found the way to patch properties to primitives' prototypes without affecting their constructors:

function CustomString(from){
  let string
  if(new.target){ //Whether or not was called as constructor
    string = new String(from)
  }else{
    string = String(from)
  }
  Object.assign(Object.getPrototypeOf(string),CustomString.prototype)
  return string
}
//Add your custom properties like this:
CustomString.prototype.addOne=function(){return this.length+1}

//Without new, creates a primitive string
const primitiveCustomString = CustomString('asd')

//With new, creates a string object
const objectCustomString = new CustomString('asd')

console.log(primitiveCustomString) //"asd"
console.log(objectCustomString)    //String{0:"a",1:"s",2:"d",addOne(){...}}

console.log(primitiveCustomString.addOne()) //4
console.log(objectCustomString.addOne())    //4

//You can even concatenate it together with normal strings, the custom properties still remain
console.log(("asd" + primitiveCustomString + "asd").addOne()) //10
console.log(("asd" + objectCustomString    + "asd").addOne()) //10

Upvotes: 0

Holli
Holli

Reputation: 5072

You can write a factory-function.

function addoneable_string(s) {
  let ns = new String(s);
  ns.addOne = function() { return this.length + 1};
  return ns;
}

let s = addoneable_string("1");
console.log( s.addOne() );

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370819

Sounds like you might want to make a class that extends String:

(() => {
  // In one module:
  class SpecialString extends String {
    addOne() {
      return this.length + 1
    }
  }

  const str = new SpecialString('abc');
  console.log(str.addOne()) // 4
})();

(() => {
  // In another module:
  class SpecialString2 extends String {
    addTwo() {
      return  this.addOne() + 1
    }
    addOne() {
      return this.length + 10
    }
  }
  const str = new SpecialString2('abc');
  console.log(str.addTwo());

  console.log(str.addOne());
  console.log(str.addTwo());
})();

Upvotes: 1

Related Questions