ECMAScript-Muster (Pattern)

Link zur Norm ECMAScript-262 (pdf 2.5MB) Typische ECMAScript-Code-Muster sind: Constructor Pattern, Module Pattern, Revealing Module Pattern, Singleton Pattern, Observer Pattern, Mediator Pattern, Prototype Pattern, Command Pattern, Facade Pattern, Factory Pattern, Mixin Pattern, Decorator Pattern, Flyweight Pattern.

Typen erweitern (Prototype)

In ECMAScript fehlt eine Methode str.trim(), die alle Leerzeichen am Stringanfang und am Stringende entfernt. Hierzu können den bereits verfügbaren (eingebauten) String-Funktionen eine weitere hinzu gefügt werden, etwa:

String.prototype.trim = function () {
  return this.replace(/^\s+|\s+$/g, "");
};
document.write(
 " hallo ".length + ",  " +
 " hallo ".trim().length
);

reverse invertiert die Zeichenfolge:

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

alert( '✡123456789'.reverse() ); 

Implizite Deklaration werden oft als Prototyp bezeichnet. Mit der folgenden Hilfsfunktion Function.prototype.method kann das Schreiben von neuen, "quasi eingebauten" Funktionen vereinfacht werden.

Code Snippet
  1. <script type="text/javascript">//<![CDATA[
  2.  
  3. Function.prototype.method=function (name, func) {
  4.   if(!this.prototype[name]){
  5.     this.prototype[name] = func;
  6.   }
  7. }
  8.  
  9. String.method('trim',function(s) {
  10.    return this.replace(/^\s+|\s+$/g,"");
  11. });
  12.  
  13. var s = "<pre>0123456";
  14. var s1 = "    0123   ";
  15. s = s + "<br />" + s1.trim();
  16.  
  17. window.onload = function() {
  18.    document.write(s+"</pre>");
  19. }
  20. //]]>
  21. </script>
Funktionsmuster (ECMAScript Pattern: Public)
function Constructor(...) { 
 this.membername = value;
}
Constructor.prototype.membername = value;
Funktionsmuster (ECMAScript Pattern: Prototype)

Beispiel 1: Der eingebaute Konstruktor String-Konstruktor kann zu den vorhandenen Stringfunktionen kann um die Funktion trim erweitert werden:

Beispiel: String-Konstruktor und RexExpr
a) mit RexExpr programmiert

String.prototype.trim = function () {
 return this.replace(/^\s+|\s+$/g,"");
};

b) konservativ programmiert

function trim(s) {if (!s || s=="") return "";
  while ((s.charAt(0)==' ') 
  ||(s.charAt(0)=='\n')||(s.charAt(0,1)=='\r'))
     s=s.substring(1,s.length);
  while ((s.charAt(s.length-1)==' ') 
  ||(s.charAt(s.length-1)=='\n')
  ||(s.charAt(s.length-1)=='\r'))s=s.substring(0,s.length-1);
  return s;
}

Beispiel 2: Jetzt soll ein möglicher "Nachbau" der C-Funktion sprintf (variable Anzahl und unterschiedliche Parametern-Typen) angedeutet werden.

var fs = "%s %d"; // Formatstring
var out = fs.sprintf("Text und ", 123) 

Als Beispiel erfordert der Format-String
"hex: %x, hex: %4x, dez: %d, dez: %4d" 4 Parameter. Der eingebaute Konstruktor String-Konstruktor kann (zu den vorhandenen Stringfunktionen) um die Funktion sprintf erweitert werden:

String.prototype.sprintf = function sprintf() 
{
 var p = new String(this);
 var args = String.prototype.sprintf.arguments;
 if(args.length < 2) return p;
 var p_ = p.split("%");
 var n,bl, blank = "000000000000";
 for(var i=0; i < args.length; i++) { 
   var x = args[i]; 
   switch(typeof x){
   case 'string': 
    if(/^\d*?s/.test(p_[i+1])) { 
      n = parseInt(p_[i+1]);
      bl = ""; if(n>x.length)bl=blank.substr(0,n-x.length);
      p = p.replace(/(^[\s\S]*?)%\d*?s/,"$1"+bl+x);
    } break; 
   case 'number':
    if(/^\d*?d/.test(p_[i+1])) { 
      n = parseInt(p_[i+1]); x = ""+x;
      bl = ""; if(n>x.length)bl=blank.substr(0,n-x.length);
      p = p.replace(/(^[\s\S]*?)%\d*?d/,"$1"+bl+x);
    } 
    if(/^\d*?x/.test(p_[i+1])) { 
      n = parseInt(p_[i+1]); x = x.toString(16);
      bl = ""; if(n>x.length)bl=blank.substr(0,n-x.length);
      p = p.replace(/(^[\s\S]*?)%\d*?x/,"$1"+bl+x);
    } 
    break;  
    // demnaechst ... fuer float  f ... var k = x.toFixed(2);
    default: // boolean,function,object,undefined
    break;
   }
  } return p;
}
Funktionsmuster (ECMAScript Pattern: Private)
function Constructor(...) { 
  var that       = this;
  var membername = value;
  
  function membername(...) {...}
}

Die Schreibweise
   function membername(...) {...}
ist eine Kurzschreibweise für
   var membername = function membername(...) {...};

Funktionsmuster (ECMAScript Pattern: Privileged)
function Constructor(...) { 
  this.membername = function (...) {...};
}
Beispiele: Private, Public members, base class, inherits Object

Das folgende Beispiel zeigt das Prinzip von privaten Variablen, öffentlichen Variablen, privaten Methoden, öffentlichen Methoden, get-Methoden, set-Methoden.

