четверг, 25 сентября 2014 г.

Простой набор паттернов JavaScript

Паттерны JavaScript

1) Class

function Car (model) {
    this.model = model;
    this.year = 2012;
}

Car.prototype.getInfo = function () {
    return this.model + ' ' + this.year;
};

var myCar = new Car('Ford');
myCar.year = 2010;

console.log(myCar.getInfo());

2) Mixin

var Person = function (firstName , lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.gender = 'male';
};

var Superhero = function (firstName, lastName , powers) {
    // Invoke the superclass constructor on the new object
    // then use .call() to invoke the constructor as a method of
    // the object to be initialized.

    Person.call(this, firstName, lastName);

    // Finally, store their powers, a new array of traits not found in a normal "Person"
    this.powers = powers;
};

// или

function applyMixins(derivedCtor, baseCtors) {
    baseCtors.forEach(function (baseCtor) {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(function (name) {
            derivedCtor.prototype[name] = baseCtor.prototype[name];
        });
    });
}

// Disposable Mixin
var Disposable = (function () {
    function Disposable() {}
    Disposable.prototype.dispose = function () {this.isDisposed = true;};
    return Disposable;
})();

// Activatable Mixin
var Activatable = (function () {
    function Activatable() {}
    Activatable.prototype.activate = function () {this.isActive = true;};
    Activatable.prototype.deactivate = function () {this.isActive = false;};
    return Activatable;
})();

var SmartObject = (function () {
    function SmartObject() {
        var _this = this;
        this.isDisposed = false; // Disposable
        this.isActive = false; // Activatable
        setInterval(function () {return console.log(_this.isActive + " : " + _this.isDisposed);}, 500);
    }
    SmartObject.prototype.interact = function () {this.activate();};
    return SmartObject;
})();

applyMixins(SmartObject, [Disposable, Activatable]);

var smartObj = new SmartObject();

3) Extend

var extend = this.extend || function (childClass, parentClass) {
    for (var key in parentClass) {
        if (parentClass.hasOwnProperty(key)) {
            childClass[key] = parentClass[key];
        }
    }
    function F () {this.constructor = childClass;}
    F.prototype = parentClass.prototype;
    childClass.prototype = new F();
};

var ParentClass = (function () {

    // Constructor
    function ParentClass(message) {
        // Constructor body
        this.greeting = message;
        this.x = 1;
        this.y = 2;
        this.z = 3;
    }

    // Public function
    ParentClass.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };

    return ParentClass;

})();

var ChildClass = (function (parentClass) {

    // Extend
    extend(ChildClass, parentClass);

    // Constructor
    function ChildClass(message) {
        parentClass.apply(this, arguments); // или можно сделать частично parentClass.call(this, message);
        this.greeting = message;
    }

    // Public variable
    ChildClass.prototype.name = 'Parent';

    // Public function
    ChildClass.prototype.greet = function () {
        return 'Hello, ' + this.greeting;
    };

    // Public function
    ChildClass.prototype.hey = function () {
        return 'Hey! ' + privateFunc();
    };

    // Private variable
    var privateVar = 'Hello';

    // Private function
    function privateFunc () {
        return privateVar + ' my friend!';
    }

    // Static variable
    ChildClass.staticVar = 'I am static variable';

    // Static function
    function staticFunc () {
        return 'I am static function';
    }

    return ChildClass;

})(ParentClass);

var SubClass = (function (parentClass) {

    extend(SubClass, parentClass);

    function SubClass(message) {
        parentClass.apply(this, arguments);
        this.greeting = message;
    }

    SubClass.prototype.greet = function () {
        return 'Hello, ' + this.greeting;
    };

    SubClass.prototype.hey = function () {
        return 'Hey!';
    };

    return SubClass;

})(ChildClass);

var parentObject = new ParentClass('world');
var childObject = new ChildClass();
var subObject = new SubClass();

console.log(parentObject.greet());
console.log(childObject.hey());
console.log(subObject.hey());

4) Module

