Note on <Learning JavaScript Design Patterns> - 01 - Recompiled 2017 Apr-May



CHAPTER 8: Design Pattern Categorization




Creational

Based on the concept of creating an object.
Class
Factory MethodThis makes an instance of several derived classes based on interfaced data or events.
Object
Abstract FactoryCreates an instance of several families of classes without detailing concrete classes.
BuilderSeparates object construction from its representation, always creates the same type of object.
PrototypeA fully initialized instance used for copying or cloning.
SingletonA class with only a single instance with global access points.


StructuralBased on the idea of building blocks of objects
Class
AdapterMatch interfaces of different classes therefore classes can work together despite incompatible interfaces
Object
AdapterMatch interfaces of different classes therefore classes can work together despite incompatible interfaces
BridgeSeparates an object's interface from its implementation so the two can vary independently
CompositeA structure of simple and composite objects which makes the total object more than just the sum of its parts.
DecoratorDynamically add alternate processing to objects.
FacadeA single class that hides the complexity of an entire subsystem.
FlyweightA fine-grained instance used for efficient sharing of information that is contained elsewhere.
ProxyA place holder object representing the true object.


BehavioralBased on the way objects play and work together.
Class
InterpreterA way to include language elements in an application to match the grammar of the intended language.
Template MethodCreates the shell of an algorithm in a method, then defer the exact steps to a subclass.
Object
Chain of ResponsibilityA way of passing a request between a chain of objects to find the object that can handle the request.
CommandEncapsulate a command request as an object to enable, logging and/or queuing of requests, and provides error-handling for unhandled requests.
IteratorSequentially access the elements of a collection without knowing the inner workings of the collection.
MediatorDefines simplified communication between classes to prevent a group of classes from referring explicitly to each other.
MementoCapture an object's internal state to be able to restore it later.
Observer
A way of notifying change to a number of classes to ensure consistency between the classes.
StateAlter an object's behavior when its state changes
StrategyEncapsulates an algorithm inside a class separating the selection from the implementation.
VisitorAdds a new operation to a class without changing the class.









CHAPTER 11: MV* Patterns


Templating


It has long been considered (and proven) a performance bad practice to manually create large blocks of HTML markup in-memory through string concatenation. Developers doing so have fallen prey to inperformantly iterating through their data, wrapping it in nested divs and using outdated techniques such as document.write to inject the 'template' into the DOM. As this typically means keeping scripted markup inline with your standard markup, it can quickly become both difficult to read and more importantly, maintain such disasters, especially when building non-trivially sized applications.


JavaScript templating solutions (such as Handlebars.js and Mustache) are often used to define templates for views as markup (either stored externally or within script tags with a custom type - e.g text/template) containing template variables. Variables may be deliminated using a variable syntax (e.g {{name}}) and frameworks are typically smart enough to accept data in a JSON form (of which model instances can be converted to) such that we only need be concerned with maintaining clean models and clean templates. Most of the grunt work to do with population is taken care of by the framework itself. This has a large number of benefits, particularly when opting to store templates externally as this can give way to templates being dynamically loaded on an asneeded basis when it comes to building larger applications.


Namespacing Fundamentals


Some topics in this section obviously overlaps each other, like that I cannot tell the difference between single global variable and module pattern, and their implementation relys on IIFE.


Single global variables

var myApplication = (function(){
	function(){
		//...
	},
	return{
		//...
	}
})();

Object literal notation

var myApplication = {
	getInfo:function(){ /**/ },
	// we can also populate object literal to support
	// further object namespaces containing anything really:
	models : {},
	views : {
		pages : {}
	},
	collections : {}
};


And the good thing is you can modify it later on:

myApplication.foo = function(){ /**/ }
myApplication.utils = {
	toString:function(){
		//...
	},
	export: function(){
		//...
	}
}


The different ways in which you can check to see if a variable (object namespace) already exists before defining it. You'll commonly see developers using Option 1, however Options 3 and 5 may be considered more thorough and Option 4 is considered a good best-practice. 

// This doesn't check for existence of 'myApplication' in
// the global namespace. Bad practice as you can easily
// clobber an existing variable/namespace with the same name
var myApplication = {};
// The following options *do* check for variable/namespace existence.
// If already defined, we use that instance, otherwise we assign a new
// object literal to myApplication.
//
// Option 1: var myApplication = myApplication || {};
// Option 2 if(!MyApplication) MyApplication = {};
// Option 3: var myApplication = myApplication = myApplication || {}
// Option 4: myApplication || (myApplication = {});
// Option 5: var myApplication = myApplication === undefined ? {} : myApplication;
//


In addition to namespacing, it's often of benefit to decouple the default configuration for your application into a single area that can be easily modified without the need to search through your entire codebase just to alter them - object literals work great for this purpose.


Nested namespacing

A sample implementation of nested namespacing may look like this:

var myApp = myApp || {};
// perform a similar existence check when defining nested
// children
myApp.routers = myApp.routers || {};
myApp.model = myApp.model || {};
myApp.model.special = myApp.model.special || {};
// nested namespaces can be as complex as required:
// myApp.utilities.charting.html5.plotGraph(/*..*/);
// myApp.modules.financePlanner.getSummary();
// myApp.services.social.facebook.realtimeStream.getLatest();


