My recent post titled Fun with JavaScript Numbers and Prototype got a lot more attention than I thought it would. So, I considered it cruel not to give String objects equal treatment.

More than a few reader's of my previous post on numbers pointed out that some of the functions I created were little more than semantically different from built-in functions. I agree, and that's okay. For me, adding functions to a built-in object's prototype doesn't serve to create cutting-edge string manipulation routines, but rather to reduce the complexity of my code, by encapsulating commonly used algorithms into an easy-to-call function.

Using a built-in object's prototype not only allows us to use a more intuitive syntax:

newString = myString.awesomeFunction();

But also helps us to maintain functional cohesion within our objects. Meaning, we don't need to scramble a bunch of unrelated functions together into a giant utility class, i.e.

newString = util.awesomeFunction(myString);

Not only does this lengthen our code, but it's disorganized. A function that does string manipulation has no business in a class (object) that calculates the perimeter of a rectangle.

Furthermore, it wouldn't make sense to create another class (object) to house string manipulation methods when we already have the perfect one built in – the String object.

So, let's have some fun with String's prototype!

 

Occurrences in a string

Sure, this one's easy. Just perform a RegEx match. But, to someone else reading your code, it might not be immediately apparent what you're trying to do.

Drop that into a function on String's prototype and you've got more readable code at the very least:

String.prototype.occurrences = function(substring) {
  var occRegEx = new RegExp(substring, 'g');
  return this.match(occRegEx).length;
}

> myString.occurrences('is');
  2

Okay, that was not awesome. Hey, I'm just getting started!

 

Logging

What's the scariest thing about JavaScript? It runs in the user's browser, you have no eyes and ears! One of my favorite post-launch debugging strategies on the server side is having an email sent to me any time there's an error on the site. Easily accomplished with server-side code, right. Not so easy on the client side. But, not impossible. I'm admittedly going to break the rule of cohesion here. A logging function really doesn't belong in company of string manipulation functions. This concept is just too cool to pass up though.

Wouldn't it be great to just drop in a statement like:

myString.serverLog();

and have it append to a server-side error log, along with a few context identifiers? Let's make it happen:

String.prototype.serverLog = function() {
  /* you can find implementations of an xmlHttpPost AJAX call anywhere and everywhere on the web
     I won't waste your time recreating it here. */
  var logString = this + ' | ' + (new Date()).toString() + ' | ' + location.href;
  xmlHttpPost('/log.php?s=' + logString);
}

Put that in the context of a try/catch block and it becomes a lot more useful:

    try {
     x = y;
    }
    catch(e) {
     if(typeof arguments !== 'undefined') {
       console.log(arguments.callee.toString().match(/function\s+([^\s\(]+)/)[0])
     }
     console.log(e.toString());
    }

try {
  x = something;
}
catch(e) {
  var soThisHappened = '';
  soThisHappened += e.toString();
  if(typeof arguments !== 'undefined') {
    // if we're inside a function, snag the function name
    soThisHappened += arguments.callee.toString().match(/function\s+([^\s\(]+)/)[0]);
  }
  soThisHappened.serverLog();
}

Markup

How many times have you written or seen JavaScript like this, myself included:

if(document.form1.name.value === '') {
  document.write('<b>Oh no, you forgot your name?</b>');
}

All those gurus are always telling you to keep JavaScript out of your HTML, so shouldn't the reverse apply? But, I really want to just spit out a boldface message. Here's a cleaner way:

String.prototype.bold = function() {
  return '<b>' + this + '</b>';
}

document.write('Oh no, you forgot your name?'.bold());

 

Language

The previous couple functions walk the line of cohesion and usefulness, maybe even step over it. So, let's get back to the core of what a string is: typically a word or group of words in a given language. The String prototype is the perfect home for language functions.

String.prototype.isQuestion = function() {
   var questionIdentifiers = ['when', 'where', 'why', 'what', 'who', 'how', 'can'];

   // does the string contain a question mark?
   if(this.indexOf('?') !== -1) {
      return true;
   }
  
   // search keyword may indicate a question without explicitly specifying the question mark
   for(var i = 0; i < questionIdentifiers.length; i++) {
      if(this.indexOf(questionIdentifiers[i]) !== -1) {
         return true;
      }
   }

   return false;
}

We could use our new function to refine a user's search by conditionally returning results more likely to answer questions, than to provide generic information:

if(searchPhrase.isQuestion()){
  // favor search results in the knowledge base
}
else {
  // favor search results in the product store
}

 

Is it a Word?

There's an unfair bias towards the numeric in computer science. Of course, it all stems from the fact that the CPU speaks numeric, while we humans speak string. You can follow a gradual trend towards natural language-based syntax in popular development languages such as ColdFusion and Python. But, there really isn't much happening in terms of native support for natural language processing. I'm a little surprised by this, because I feel as if half of the applications that I develop require at least a very primitive form of natural language processing (search, form validation, speech recognition, etc.)

Many languages offer isNumeric(), isDate(), or isArray() functions. But, how about isWord(), isVerb(), isCommand()?

isCommand() could be used in searching algorithms (similar to isQuestion()) above. If a command is detected, the user might know more or less what they are looking for, otherwise, they may need a deeper level of assistance.

isWord() could be used in form validation. If a user submits a field, validate that it's actually a word (or group of words). The definition of a word is subjective, but we'll make a few assumptions:

  1. A word does not contain spaces
  2. A word can contain only letters or the hyphen
  3. A word is less than 25 characters in length

String.prototype.isWord = function() {
  if(this.length > 25) {
    return false;
  }
  return /^[A-Za-z\-]*$/.test(this);
}

A user submits a form which has fields for first and last name, so you validate against our new isWord() function:

if(!firstname.isWord()) {
  alert('Is that really your name?');
}

Well, maybe it isn't. Many folks out there have a two-word first name. So, let's expand our String prototype to help out here:

String.prototype.words = function() {
  return this.split(' ');
}

Now we are armed with the basics necessary to properly validate a first name:

for each(item in firstname.words()) {
  if(!item.isWord()) {
    alert('Is that really your first name?');
    break;
  }
}

 

Tags:

3 Comments

  1. I feel I need to state my case for adding to a built-in object's prototype a little more clearly. I'm not suggesting that you modify any of the object's existing functions. Other libraries, and more likely, your own code, expect certain behavior that you should not interfere with. But, in adding a function to a built-in object's prototype, you in no way affect the expected behavior of the object.

    My DVD player has a standard size "slot" for a DVD. DVD manufacturers expect this, so I would never want to modify it. But, adding the ability for my DVD player to also stream Netflix is okay. A DVD doesn't care that the player also supports streaming, and the ability to stream in no way interferes with the operation of the DVD playing feature.

  2. String.prototype.isVowel = function() {
       if (['a','e','i','o','u'].indexOf(this) !== -1)
            return true;
       else
            return false;
    }

Leave a Reply to admin Cancel reply

Your email address will not be published. Required fields are marked *