var module = (function($){

    var privateVar = 1
       , publicVar = 2 + privateVar;

    function Boat (name) {
        this.name = name;
        this.year = 2012;
    }

    Boat.prototype.getInfo = function () {
        return this.name + ' ' + this.year;
    };

    function Plane (name) {
        this.name = name;
        this.year = 2012;
    }

    Plane.prototype.getInfo = function () {
        return this.name + ' ' + this.year;
    };

    function insertHTML (tag, HTMLcode) {
        $(tag).html(HTMLcode);
    }

    return {
          publicVar: publicVar
        , Boat: Boat
        , Plane: Plane
        , insertHTML: insertHTML
    };
})(jQuery);

console.log(model.publicVar);

var myBoat = new model.Boat('Ann');
myBoat.year = 2010;

console.log(myBoat.getInfo());

model.insertHTML('div', '<h1>Hello!</h1>');

// Using CommonJS, AMD or browser globals to create a module

(function (root, factory) {
             if (typeof exports === 'object') {factory(exports, require('b')); // CommonJS
    } else if (typeof define === 'function' && define.amd) {define(['exports', 'b'], factory);  // AMD. Register as an anonymous module.
    } else {factory((root.commonJsStrict = {}), root.b); // Browser globals
    }
}(this, function (exports, b) {
    // use b in some fashion.
    // attach properties to the exports object to define
    // the exported module properties.
    exports.action = function () {};
}));

5) Namespace

var myNamespace = myNamespace || {};

myNamespace.someVariable = 1;
myNamespace.someFunction = function () {alert(myNamespace.someVariable);};

// Namespace injection

var myApp = myApp || {};
myApp.utils =  {};

(function () {
  var val = 5;

  this.getValue = function () {return val;};
  this.setValue = function(newVal) {val = newVal;}

  // also introduce a new sub-namespace
  this.tools = {};

}).apply(myApp.utils);

(function () {
    this.diagnose = function(){return "diagnosis";}
}).apply(myApp.utils.tools);

console.log(myApp); // Outputs: 5
console.log(myApp.utils.getValue());

myApp.utils.setValue(25);
console.log(myApp.utils.getValue());

console.log(myApp.utils.tools.diagnose());

6) Singleton

var singleton = (function(){
    var instance;

    function init () {

         var privateVariable = 'I am private variable.';

         function privatMethod () {
             console.log('I am private method.');
         }

         return {
               publicProperty: 'I am public property.'
             , publicMethod: function () {
                 console.log('I am public method.');
               }
         };

    }

    return {
        getInstance: function () {
            if (!instance) {
                instance = init();
            }
            return instance;
        }
    }

})();

var mySingleton1 = singleton.getInstance()
    , mySingleton2 = singleton.getInstance();

console.log(mySingleton1 === mySingleton2); // true

console.log(mySingleton1.publicProperty);
mySingleton1.publicMethod();

7) Observer

function Observer () {
    this.events = [];
}

Observer.prototype.on = function (eventName, callbackFunction) {
    return this.events.push({
          eventName: eventName
        , callbackFunction: callbackFunction
    });
};

Observer.prototype.off = function (eventName) {
    var eventsLength = this.events.length;
    for (;eventsLength--;) {
        if (this.events[eventsLength].eventName === eventName) {
            this.events.splice(eventsLength, 1);
        }
    }
};

Observer.prototype.trigger = function (eventName) {
    var eventsLength = this.events.length;
    for (;eventsLength--;) {
        if (this.events[eventsLength].eventName === eventName) {
            this.events.callbackFunction();
        }
    }
}

Observer.prototype.count = function () {
    return this.events.length;
};

8) Command

var carManager = {

      requestInfo: function (model, id) {
          return "The information for " + model + " with ID " + id + " is foobar";
      }

    , buyVehicle: function (model, id) {
          return "You have successfully purchased Item " + id + ", a " + model;
      }

    , arrangeViewing: function (model, id) {
          return "You have successfully booked a viewing of " + model + " ( " + id + " ) ";
     }

};

var car = {
      model: 'Ford'
    , id: 52738
};

console.log(carManager.call(car));

// или

