1. 程式人生 > >《JavaScript模式》--第五章:物件建立模式

《JavaScript模式》--第五章:物件建立模式

/*
	物件建立模式
*/
/*----------------------------START 名稱空間--------------------------------------*/
/*
		JS中並沒有內建名稱空間
		要實現名稱空間,可以為應用程式或庫建立一個全域性物件,然後可以將所有功能新增到該全域性物件中,從而在具有大量函式、物件和其他變數的情況下並不會汙染全域性範圍
*/
var MYAPP = {};

MYAPP.Parent = function() {};
MYAPP.Child = function() {};

MYAPP.some_var = 1;
MYAPP.modules = {};

MYAPP.modules.module1 = {};
MYAPP.modules.module1.data = {
	a: 1,
	b: 2
};
MYAPP.modules.module2 = {};

/*
	優點
		可以避免程式碼中的命名衝突
		並且還可以避免在同一個頁面中的您的二程式碼和第三方程式碼之間的命名衝突
	缺點
		需要輸入更多的字元,每個變數和函式前都要附加字首
		僅有一個全域性例項意味著任何部分的程式碼都可以修改該全域性例項
		長巢狀名字以為著更長的屬性解析查詢時間
*/
/*
	通用名稱空間函式
*/
/*在新增一個屬性或者建立一個名稱空間之前,最好是首先檢查它是否已經存在*/
var APP = {};
APP.namespace = function(ns_string) {
	var parts = ns_string.split('.'),
		parent = APP;

	if (parts[0] === 'APP') {
		parts = parts.slice(1);
	}

	for (var i = 0, m = parts.length; i < m; i++) {
		if (typeof parent[parts[i]] === "undefined") {
			parent[parts[i]] = {};
		}
		parent = parent[parts[i]];
	}
	return parent;
};
var module2 = APP.namespace('APP.modules.module2');
APP.namespace('a.b.c.d.e.f.g');
console.log(APP);
/* 
	輸出:
	Object {namespace: function, modules: Object, a: Object}
		a: Object
			b: Object
				c: Object
					d: Object
						e: Object
							f: Object
								g: Object
								__proto__: Object
							__proto__: Object
						__proto__: Object
					__proto__: Object
				__proto__: Object
			__proto__: Object
		__proto__: Object
		modules: Object
			module2: Object
			__proto__: Object
		__proto__: Object
		namespace: function (ns_string) {
		__proto__: Object
*/

/*----------------------------END 名稱空間--------------------------------------*/

/*----------------------------START 宣告依賴關係-------------------------------------*/
/*
	在函式或模組頂部宣告程式碼所依賴的模組是一個非常好的主意
		優點
			顯式地依賴宣告向用戶表明了他們確定需要的特定指令碼已經包含在該頁面中
			在函式頂部的前期宣告可以更容易地發現並解析依賴
			解析區域性變數的速度要比解析全域性變數要快,能提高效能
			使用一些小公雞可以重新命名區域性變數,可以減低程式碼量
*/
/*----------------------------END 宣告依賴關係-------------------------------------*/

/*----------------------------START 私有屬性和方法-------------------------------------*/
/*
	JS中所有物件的成員都是公共的
	我們可以使用閉包來實現私有屬性和方法。建構函式建立一個閉包,而在必報範圍內部的任意變數都不會暴露給建構函式以外的程式碼
*/

function Constructor() {
	var name = "wly";
	this.getName = function() {
		return name;
	}
}
var obj = new Constructor();
console.log(obj.name);
console.log(obj.getName);
// 輸出:
// undefined
// function (){
// 		return name;
// } 

/*
	特權方法
		所謂特權方法,就是指那些可以訪問私有成員你的公共方法,如上例中的getName()方法

	私有性失效
		當直接從一個特權方法中返回私有的變數,而該變數是陣列或是物件,那麼外面的程式碼將可以隨意更改這個變數
		因為傳遞的是引用,很好理解,故不舉例
		解決方法
			返回一個新物件,僅僅包含客戶關注的原物件中的資料
			建立一個克隆,返回那個克隆的副本
*/

/*
	物件字面量以及私有性
		在使用物件字面量的情況下,可以使用一個額外的匿名即時函式建立閉包來實現私有性
		這也就是眾所周知的模組模式的基礎框架
*/
var myObj = (function() {
	var name = "wly";
	return {
		getName: function() {
			return name;
		}
	};
}());
console.log(myObj.name);
console.log(myObj.getName());
// 輸出:
// undefined
// wly

/*
	原型和私有性
		將私有成員和建構函式一起使用時,其中的一個缺點在於每次呼叫建構函式以建立物件時,這些私有成員都會被重新建立
		為了避免複製工作以及節省記憶體,可以將常用屬性和方法新增到建構函式的prototype中,這樣同一個建構函式建立的多個例項可以共享常見的部分資料
		由於prototype屬性僅僅是一個物件,因此可以使用物件字面量建立該物件
*/

function SomeObj() {
	var name = "wly";
	this.getName = function() {
		return name;
	}
}

SomeObj.prototype = (function() {
	var address = "bupt";
	return {
		getAddress: function() {
			return address;
		}
	};
}());

var obj = new SomeObj();
console.log(obj.name);
console.log(obj.getName());
console.log(obj.address);
console.log(obj.getAddress());
// 輸出:
// undefined 
// wly 
// undefined 
// bupt 

/*
	揭示模式
		將私有方法暴露成公有方法,在返回的物件中包含私有方法的引用
*/

var SomeObjConstructor = function() {
	var name = "wly",
		changeName = function() {
			name = "I am not wly";
		};
	return {
		getName: function() {
			return name;
		},
		setName: changeName
	};
};

var myObj = new SomeObjConstructor();
console.log(myObj.getName());
myObj.setName();
console.log(myObj.getName());
// 輸出:
// wly
// I am not wly 

/*----------------------------END 私有屬性和方法-------------------------------------*/

/*----------------------------START 模組模式-------------------------------------*/
/*
	模組模式是一種得到廣泛應用的模式,其實際上就是以下模式的組合
		名稱空間
		即時函式
		私有和特權成員
		宣告依賴

	模組模式的步驟:
		首先建立一個名稱空間
		然後定義這個模組
		向其私有作用域新增一些私有屬性和方法
		返回一個物件,該物件中包含了模組的公共API
*/
/*使用名稱空間模式建立名稱空間*/
APP.namespace("APP.modules.myModule");

APP.modules.myModule = (function() {
	var otherModule_1 = APP.modules.otherModule_1,
		otherModule_2 = APP.modules.otherModule_2,
		name = "wly";
	return {
		getName: function() {
			return name;
		},
		setName: function() {
			name = "I am not wly";
		}
	};

}());

console.log(APP.modules.myModule.getName());
APP.modules.myModule.setName();
console.log(APP.modules.myModule.getName());
// 輸出:
// wly
// I am not wly 

/*
	揭示模組模式
		模組模式中將所有方法定義為私有,只揭示那些公開的方法
*/
APP.namespace("APP.modules.myModule");

APP.modules.myModule = (function() {
	var otherModule_1 = APP.modules.otherModule_1,
		otherModule_2 = APP.modules.otherModule_2,
		name = "wly",
		getName = function() {
			return name;
		},
		setName = function() {
			name = "I am not wly";
		};
	return {
		getName: getName,
		setName: setName
	};

}());

console.log(APP.modules.myModule.getName());
APP.modules.myModule.setName();
console.log(APP.modules.myModule.getName());
// 輸出:
// wly
// I am not wly

/*
	建立建構函式的模組
		在使用建構函式建立物件時,使用模組模式來執行建立物件的操作,與之前不同的是在於包裝了模組的即時函式最終返回一個函式,而不是物件,這個返回的函式就是建構函式
*/
APP.namespace("APP.modules.myModule");