//static object (it's like Math)
var StaticObject = function() {
  /* Private members */
  var privateVar = "Private";
  
  function privateFunc(){ return "Private"; };
    
  /* Public members */
  return {
    publicVar: "Public",
    getPrivateVar: function(){ return privateVar; },
    setPrivateVar: function(v){ privateVar = v; }
  };
}();

Hier kommt unvollständiger Quelltext, der das Prinzip einer Basis-Klasse (und abgeleiteter Objekte) aufzeigt:

//base class; inherits Object
function BaseClass(arg1, arg2){
  /* Private members; only accessible within this class */ 
  var privateVar = "Private";
  function privateFunc(){ return "Private"; };

  /* Protected members; only accessible within this class and derived classes */
  //Not supported in JavaScript

  /* Public members */  //override inherited toString() method
  this.toString = function(){ return "[object BaseClass]"; };  
  this.publicVar = "Public";
  this.publicFunc = function(){ return "Public"; };
}
  
//static method of BaseClass class; 
// this is not inherited (it's like String.fromCharCode())
BaseClass.staticMethod = function(){ return "Static"; };  
  
//subclass; inherits BaseClass
function SubClass(arg1, arg2){  
  BaseClass.apply(this, [arg1, arg2]); //construct this object using the BaseClass constructor 
    
  /* Private and Public members specific to SubClass */
  //override inherited toString() method
  this.toString = function(){ return "[object SubClass]"; };  
  //...
}

//put SubClass into the prototype chain as a subclass of BaseClass
SubClass.prototype = new BaseClass(); 
Beispiele: Vergleich zwischen ECMAScript und Java

Dieses Beispiel findet sich hier (Vergleich zwischen JavaScript und Java, Creating the Hierarchy).

beispiel-vergleich-js-java
JavaScript Java
function Employee () {
  this.name = "";
  this.dept = "general";
}
public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this.name = "";
      this.dept = "general";
   }
}
JavaScript Java
function Manager () {
  this.reports = [];
}
Manager.prototype = new Employee;

function WorkerBee () {
  this.projects = [];
}
WorkerBee.prototype = new Employee;
public class Manager extends Employee {
   public Employee[] reports;
   public Manager () {
      this.reports = new Employee[0];
   }
}

public class WorkerBee extends Employee {
   public String[] projects;
   public WorkerBee () {
      this.projects = new String[0];
   }
}
JavaScript Java
function SalesPerson () {
   this.dept = "sales";
   this.quota = 100;
}
SalesPerson.prototype = new WorkerBee;

function Engineer () {
   this.dept = "engineering";
   this.machine = "";
}
Engineer.prototype = new WorkerBee;
public class SalesPerson extends WorkerBee {
   public double quota;
   public SalesPerson () {
      this.dept = "sales";
      this.quota = 100.0;
   }
}

public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      this.dept = "engineering";
      this.machine = "";
   }
}
Beispiel: "Class-factory" inspired by base2 and Prototype

In "http://ejohn.org/blog/simple-javascript-inheritance/" wird beschrieben, wie var MYCLASS = Class.extend({ ... }); als "Class-factory" inspired by base2 and Prototype angelegt wird. Hier ein Testbeispiel:

(function(){
  var initializing = false, 
  fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
  // The base Class implementation (does nothing)
  this.Class = function(){};
  // Create a new Class that inherits from this class
  Class.extend = function(prop) {
    var _super = this.prototype;
    // Instantiate a base class (but only create the instance,
    // don't run the init constructor)
    initializing = true;
    var prototype = new this();
    initializing = false;
    // Copy the properties over onto the new prototype
    for (var name in prop) {
      // Check if we're overwriting an existing function
      prototype[name] = typeof prop[name] == "function" && 
        typeof _super[name] == "function" && fnTest.test(prop[name]) ?
        (function(name, fn){
          return function() {
            var tmp = this._super;
            // Add a new ._super() method that is the same method
            // but on the super-class
            this._super = _super[name];
            // The method only need to be bound temporarily, so we
            // remove it when we're done executing
            var ret = fn.apply(this, arguments);        
            this._super = tmp;
            return ret;
          };
        })(name, prop[name]) :
        prop[name];
    }
    // The dummy class constructor
    function Class() {
      // All construction is actually done in the init method
      if ( !initializing && this.init ) 
        this.init.apply(this, arguments);
    }
    // Populate our constructed prototype object
    Class.prototype = prototype;
    // Enforce the constructor to be what we expect
    Class.constructor = Class;
    // And make this class extendable
    Class.extend = arguments.callee;
    return Class;
  };
})();

Hier ein Test-Beispiel:

var A = Class.extend({
  init: function(v1,v2){this.v1=v1; this.v2=v2;},
  val: "A-val",
  getVal: function() { return this.val; }
});

var B = A.extend({ 
  init: function(v1){ this.v1=v1; },
  val: "B-val"
});

var C = B.extend({ 
  init: function(v1){ 
    this.v1=v1;
    this._super( false );
  },
  val: "C-val",
  getVal: function(){ 
    return this._super(); 
  }
});

var a = new A("A-new");
var b = new B("B-new");
var c = new C("C-new");

var s = "<pre> a.v1       = " + a.v1 
 +" a.val      = " + a.val 
 +" a.v2       = " + a.v2  
 +" a.getVal() = " + a.getVal()
 +""
 +" b.v1       = " + b.v1  
 +" b.val      = " + b.val 
 +" b.getVal() = " + b.getVal()  
 +""
 +" c.v1       = " + c.v1  
 +" c.val      = " + c.val 
 +" c.getVal() = " + c.getVal()
 +""
 +" (a instanceof A) = " + (a instanceof A)
 +" (b instanceof A) = " + (b instanceof A)
 +" (c instanceof A) = " + (c instanceof A)
 +"</pre>";
document.write(s);
</script>

Test-Beispiel-Ausgabe: