我知道的JavaScript
作者: 韩炎冰
英文名: Robbin Han
版本号: 0.00001
开始日期:2011年9月26日
结束日期:undefined
历史记录:
1. 2011年9月26日开始写作,完成1-4小节.
1. JavaScript闭包
代码:
(function(){ var validator_elements_blur_selector = ‘…’; var validator_elements_change_selector = ‘…’; var validator_elements_selector = ‘…’; //some other codes… })();
解释:以上代码中的三个变量的作用域就在整个( )之间,外部无法改变这三个变量的值。这样就可以防止第三方代码对自己本身逻辑的侵入。这也是现在很多Js框架常用的自我保护的方法。代码最后的( )是对这个function 执行操作。前面的( )是对function 进行对象化的包装。这样这段逻辑就可以在代码所在的上下文立即执行了。
2. Javascript 数据结构之– Hashtable
代码:
function Hashtable() { this._hashValue= new Object(); this._iCount= 0; } Hashtable.prototype.add = function(strKey, value) { if(typeof (strKey) == "string"){ this._hashValue[strKey]= typeof (value) != "undefined"? value : null; this._iCount++; returntrue; } else throw"hash key not allow null!"; } Hashtable.prototype.get = function (key) { if (typeof (key)== "string" && this._hashValue[key] != typeof('undefined')) { returnthis._hashValue[key]; } if(typeof (key) == "number") returnthis._getCellByIndex(key); else throw"hash value not allow null!"; returnnull; } Hashtable.prototype.contain = function(key) { returnthis.get(key) != null; } Hashtable.prototype.findKey = function(iIndex) { if(typeof (iIndex) == "number") returnthis._getCellByIndex(iIndex, false); else throw"find key parameter must be a number!"; } Hashtable.prototype.count = function () { returnthis._iCount; } Hashtable.prototype._getCellByIndex = function(iIndex, bIsGetValue) { vari = 0; if(bIsGetValue == null) bIsGetValue = true; for(var key in this._hashValue) { if(i == iIndex) { returnbIsGetValue ? this._hashValue[key] : key; } i++; } returnnull; } Hashtable.prototype.remove = function(key) { for(var strKey in this._hashValue) { if(key == strKey) { deletethis._hashValue[key]; this._iCount--; } } } Hashtable.prototype.clear = function () { for (var key in this._hashValue) { delete this._hashValue[key]; } this._iCount = 0; }
解释:Hashtable在c#中是最常用的数据结构之一,但在JavaScript 里没有各种数据结构对象。但是我们可以利用动态语言的一些特性来实现一些常用的数据结构和操作,这样可以使一些复杂的代码逻辑更清晰,也更符合面象对象编程所提倡的封装原则。这里其实就是利用JavaScriptObject 对象可以动态添加属性的特性来实现Hashtable, 这里有需要说明的是JavaScript 可以通过for语句来遍历Object中的所有属性。但是这个方法一般情况下应当尽量避免使用,除非你真的知道你的对象中放了些什么。
[By the way]: StringCollection/ArrayList/Stack/Queue等等都可以借鉴这个思路来对JavaScript 进行扩展。
3. JavaScript设计模式(桥接)应用之 – Validation
引子:
首先请各位同学跟我来一起复习设计模式中的桥接模式(Bridge), 废话不多言表直接上图:
在这个设计模式中我们的抽象类和实现类可以各自进行扩展和封装这样就可以对它们进行脱耦, 通过组合来产生很多变化。这种思想也符合“少用继承,多用组合”的设计原则.在桥接模式中我们可以用Abstraction 类来对实现类(ConreteImplementor)和修正抽象化类(RefinedAbstraction)进行桥接。但JavaScript 如何实现桥接呢?Please follow me
代码:
Validation类:
Validation= { required: function(elem) { return!$(elem).val().trim().isNullOrEmpty(); }, email: function(elem) { returnValidation.regexValidator($(elem).val().trim(),Validation.Regex.email); }, regexValidator: function(elemVal, /*string*/regex,) { if(!elemVal.isNullOrEmpty() && !(elemVal.match(regex, "\g"))) { returnfalse; } else{ returntrue; }; }, Regex: { email:/^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)?$/ } }; Validation.validateColl= { 'jq-validation-required': {validFunc:Validation.required, ErrMsg: 'Required'}, 'jq-validation-email': {validFunc:Validation.email, ErrMsg: 'Invalid email address'} };
Validator类:
调用例子:(function () { varvalidator_elements_blur_selector = 'input[type="text"]'; varvalidator_elements_change_selector = 'select,input[type="hidden"]'; var validator_elements_selector =validator_elements_blur_selector + ',' +validator_elements_change_selector; Validator = function(validateScopeSelector) { this._validateColl= $.extend(true, {}, Validation._validateColl); this._validateDom= $(validateScopeSelector); vartheValidator = this; this._validateDom.delegate(validator_elements_blur_selector,'blur',function() { theValidator.validateInput(this, 'blur'); }); this._validateDom.delegate(validator_elements_change_selector,'change', function() { varinputValidated = theValidator.validateInput(this,'change'); }); }; Validator.prototype = { validate: function() { varvalidated = true; vartheValidator = this; $(validator_elements_selector, this._validateDom).each(function() { if(!theValidator.validateInput.call(theValidator, this)) { validated = false; }; }); returnvalidated; }, validateInput: function(elem, event) { varinput = $(elem); varclassArr = input.attr('class').split(' '); varvalidated = true; varinValidTable = new Hashtable(); for(var i = 0; i < classArr.length; i++) { varclassItem = classArr[i]; if(!classItem.startWith('jq-validation')) continue; varvalidateItem = this._validateColl[classItem]; if(validateItem && validateItem.validFunc) { if(!validateItem.validFunc(input, validateItem)) { validated = false; if (!strPopupErr.isNullOrEmpty()) { strPopupErr += " "; } inValidTable.add(classItem, validateItem); } } } returnvalidated; } }; })();
<html> <div id="validation_region"> < input type="text"class="jq-validation-required"/> < input type="text"class="jq-validation-email"/> </div> < input type="button"οnclick="submit()"/> <script language="javascript"type="text/javascript"> var validator = new Validator("#validation_region"); function submit(){ if(validator.validate()){ alert('验证通过!'); }else{ alert('验证失败!'); } } </script> </html>
解释:
1.设计思想
其中Validation 可以定义和扩展各种验证规则的方法,而Validator则负责处理验证后的错误提示以及如何正确反馈给代码调用者是否验证通过, Validation.validateColl则定义了哪种类型的验证调用哪一个验证规则的处理方法,他们各自分工明确,这也符合单一职责的设计原则。以上代码中我们可以看到Validation 对象是实现类,而Validator 对象是修正抽象化类, 而Validation.validateColl则是桥接器(在经典的Gof 23模式中没有这个定义)。当然在这里我们已经不能完全按照设计模式中定义的术语来描述以上代码了。我们只是按照设计模式的思路和理念来设计和构造我们的代码。
2.工作原理
在调用的例子中:
var validator = newValidator("#validation_region");
当页面加载完成后我们首先实例化Validator对象并传入需要验证的范围(Scope), 在这里我们传入需要验证区域的ID, 我们利用jQuery的$() 方法来把选择符“#validation_region"转换成可操作的DOM对象.
当Button 点击时我们调用submit方法,这时执行validator.validate 方法,这个方法会利用jQuery的each方法遍历验证范围内的所有input 控件进行验证,并最终返回验证的结果。
在Validate的内部方法中我们还可以加入当验证未通过时对input 进行改变样式并错误提示的功能,一般作法是在input加上红色的边框以提示用户,在这里这个功能需要读者根据项目的需求自己进行扩展了.3.扩展验证规则
前面我们讲解了设计思想以及工作原理,那么我们如何对validation 进行扩展呢?
我们只需要增加新的验证规则方法到Validation对象上,并在Validation.validateColl 对验证类型(input 的class 名)和新的验证规则进行桥接。
例如:
如果我们要加入一个验证是否是数字的规则,我们需要在Validation 对象中加入
number:function (elem) {
return!isNaN($(elem).val());
}
并在Validation.validateColl中加入
'jq-validation-number': { validFunc:Validation.number, ErrMsg: 'Notnumber’ }
这时我们并不需要更改任何Validator的代码就可以在input 的class中加入’jq-validation-number’ 来进行数字规则验证了。
这里需要说明一点如果需要对一个input 进行多种验证规则可以在class中以空格分割写入多种验证规则的名称
例如:
<input type="text" class="jq-validation-requiredjq-validation-number"/>
注:图片引自博客园吕震宇的设计模式系列,另附引用地址可以更详细的了解桥接模式:
http://www.cnblogs.com/zhenyulu/articles/62720.html
4. 在Asp.net mvc 下Json对象转换(扁平化)
------------ 解决Ajax无法提交复杂Json数据到Action 的问题
引子:
在Asp.net mvc 框架下当提交一个表单到Action方法上。可以把表单中的数据自动绑定到Action方法上参数的对象上,用Ajax 方法调用Action时当需要提交一个复杂对象如以下对象结构:
{ hotelName:’abc’, hotelAddress:’ 北京海淀路72号’, Rooms:[ {roomName:’标准间’,roomPrice:720}, {roomName:豪华间,roomPrice:1020}], HotelStar:4 }
这时我们必需转换成如下格式才能正确提交到后台Action的对象上。
{ hotelName:’abc’, hotelAddress:’ 北京海淀路72号’, Rooms[0]: {roomName:’标准间’,roomPrice:720}, Rooms[1]: {roomName:豪华间,roomPrice:1020}, HotelStar:4 }
代码:
解释:以上代码就是完成Json对象格式的转换。只有通过转换后的复杂Json对象才能提交到后台的Action 方法上。JsonFloat方法运用递归遍历json对象上的所有属性进行转换。Convert={ _jsonFlat:function (data, parentPro, returnObj) { if (data instanceof Object) { for (varpro in data) { try{ varproValue = eval("data." +pro.toString()); if(proValue instanceof Array) { for (var i = 0; i <proValue.length; i++) { if (parentPro){ Convert._jsonFlat(proValue[i], parentPro + "." + pro + "["+ i + "]", returnObj); } else Convert._jsonFlat(proValue[i], pro + "[" + i + "]",returnObj); } continue; } if(proValue instanceof Object) { if(parentPro) Convert._jsonFlat(proValue, parentPro + "."+ pro, returnObj); else Convert._jsonFlat(proValue, pro, returnObj); continue; } if(parentPro) returnObj[parentPro + "." + pro] = proValue; else returnObj[pro] =proValue; } catch(e) { }; } return; } //otherwiselike string/int/datetime format returnObj[parentPro] = data; },jsonFlat: function(data) { //debugger; if(data && data instanceof Object) { varretObj = {}; Convert._jsonFlat(data, null, retObj); returnretObj; } return null; }, }
5.JavaScript 实现深度复制(DeepClone)
引子:
在JavaScript中所有String/Int/Object以及function 都是以指针引用的形式出现的,这样可以节省内存开销,但当我们在代码中需要一个对象的复本的时候,这时就比较头痛了。那么如何解决这个问题呢?
代码:
方法1:
function deepClone(jsonObj) {
var buf;
if (jsonObjinstanceof Array) {
buf = [];
var i =jsonObj.length;
while(i--) {
buf[i] = deepClone(jsonObj[i]);
}
returnbuf;
} else if (jsonObj instanceofObject) {
buf = {};
for (var k in jsonObj) {
buf[k] = deepClone(jsonObj[k]);
}
returnbuf;
} else {
returnjsonObj;
}
};
方法2:
function deepClone(jsonObj) {
return $.extend(true, {}, jsonObj);
};
解释:第一种方法是递归遍历对象中的所有属性然后生成复本,但是此方法不能有循环引用,否则会有堆栈溢出的异常。第二种方法是利用jQuery 的extend方法复制对象,个人推荐第二种方法。
6.JavaScript获取UTC 时间
代码:
Date.getUTCDate= function (date) {
var yy = date.getUTCFullYear();
var MM =date.getUTCMonth();
var dd =date.getUTCDate();
var hh =date.getUTCHours();
var mm =date.getUTCMinutes();
var ss =date.getUTCSeconds();
var sss =date.getUTCMilliseconds();
return new Date(yy, MM, dd, hh, mm, ss, sss);
};
[By the way]:得到当前的UTC时间的代码: var currentUTCDate =Date.getUTCDate(new Date());
解释:当需与服务器时间进行计算或者跨时区进行时间比较时我们需要得到UTC时间,以上代码就是获取一个UTC时间。
3.JavaScript Date对象扩展
代码:
Date.prototype.addDay= function (n) {
var yy = this.getYear();
var MM = this.getMonth();
var dd = this.getDay();
var hh = this.getHours();
var mm = this.getMinutes();
var ss = this.getSeconds();
var sss = this.getMilliseconds();
return new Date(yy, MM, dd + n, hh, mm, ss, sss);
};
Date.prototype.compareToWithOutTime= function (date) {
var year = this.getFullYear() - date.getFullYear();
var month =this.getMonth() - date.getMonth();
var day = this.getDate() - date.getDate();
if (year ==0 && month == 0 && day == 0)
return0;
if (year> 0 || (year == 0 && month > 0) ||
(year == 0 && month == 0&& day > 0)) {
return1;
}
return -1;
};
解释:第一个方法是对一个Date对象执行加一天操作, 同理可以按照.net DateTime对象的API进行其它的扩展。例如:addMoth/addYear 等。
第二个方法是比较两个Date对象的日期部份,不比较时间部分。
7.JavaScript String对象扩展
代码:
String.prototype.isNullOrEmpty= function () {
return this == null || this.length <= 0;
};
String.prototype.format= function () {
varformattedString = this.toString();
for (var i = 0; i < arguments.length; i++) {
formattedString =formattedString.split('{' + i + '}').join(arguments[i]);
}
returnformattedString;
};
String.prototype.trimBegin= function () {
var str = this;
var i;
for (i = 0;i < str.length; i++) {
if(str.charAt(i) != " ") break;
}
returnstr.substring(i, str.length);
};
String.prototype.trimEnd= function () {
var str = this;
var i;
for (i =str.length - 1; i >= 0; i--) {
if(str.charAt(i) != " ") break;
}
returnstr.substring(0, i + 1);
};
String.prototype.trim= function () {
return this.trimBegin().trimEnd();
};
String.prototype.startWith= function (str) {
if (str == null || str == ""|| this.length == 0 || str.length > this.length)
return false;
return this.substr(0, str.length) == str;
};
String.prototype.endWith= function (str) {
if (str == null || str == ""|| this.length == 0 || str.length > this.length)
return false;
return this.substring(this.length- str.length) == str;
};
String.prototype.replaceAll= function (s1, s2) {
return this.replace(newRegExp(s1, "gm"), s2);
};
解释:JavaScript String 对象本身提供的的方法不多,但我们可以对String.prototype 原型对象进行扩展,这样所有的String 实例对象都可以使扩展的方法进行操作,这样大大方便了我们的编程。
8.JavaScript命名空间
引子:
在JavaScript中没有象在.net中的命名空间的机制,所以我们没有一个方便和安全的方式来管理和组织我们的代码结构,但是我们可以利用JavaScript Object 的动态属性来模拟命名空间来管理我们的JavaScript代码。
代码:
方法1
if (typeof (NameSpace)=== "undefined") {
NameSpace = {};
}
NameSpace.Utility= {/*some method*/};
方法2
NameSpace = NameSpace|| {};
NameSpace.Utility= {/*some method*/};
方法3:
//直接使用Microsoft AJAX Library
Type.registerNamespace("NameSpace ");
方法4:
//自已实现注册命名空间的方法
Namespace = new Object();
// 全局对象仅仅存在register函数,参数为名称空间全路径,如"Grandsoft.GEA"
Namespace.register = function(fullNS)
{
// 将命名空间切成N部分, 比如Grandsoft、GEA等
var nsArray= fullNS.split('.');
var sEval ="";
var sNS ="";
for (var i =0; i < nsArray.length; i++)
{
if (i !=0) sNS += ".";
sNS +=nsArray[i];
// 依次创建构造命名空间对象(假如不存在的话)的语句
// 比如先创建Grandsoft,然后创建Grandsoft.GEA,依次下去
sEval +="if (typeof(" + sNS + ") == 'undefined') " + sNS + " =new Object();"
}
if (sEval !="") eval(sEval);
}
// 调用的例子
Namespace.register("Grandsoft.GEA");
Namespace.register("Grandsoft.GCM");
解释:以上代码中方法1和方法2的实现原理是一样的,只是第二种写法更简洁一些。第三种方法直接使用Microsoft AJAX Library 中的registerNamespace方法,如果项目当中没有引用这个库也可以使用方法四中的自定义实现。
[By theway]:现在很多第三方的Js框架都提供注册命名空间的API,你只需要利用Google相信你能得到的比这里的多。