APP.modules.MyModule = (function(global) {
	var otherModule_1 = APP.modules.otherModule_1,
		otherModule_2 = APP.modules.otherModule_2,
		moduleConstrutor;
	moduleConstrutor = function(obj) {
		this.properties = obj;
		this.reset = function() {
			this.properties = {
				name: "none",
				address: "no house"
			};
			return this;
		};
	}
	moduleConstrutor.prototype = {
		constructor: APP.modules.myModule,
		version: "1.0",
		outputToConcole: function() {
			for (var tmp in this.properties) {
				console.log(this.properties[tmp]);
			}
			return this;
		},
		outputContext: function() {
			console.log(global);
		}
	};
	return moduleConstrutor;
}(this));

var newModule = new APP.modules.MyModule({
	name: "wly",
	address: "bupt"
});
newModule.outputToConcole().reset().outputToConcole().outputContext();
// 輸出:
// wly 
// bupt 
// none 
// no house 
// Window {top: Window, window: Window, location: Location, external: Object, chrome: Object…}

/*
	匯入全域性變數
		將全域性變數作為引數傳遞到模組中,可以加快全域性符號解析速度,具體使用如上
*/
/*----------------------------END 模組模式-------------------------------------*/

/*----------------------------START 沙箱模式-------------------------------------*/
/*
	沙箱模式解決了名稱空間模式的幾個缺點
		對單個全域性變數的依賴變成了對應用程式的全域性變數依賴
		對這種以點分割的名字來說,需要輸入更長的字元,並且在執行時需要解析更長的時間

	沙箱模式提供了一個可用於模組執行的環境,且不會對其他模組和個人沙箱造成任何影響
*/

/*
	全域性建構函式
		在沙箱模式中,則是一個全域性建構函式,為Sandbox()

	可以給這個模式新增兩個新的特徵
		強制new模式
		Sandbox()建構函式可以接受一個或多個額外的配置引數,這些引數代表了物件例項所需要的模組名

	增加模組
		Sandbox.modules = {}
		Sandbox.modules.dom = function(box){};
		Sandbox.modules.event = function(box){};
		Sandbox.modules.ajax = function(box){};
*/
/*
	實現建構函式
*/

function Sandbox() {
	var args = Array.prototype.slice.call(arguments),
		callback = args.pop(),
		modules = (arg[0] && typeof args[0] === 'string') ? args : args[0],
		i;
	//強制new模式
	if (!(this instanceof Sandbox)) {
		return new Sandbox(modules, callback);
	}
	//新增屬性到this中
	this.name = 'wly';
	this.address = 'bupt';
	//不指定模組名稱和使用*都表示使用所有模組
	if (!modules || modules === '*') {
		modules = [];
		for (i in Sandbox.modules) {
			if (Sandbox.modules.hasOwnProperty(i)) {
				modules.push(i);
			}
		}
	}
	//初始化所有模組
	for (i = 0; i < modules.length; i++) {
		Sandbox.modules[modules[i]](this);
	}
	//回撥函式呼叫
	callback(this);
};
//沙箱的原型屬性
Sandbox.prototype = {
	version: "1.0",
	getName: function() {
		return this.name;
	}
};

/*----------------------------END 沙箱模式-------------------------------------*/

/*----------------------------START 靜態成員-------------------------------------*/
/*
	公有靜態成員
		JS中可以通過使用建構函式並且向其新增屬性,從而獲得類似的效果,備忘錄模式就是這種思想
*/
var SomeObj = function() {};
SomeObj.staticMethod = function() {
	console.log("static method");
};
SomeObj.prototype.normalMethod = function() {
	console.log("normal method");
};
SomeObj.staticMethod();
var newObj = new SomeObj();
newObj.normalMethod();
console.log(typeof SomeObj.normalMethod);
console.log(typeof newObj.staticMethod);
// 輸出:
// static method
// normal method
// undefined
// undefined
// 分析:
// 第一個undefined表示例項方法無法以靜態方式呼叫
// 第二個undefined表示靜態方法無法以例項方式呼叫

/*
	如需要讓例項也能直接呼叫靜態方法,需要將靜態方法加入到建構函式的原型中
		需要注意這種模式下的this,建構函式直接呼叫時指向建構函式,而如果是物件進行靜態方法呼叫,則this指向這個物件
*/
SomeObj.prototype.staticMethod = SomeObj.staticMethod;
newObj.staticMethod();
// 輸出:
// static method