carManager.execute = function (name) {
    return carManager[name] && carManager[name].apply(null, [].slice.call(arguments, 1));
};

console.log(carManager.execute('buyVehicle', 'Ford Escort', '34232'));

9) Facade

var addMyEvent = function (element, eventName, callbackFunction) {
             if (element.addEventListener) {element.addEventListener(eventName, callbackFunction, false);
    } else if (element.attachEvent) {element.attachEvent('on' + eventName, callbackFunction);
    } else {element['on' + eventName] = callbackFunction;
    }
};

addMyEvent(document.getElementById('myButton'), 'click', function(){alert('OK');});

10) Factory

function Car (options) {
    this.doors = options.doors || 4;
    this.state = options.state || 'brand new';
    this.color = options.color || 'silver';
}

function Truck (options) {
    this.state = options.state || 'used';
    this.wheelSize = options.wheelSize || 'large';
    this.color = options.color || 'blue';
}

function VehicleFactory() {}

VehicleFactory.prototype.vehicleClass = Car;
VehicleFactory.prototype.createVehicle = function (options) {
    switch(options.vehicleType){
        case 'car':this.vehicleClass = Car; break;
        case 'truck':this.vehicleClass = Truck; break;
    }
    return new this.vehicleClass(options);
};

var carFactory = new VehicleFactory();

var car = carFactory.createVehicle( {
      vehicleType: 'car'
    , color: 'yellow',
    , doors: 6
});

var movingTruck = carFactory.createVehicle( {
      vehicleType: 'truck'
    , state: 'like new'
    , color: 'red'
    , wheelSize: 'small'
});

11) Decorator

function door () {
    return 'metal door';
}

function decorator (fn) {
    return function(){
        return 'black ' + fn();
    }
}

door = decorator(door);

console.log(door());

12) Iterator

function forEachInArrayDo (array, func) {
    for (var i = 0, len = array.length; i < len; i++) {
        func(array[i]);
    }
}

var arr = [1,2,3,4,5];

forEachInArrayDo(arr, function(element){console.log(element);});

13) Curry

function curry (func) {
    return function (args) {
        return func (args);
    };
}

['11','11','11','11'].map(parseInt) // [11, NaN, 3, 4]

['11','11','11','11'].map(curry(parseInt)); // [11, 11, 11, 11]

function curry2(fun) {
    return function(secondArg) {
        return function(firstArg) {
            return fun(firstArg, secondArg);
        };
    };
}

function div(n, d) {return n / d;}
var div10 = curry2(div)(10);
div10(50); // 5

function curry3(fun) {
    return function(last) {
        return function(middle) {
            return function(first) {
                return fun(first, middle, last);
            };
        };
    };
};

var songsPlayed = curry3(_.uniq)(false)(songToString);
songsPlayed(plays);

// [{artist: "Burial", track: "Archangel"},
//  {artist: "Ben Frost", track: "Stomp"},
//  {artist: "Emeralds", track: "Snores"}]

14) Proxy

$('button').on('click', function () {

    setTimeout($.proxy(function () {
        $(this).addClass('active'); // "this" теперь ссылается на внешний "this" функции "click"
    }, this), 500); // <-- Этот "this" передается из функции "click" во внутреннюю функцию внутри "proxy"

});

// jQuery's implementation of jQuery.proxy() can be found below:
// Bind a function to a context, optionally partially applying any
// arguments.
proxy: function(fn, context) {
  if (typeof context === "string") {
    var tmp = fn[context];
    context = fn;
    fn = tmp;
  }

  // Quick check to determine if target is callable, in the spec
  // this throws a TypeError, but we will just return undefined.
  if (!jQuery.isFunction( fn ) ) {return undefined;}

  // Simulated bind
  var args = slice.call( arguments, 2 ),
    proxy = function() {
      return fn.apply( context, args.concat( slice.call( arguments ) ) );
    };

  // Set the guid of unique handler to the same of original handler, so it can be removed
  proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;

  return proxy;
}

15) jQuery plugin

