JavaScript Snippet – String.prototype.before()

WARNING:
Extending native prototypes is frowned upon by many JS engineers but can be helpful as long as the extensions are properly documented in the codebase.
SCRIPTER’S DISCRETION IS ADVISED. 😆

Even though its NOT encouraged to extend native prototypes, at times you may find that doing so is pretty useful. One extension that you may find useful is String.prototype.before(...) which can be used to return the substring before a specified target. Here is the definition:

First I think its worth mentioning that depending on your preference you could choose to rename this function as String.prototype.leftOf(...) to clearly identify what it does. This function takes at least a target to find in the string which is either a string or a regexp. You may also optionally pass in a second argument which indicates the occurrence of the target to key off of. For example, if you wanted to extract the substring before the second comma in "one, two, three, and four" you could do something like the following:

var str = "one, two, three, and four";
var firstTwo = str.before(',', 2);

Of course, that is just one simple example of what you can do with this prototype extension. Check out some tests that exemplify how to use this helpful function:

Most likely if you like this function you will also want to check out the String.prototype.after(...) post. Enjoy using this helpful utility function. 😎

JavaScript – Simplest Inheritance Before ES5

One of the great things about reading through Douglas Crockford’s site is that you get to learn about how certain things like prototypal inheritance was implemented prior to ECMAScript 5. Since then Object.create() made its way into JavaScript and has been available in most modern browsers for a while. If you haven’t read through Crockford’s site or seen how prototypal inheritance was done before, here is a quick overview:

You can click here to test out the code yourself. As you may have noticed, the key to prototypal inheritance is as follows (please ignore the names of these prototypes disguising themselves as classes :smile:):

SubClass.prototype = new Class();

Unfortunately, at times you may not be able to safely execute the constructor of the prototype which is being inherited (which in this case would be Class). In order to get around it, you can create a surrogate function whose prototype will become that of the prototype to be inherited:

function SurrogateClass(){}
SurrogateClass.prototype = Class.prototype;
SubClass.prototype = new SurrogateClass();

Unfortunately, it isn’t the most memorable sequence so let’s turn this into a simple function:

function inherit(Class, SubClass) {
    function SurrogateClass(){}
    SurrogateClass.prototype = Class.prototype;
    SubClass.prototype = new SurrogateClass();
}

Finally, let’s minify it:

function inherit(C,S,p){function O(){}O[p='prototype']=C[p];S[p]=new O}

OK, so this is minification to an extreme since I placed p in the function’s signature. I also went a bit far by sketchily defining p in the left-hand side of the assignment of the prototype to the surrogate function. Even though the above code seems to work, just to be safe, here is a slightly longer, more trustworthy definition:

In the end, both of these definitions are extremely small so I’d say even prior to ES5, prototypal inheritance wasn’t as difficult as one might have thought. 😎

JavaScript – Creating Classes

As many people know, JavaScript doesn’t really have classes but you can mimic some of their behavior with the prototypal setup available in JavaScript. Still, a lot of times it is just easier when you have a function that does most of the work for you. For that reason, I wrote the classify:

/**
 * @license JS Classify (v1.1) - By Chris West - MIT License
 */
/**
 * Creates a class using the specified constructor and options.
 * @param  {!Function} constructor  The constructor function.
 * @param  {{ privateKey:string, setters:Array, getters:Array<=string>, prototype:Object, properties:Object, superClass:Function }} options
 *     Object containing the class options.  The privateKey is the name of the
 *     privateKey that will be assigned to every instance.  The setters array
 *     contains names of private data members for which setters will be setup.
 *     The getters array contains names of private data members for which
 *     getters will be setup.  The prototype object will contain the prototype
 *     values that will be attached to the class' prototype.  The superClass
 *     is the function that will act as the class' super class and all
 *     prototypes will be inherited from it.
 * @return {!Function}  The newly created class.
 */