SomeObj.staticMethod_2 = function() {
	var msg = 'Static invoke';

	if (this instanceof SomeObj) {
		msg = 'Object invoke';
	}

	return msg;
};

SomeObj.prototype.staticMethod_2 = function() {
	return SomeObj.staticMethod_2.call(this);
};

console.log(SomeObj.staticMethod_2());
console.log(new SomeObj().staticMethod_2());
// 輸出:
// Static invoke
// Object invoke

/*
	私有靜態成員
		以同一個建構函式建立的所有物件共享該成員
		建構函式外部不可訪問該成員
*/
var SomeObj = (function() {
	var count = 0;
	return function() {
		count++;
		console.log(count);
	};
}());
new SomeObj();
new SomeObj();
new SomeObj();
new SomeObj();
// 輸出:
// 1
// 2
// 3
// 4

/*
	物件常量
		JS中沒有常量這個概念,一般是以命名約定來標識常量,常量的名稱全部用大寫字母顯示

	一個通用的常量物件實現方法例項
		set(name,value):定義一個新的常量
		isDefined(name) :檢測指定常量是否存在
		get(name):讀取指定常量的值
*/

var constant = (function() {
	var constants = {},
		ownProp = Object.prototype.hasOwnProperty,
		allowed = {
			string: 1,
			number: 1,
			boolean: 1
		},
		prefix = (Math.random() + "_").slice(2);
	return {
		set: function(name, value) {
			if (this.isDefined(name)) {
				return false;
			}
			if (!ownProp.call(allowed, typeof value)) {
				return false;
			}
			constants[prefix + name] = value;
			return true;
		},
		isDefined: function(name) {
			return ownProp.call(constants, prefix + name);
		},
		get: function(name) {
			if (this.isDefined(name)) {
				return constants[prefix + name];
			}
			return null;
		}
	};
}());

console.log(constant.isDefined("name"));
console.log(constant.set("name","wly"));
console.log(constant.get("name"));
console.log(constant.set("name","not wly"));
console.log(constant.get("name"));
// 輸出:
// false
// true
// wly
// false 
// wly 

/*----------------------------END 靜態成員-------------------------------------*/

/*----------------------------START 鏈模式-------------------------------------*/
/*
	鏈模式
		使使用者能夠一個接一個的呼叫物件的方法,而無需將前一個操作返回的值賦給變數
		當建立的方法其返回的是無任何意義的值時,可以使他們返回this,即正在使用的物件例項

	鏈模式優點
		可以節省一些輸入字元,而且可以建立更加簡潔,更加清晰的程式碼
		可以幫助考慮分割函式,以建立更加簡短、具有特定功能的函式,而不是建立嘗試實現太多功能的函式

	鏈模式的缺點
		出錯了難以除錯

	jQuery使用了這種模式
*/
var someObj = {
	value : 1,
	increment : function(){
		this.value += 1;
		return this;
	},
	add : function(v){
		this.value += v;
		return this;
	},
	output : function(){
		console.log(this.value);
		return this;
	}
};
someObj.increment().output().add(5).output().add(3).output();
// 輸出:
// 2 
// 7 
// 10 

/*----------------------------END 鏈模式-------------------------------------*/

/*----------------------------START method()方法模式-------------------------------------*/
/*
	method()方法
		Douglas Crockford引入
		有兩個引數
			新方法的名稱
			新方法的實現
*/
// method方法實現
if(typeof Function.prototype.method !== 'function'){
	Function.prototype.method = function (name,implementation){
		this.prototype[name]  = implementation;
		return this;
	};
}
var Person = function(name){
	this.name = name;
}.method('getName',function(){
	return this.name;
}).method('setName',function(name){
	this.name = name;
	return this;
});

var wly = new Person("wly");
console.log(wly.getName());
console.log(wly.setName("not wly").getName());
// 輸出:
// wly
// not wly 
/*----------------------------END method()方法模式-------------------------------------*/