/*!
 * jQuery lightweight plugin boilerplate
 * Original author: @ajpiano
 * Further changes, comments: @addyosmani
 * Licensed under the MIT license
 */


// the semi-colon before the function invocation is a safety
// net against concatenated scripts and/or other plugins
// that are not closed properly.
;(function ( $, window, document, undefined ) {

    // undefined is used here as the undefined global
    // variable in ECMAScript 3 and is mutable (i.e. it can
    // be changed by someone else). undefined isn't really
    // being passed in so we can ensure that its value is
    // truly undefined. In ES5, undefined can no longer be
    // modified.

    // window and document are passed through as local
    // variables rather than as globals, because this (slightly)
    // quickens the resolution process and can be more
    // efficiently minified (especially when both are
    // regularly referenced in our plugin).

    // Create the defaults once
    var pluginName = "defaultPluginName",
        defaults = {
            propertyName: "value"
        };

    // The actual plugin constructor
    function Plugin( element, options ) {
        this.element = element;

        // jQuery has an extend method that merges the
        // contents of two or more objects, storing the
        // result in the first object. The first object
        // is generally empty because we don't want to alter
        // the default options for future instances of the plugin
        this.options = $.extend( {}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    }

    Plugin.prototype.init = function () {
        // Place initialization logic here
        // We already have access to the DOM element and
        // the options via the instance, e.g. this.element
        // and this.options
    };

    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[pluginName] = function ( options ) {
        return this.each(function () {
            if ( !$.data(this, "plugin_" + pluginName )) {
                $.data( this, "plugin_" + pluginName,
                new Plugin( this, options ));
            }
        });
    }

})( jQuery, window, document );

$("#elem").defaultPluginName({
      propertyName: "a custom value"
});

16) jQuery UI widget factory

/*!
 * jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
 * Author: @addyosmani
 * Further changes: @peolanha
 * Licensed under the MIT license
 */


;(function ( $, window, document, undefined ) {

    // define our widget under a namespace of your choice
    // with additional parameters e.g.
    // $.widget( "namespace.widgetname", (optional) - an
    // existing widget prototype to inherit from, an object
    // literal to become the widget's prototype );

    $.widget( "namespace.widgetname" , {

        //Options to be used as defaults
        options: {
            someValue: null
        },

        //Setup widget (e.g. element creation, apply theming
        // , bind events etc.)
        _create: function () {

            // _create will automatically run the first time
            // this widget is called. Put the initial widget
            // setup code here, then we can access the element
            // on which the widget was called via this.element.
            // The options defined above can be accessed
            // via this.options this.element.addStuff();
        },

        // Destroy an instantiated plugin and clean up
        // modifications the widget has made to the DOM
        destroy: function () {

            // this.element.removeStuff();
            // For UI 1.8, destroy must be invoked from the
            // base widget
            $.Widget.prototype.destroy.call( this );
            // For UI 1.9, define _destroy instead and don't
            // worry about
            // calling the base widget
        },

        methodB: function ( event ) {
            //_trigger dispatches callbacks the plugin user
            // can subscribe to
            // signature: _trigger( "callbackName" , [eventObject],
            // [uiObject] )
            // e.g. this._trigger( "hover", e /*where e.type ==
            // "mouseenter"*/, { hovered: $(e.target)});
            this._trigger( "methodA", event, {
                key: value
            });
        },

        methodA: function ( event ) {
            this._trigger( "dataChanged", event, {
                key: value
            });
        },

        // Respond to any changes the user makes to the
        // option method
        _setOption: function ( key, value ) {
            switch ( key ) {
            case "someValue":
                // this.options.someValue = doSomethingWith( value );
                break;
            default:
                // this.options[ key ] = value;
                break;
            }

            // For UI 1.8, _setOption must be manually invoked
            // from the base widget
            $.Widget.prototype._setOption.apply( this, arguments );
            // For UI 1.9 the _super method can be used instead
            // this._super( "_setOption", key, value );
        }
    });

})( jQuery, window, document );

var collection = $("#elem").widgetName({
    foo: false
});

collection.widgetName("methodB");

Комментариев нет:

Отправить комментарий