var classify = (function(fakeClass) {
  var hasOwnProperty = {}.hasOwnProperty;
 
  function camelCase(str, delim) {
    delim = delim || ' ';
    var pos;
    while ((pos = str.indexOf(delim)) + 1) {
      str = str.slice(0, pos) + str.charAt(pos + 1).toUpperCase() + str.slice(pos + 2);
    }
    return str;
  }
 
  /**
   * Setup inheritance.
   * @param {!Function} baseClass  The base class from which the subclass
   *     inherits its prototypal values.
   * @param {!Function} subClass  Class which inherits from the base class.
   * @return {!Function}  The updated subclass.
   */
  function inherit(baseClass, subClass) {
    fakeClass.prototype = baseClass.prototype;
    var prototype = subClass.prototype = new fakeClass();
    prototype.superClass = baseClass;
    return prototype.constructor = subClass;
  }
 
  // Return classify function.
  return function(constructor, options) {
    var outerPrivateData;
 
    var privateKey = options.privateKey || '_';
 
    var realConstructor = function() {
      this[privateKey] = properties && hasOwnProperty.call(properties, privateKey)
        ? properties[privateKey]
        : {};
      if (superClass) {
        this.superClass = superClass;
      }
      try {
        return constructor.apply(this, arguments);
      }
      finally {
        if (superClass) {
          delete this.superClass;
        }
      }
    };
 
    // If the super-class is defined use it.
    var superClass = options.superClass;
    if (superClass) {
      realConstructor = inherit(superClass, realConstructor);
    }
 
    // Add class level properties.
    var properties = options.properties;
    if (properties) {
      for (var key in properties) {
        realConstructor[key] = properties[key];
      }
    }
 
    var realPrototype = realConstructor.prototype;
    var myPrototype = options.prototype || {};
 
    // Add getters.
    var getters = options.getters || [];
    for (var i = 0, len = getters.length; i < len; i++) {
      (function(name) {
        myPrototype[camelCase('get_' + name, '_')] = function() {
          return this[privateKey][name];
        };
      })(getters[i]);
    }
 
    // Add setters.
    var setters = options.setters || [];
    for (var i = 0, len = setters.length; i < len; i++) {
      (function(name) {
        myPrototype[camelCase('set_' + name, '_')] = function(newValue) {
          var privateData = this[privateKey];
          var oldValue = privateData[name];
          privateData[name] = newValue;
          return oldValue;
        };
      })(setters[i]);
    }
 
    // Add all prototypal values.
    for (var key in myPrototype) {
      realPrototype[key] = myPrototype[key];
    }
 
    return realConstructor;
  }
})(function(){});

How To Use classify()

The first parameter that you pass should be the constructor. The second parameter will be an object containing properties representing any options you want to add to the class:

  • privateKey - string
    Defaults to "_". The property name for the object which will house all of the private data for each class instance.
  • getters - Array
    An array of strings indicating the getters that should be automatically setup to retrieve the private data members with the same name. These names will be camel-cased based on underscore characters.
  • setters - Array
    An array of strings indicating the setters that should be automatically setup to set the private data members with the same name. These names will be camel-cased based on underscore characters. All setters return the previous value.
  • properties - Object
    An object containing all of the properties that will be added to the class object.
  • prototype - Object
    An object containing all of the values that should be added to the prototype of the class.
  • superClass - Function
    The super-class from which this new class will inherit. This will overwrite the superClass property of the class' prototype.

Example Classes

The following exemplifies how easy it is to create classes with classify():

// Create a simple base class.
Being = classify(
  function (species) {
    this._.species = species;
  },
  { getters: ['species'] }
);

// Create a more complex sub-class.
Human = classify(
  function (firstName, lastName) {
    this.superClass.call(this, 'Human');
    this._.firstName = firstName;
    this._.lastName = lastName;
  },
  {
    getters: [ 'firstName', 'lastName' ],
    setters: [ 'firstName' ],
    prototype: {
      toString: function() {
        return this.getFullName() + ' (' + this._.species + ')';
      },
      getFullName: function() {
        return this._.firstName + ' ' + this._.lastName;
      }
    },
    superClass: Being
  }
);

The first class that I created above is a Being class which just has one private member: species. The Being.prototype.getSpecies() function is defined for this class as well. The second class is the Human class which is a sub-class of the Being class. The species defaults to "human" while two more private members are included: firstName and lastName. There are getters for all of the private members but there is only one setter which is for the firstName. Additionally the Human.prototype.toString() and Human.prototype.getFullName() functions have been defined to provide additional features to the class.

Try Me!

The above example shows (as long as you are using a modern browser) how these newly created functions work. There is more you can do with this of course so if you want go ahead and play with the above example or copy the classify code and create your own classes. Happy coding! 😎