You can also opt to declare new nested namespaces/properties as indexed properties as follows:

myApp["routers"] = myApp["routers"] || {};
myApp["models"] = myApp["models"] || {};
myApp["controllers"] = myApp["controllers"] || {};


This can mean an increased amount of work to perform lookups, however developers such as Juriy Zaytsev have previously tested and foundthe performance differences between single object namespacing vs the 'nested' approach to be quite negligible.


Immediately-invoked Function Expressions (IIFE)s

The simplest version of an IIFE:

// an (anonymous) immediately-invoked function expression
(function(){ /*...*/})();
// a named immediately-invoked function expression
(function foobar(){ /*..*/}());
// this is technically a self-executing function which is quite different
function foobar(){ foobar(); }


IIFE being applied to extend namespace:

var namespace = namespace || {};
// here a namespace object is passed as a function
// parameter, where we assign public methods and
// properties to it
(function( o ){
	o.foo = "foo";
	o.bar = function(){
		return "bar";
	};
})(namespace);


More over, being used to encapsulate:

// namespace (our namespace name) and undefined are passed here
// to ensure 1. namespace can be modified locally and isn't
// overwritten outside of our function context
// 2. the value of undefined is guaranteed as being truly
// undefined. This is to avoid issues with undefined being
// mutable pre-ES5.
;(function ( namespace, undefined ) {
// private properties
var foo = "foo",
bar = "bar";
// public methods and properties
namespace.foobar = "foobar";
namespace.sayHello = function () {
speak("hello world");
};
// private method
function speak(msg) {
console.log("You said: " + msg);
};
// check to evaluate whether 'namespace' exists in the
// global namespace - if not, assign window.namespace an
// object literal
}(window.namespace = window.namespace || {});
// we can then test our properties and methods as follows
// public
console.log(namespace.foobar); // foobar
namescpace.sayHello(); // hello world
// assigning new properties
namespace.foobar2 = "foobar";
console.log(namespace.foobar2);


Or, there is another alternative:

// let's extend the namespace with new functionality
(function( namespace, undefined ){
	// public method
	namespace.sayGoodbye = function(){
		console.log(namespace.foo);
		console.log(namespace.bar);
		speak('goodbye');
	}
}( window.namespace = window.namespace || {});


Namespace injection

Namespace injection is another variation on the IIFE where we 'inject' the methods and properties for a specific namespace from within a function wrapper using this as a namespace proxy.

The benefit this pattern offers is easy application of functional behaviour to multiple objects or namespaces and can come in useful when applying a set of base methods to be built on later (e.g. getters and setters).

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);
// inject new behaviour into the tools namespace
// which we defined via the utilities module
(function(){
	this.diagnose = function(){
		return 'diagnosis';
	}
}).apply(myApp.utils.tools);
// note, this same approach to extension could be applied
// to a regular IIFE, by just passing in the context as
// an argument and modifying the context rather than just
// 'this'

// testing
console.log(myApp); //the now populated namespace
console.log(myApp.utils.getValue()); // test get
myApp.utils.setValue(25); // test set
console.log(myApp.utils.getValue());
console.log(myApp.utils.tools.diagnose());


Its analogue of Call: 

// define a namespace we can use later
var ns = ns || {}, ns2 = ns2 || {};
// the module/namespace creator
var creator = function(val){
	var val = val || 0;
	this.next = function(){
		return val++
	};
	this.reset = function(){
		val = 0;
	}
}
creator.call(ns);
// ns.next, ns.reset now exist
creator.call(ns2, 5000);
// ns2 contains the same methods
// but has an overridden value for val of 5000



Advanced Namespacing


Automating nested namespacing


This approach was brought to more cleverly handle deeply extending namespace rather than with object literal:


var application = {
	utilities:{
		drawing:{
			canvas:{
				2d:{
						//...                   
				}
			}
		}
	}
};


The trick is to define a function to handle the dirty work:


// top-level namespace being assigned an object literal 
var myApp = myApp || {}; 

// a convenience function for parsing string namespaces and 
// automatically generating nested namespaces 
function extend( ns, ns_string ) {
	var parts = ns_string.split('.'),
		parent = ns,
		pl, i;
		
	pl = parts.length;
	for (i = 0; i < pl; i++) {
		// create a property if it doesnt exist
		if (typeof parent[parts[i]] == 'undefined') {
			parent[parts[i]] = {};
		}
		
		parent = parent[parts[i]];
	}
	
	return parent;
} 
// sample usage: 
// extend myApp with a deeply nested namespace 
var mod = extend(myApp, 'myApp.modules.module2'); 
// the correct object with nested depths is output 
console.log(mod); 
// minor test to check the instance of mod can also 
// be used outside of the myApp namesapce as a clone 
// that includes the extensions 
console.log(mod == myApp.modules.module2); //true 
// further demonstration of easier nested namespace 
// assignment using extend 
extend(myApp, 'moduleA.moduleB.moduleC.moduleD'); 
extend(myApp, 'longer.version.looks.like.this'); 
console.log(myApp); 


Dependency declaration pattern

If each time we refer to a variable inside a namespace (usually a deeply nested one), we use its full qualified name, that would be much too verbose and inefficient, and to solve this, you candeclare a local variable referencing those variables, as a shortcut. The author suggests using this pattern only at modular level, rather than function level. 


// common approach to accessing nested namespaces 
myApp.utilities.math.fibonacci(25); 
myApp.utilities.math.sin(56); 
myApp.utilities.drawing.plot(98,50,60); 

// with local/cached references 
var utils = myApp.utilities, 
maths = utils.math, 
drawing = utils.drawing; 
// easier to access the namespace 
maths.fibonacci(25); maths.sin(56); drawing.plot(98, 50,60); 
// note that the above is particularly performant when
// compared to hundreds or thousands of calls to nested 
// namespaces vs. a local reference to the namespace


Deep Object Extension

This pattern is similar to 'automating namespace', except it merges the nested sub-namespaces as deeply as possible.


// extend.js 
// written by andrew dupont, optimized by addy osmani 
function extend(destination, source) {
	var toString = Object.prototype.toString,
	objTest = toString.call({});
	for (var property in source) {
		if (source[property] && objTest == toString.call(source[property])) {
			destination[property] = destination[property] || {};
			extend(destination[property], source[property]);
		} 
		else {
			destination[property] = source[property];
		}
	}
	return destination; 
}; 
console.group("objExtend namespacing tests"); 
// define a top-level namespace for usage
var myNS = myNS || {}; 
// 1. extend namespace with a 'utils' object 
extend(myNS, {
	utils:{
		
	} 
}); 
console.log('test 1', myNS); 
//myNS.utils now exists 

// 2. extend with multiple depths (namespace.hello.world.wave) 
extend(myNS, {
	hello:{
		world:{
			wave:{
				test: function(){
					//...
				}
			}
		}
	} 
}); 

// test direct assignment works as expected 
myNS.hello.test1 = 'this is a test'; 
myNS.hello.world.test2 = 'this is another test'; 
console.log('test 2', myNS); 

// 3. what if myNS already contains the namespace being added 
// (e.g. 'library')? we want to ensure no namespaces are being 
// overwritten during extension 

myNS.library = {
	foo:function(){} 
}; 
	
extend(myNS, {
	library:{
		bar:function(){
			//...
		}
	} 
}); 

// confirmed that extend is operating safely (as expected) 
// myNS now also contains library.foo, library.bar 
console.log('test 3', myNS); 

// 4. what if we wanted easier access to a specific namespace without having 
// to type the whole namespace out each time?. 
var shorterAccess1 = myNS.hello.world; 
shorterAccess1.test3 = "hello again";
console.log('test 4', myNS); 
//success, myApp.hello.world.test3 is now 'hello again' 

console.groupEnd();



CHAPTER 9: JavaScript设计模式


创建模式(Creational Pattern)

创建对象在ES3里可以有两种语法:

var newObject = {}; // or
var newObject = new Object();


在ES5里则增添了一个新语法:

var newObject = Object.create(null);

创建之后,给对象增加属性,在ES3里有两种方式:

// 1. Dot syntax
newObject.someKey = 'Hello World'; // Write properties
var key = newObject.someKey; // Access properties
// 2. Square bracket syntax
newObject['someKey'] = 'Hello World'; // Write properties
var key = newObject['someKey']; // Access properties


在ES5里,新增加了两个语法:

// Object.defineProperty
Object.defineProperty(newObject, "someKey", {
  value: "for more control of the property's behavior",
  writable: true,
  enumerable: true,
  configurable: true
});
// Object.defineProperties
Object.defineProperties(newObject, {
  "someKey": {
    value: "Hello World",
    writable: true
  },
  "anotherKey": {
    value: "Foo bar",
    writable: false
  }
});


构造函数模式(Constructor Pattern)


基本用法

function Car(model, year, miles) {
  this.model = model;
  this.year = year;
  this.miles = miles;
  this.toString = function () {
    return this.model + " has done " + this.miles + " miles";
  };
}
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
console.log(civic.toString());
console.log(mondeo.toString());


这个做法有一些弊端,比如方法的定义会在每个对象上重复,而且继承很难实现,过于第二点,最近好多读书笔记都有相关分析。


通过原型实现的构造函数

function Car(model, year, miles) {
  this.model = model;
  this.year = year;
  this.miles = miles;
}
// Note here that we are using Object.prototype.newMethod rather than
// Object.prototype so as to avoid redefining the prototype object
Car.prototype.toString = function () {
  return this.model + " has done " + this.miles + " miles";
};
var civic = new Car("Honda Civic", 2009, 20000);
var mondeo = new Car("Ford Mondeo", 2010, 5000);
console.log(civic.toString());


单例模式(Singleton Pattern)


模块模式(Module Pattern)








观察者模式(Observer Pattern)



































  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值