在开发中,我们做java后台开发的,普遍都是擅长后端语言,对于javascript却不甚理想。偶尔我们会去w3c看看javascript的基本语法,但是每次都是学习不到位,用起来感觉总是马马马虎虎。在最近的开发中,这个老大难的问题又出现在了我面前。一开始,继续忽略,一张手就是定义了一个function xxx(){}。但是噩梦往往都是因为懒惰造成的,由于开发的需要,某个页面的js文件多大1000 line+,方法至少也是10+了。作为一个开发人员,目睹着自己的代码,尽然会超出自己的控制,显然让人不甘心啊,于是乎,重构了很多代码,但是依旧木有使用到面向对象的编程范式。在折腾了几天后,终于下定决心,使用面向对象的技术来重写整个js文件的代码。
要想用好面向对象,那么就需要理解面向对象的编程思想,作为一个后台开发人员,就不多说了,面向对象最主要的四个特性:继承,多态,封装,抽象。更加详细的内容,请参考面向对象的相关数据。
在java中,我们使用class来定义一个类,用于规范某种对象的特性(属性,方法),当该class定义好之后,就可以使用new ClassXxx()的方式来实例化一个对象,并且所有同一个class生成的对象都具备着一致的特性(属性,方法),后期不能动态的给对象添加属性与方法。对于这种后期模式的开发语言来说,优势非常明显,那就是所有对象的属性与方法都确定了下来,并且可以通过预编译,将class转换成二进制的机器码,所以在运行效率上会比较有优势。但是缺点也同样非常明显,比如需要编译源码,不能动态添加属性或者方法,开发效率较低等。
在javascript中,有如下几种数据类型,分别是:undefined,boolean,string,number,object,function。这里面,我们只关注object(对象)。对象是javascript的基本数据类型,将很多值聚合在一起,可以通过名字访问这些值。比如:{name:骆宏, age:20}。每一个对象除了保持自有的属性,通常还可以从一个称为原型的对象那里继承属性,通常情况下,会继承原型的方法。Javascript对象是动态的,可以动态的添加属性,或者删除属性,与java这类语言类型不一样。
在这里面,正方形对象继承了矩形,矩形继承了多边形,多边形继承了形状,而形状继承了Object。于是正方形对象就可以获得从Object.prototype+形状.prototype+多边形.prototype+矩形.prototype的所有属性(除了一些不可以继承的属性)。一个对象在寻找属性时,会首先查找自身是否有这个属性,如果找不到,就会去到该对象的原型中选中,如果也找不到,就会递归的往Object.prototype方向去寻找该属性。我们称之为该对象的继承关系的原型链(非严肃的说法,官方定义请查阅w3c的javascript规范)。
function createCar() { var oTempCar = new Object; //实例化一个对象 oTempCar.color = "blue"; //给对象赋值 oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { //属性的值可以为方法 alert(this.color); }; return oTempCar; //返回对象 } 使用方式:var car = createCar();
function Car(sColor,iDoors,iMpg) { this.color = sColor; //使用this来表示当前的对象 this.doors = iDoors; this.mpg = iMpg; this.showColor = function() { //每次创建对象时,都需要创建一个匿名函数 alert(this.color); }; } 使用方式:var car = new Car(“红色”, “是”, “是”);
在方式中2,每次创建一个Car时,都是实例化this.showColor,但是通过观察,我们可以发现,这其实是一个方法,只需要实例化一次即可。为了改进上述代码,使用原型的方式重写上诉的代码,如下:
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); } Car.prototype.showColor = function() { //将该方法放在原型的属性上,所有的Car //对象都从这个原型对象继承该属性 alert(this.color); };
function Car(sColor,iDoors,iMpg) { this.color = sColor; this.doors = iDoors; this.mpg = iMpg; this.drivers = new Array("Mike","John"); if (typeof Car._initialized == "undefined") { //给构造方法添加一个属性,记录下第 //一次实例化 Car.prototype.showColor = function() { alert(this.color); }; Car._initialized = true; //标志着该对象已经实例化过了 } }
5混合工厂模式
function Car() { var oTempCar = new Object; oTempCar.color = "blue"; oTempCar.doors = 4; oTempCar.mpg = 25; oTempCar.showColor = function() { alert(this.color); }; return oTempCar;
在实际开发中,那么我们该使用哪种方式呢?在目前,原型方式与动态原型方式都比较流行,个人推荐动态原型的方式,因为可以将所有代码集中在构造函数内部,避免代码分散在多个地方。
function StringBuffer(){ this.strs = []; //用数组来缓存所有的内容 if(typeof StringBuffer._initialized == "undefined"){ StringBuffer._initalized = true; //已经初始化过了,不需要重复初始化 StringBuffer.prototype.append = function(str){ this.strs.push(str); return this; }; //覆写toString,避免使用Object.toString StringBuffer.prototype.toString = function(seperator){ if(!seperator){ seperator = ""; } return this.strs.join(seperator); }; } }
function Map(){ this.elements = new Array(); if(typeof Map._initialized == "undefined"){ Map._initialized = true; //记录下已经实例化的标识符 Map.prototype.size = function(){ return this.elements.length; }; Map.prototype.isEmpty = function(){ return this.elements.length == 0; }; Map.prototype.clear = function(){ this.elements = new Array(); }; Map.prototype.put = function(key, value){ if(key == null || value == null) throw TypeError(); var exists = false; if(this.isEmpty()){ this.elements.push({"key": key, "value": value}); }else{ for(var i=0; i<this.elements.length; i++){ if(this.elements[i].key == key){ //override this.elements[i].value = value; exists = true; } } if(!exists){ this.elements.push({"key": key, "value": value}); } } }; Map.prototype.get = function(key){ if(key == null) throw TypeError(); if(this.isEmpty()) return null; for(var i=0; i<this.elements.length; i++){ if(this.elements[i].key == key) return this.elements[i].value; } return null; }; Map.prototype.remove = function(key){ if(key == null) throw TypeError(); if(this.isEmpty()) return false; for(var i = 0; i < this.elements.length; i++) { if(this.elements[i].key == key) { this.elements.splice(i, 1); //delete elements[i] return true; } } return false; }; Map.prototype.containsKey = function(key){ if(key == null) throw TypeError(); if(this.isEmpty()) return false; for(var i = 0; i < this.elements.length; i++) { if(this.elements[i].key == key) { return true; } } return false; }; Map.prototype.containsValue = function(value){ if(value == null) throw TypeError(); if(this.isEmpty()) return false; for(var i = 0; i < this.elements.length; i++) { if(this.elements[i].value == value) { return true; } } return false; }; Map.prototype.keys = function(){ if(this.isEmpty()) return []; var keys = new Array(); for(var i=0; i<this.elements.length; i++){ keys.push(this.elements[i].key); } return keys; }; Map.prototype.values = function(){ if(this.isEmpty()) return []; var values = new Array(); for(var i=0; i<this.elements.length; i++){ values.push(this.elements[i].value); } return values; }; Map.prototype.toString = function(){ if(this.isEmpty()) return "[{}]"; var sb = new StringBuffer(); sb.append("["); for(var i=0; i<this.elements.length; i++){ sb.append("{").append(this.elements[i].key).append(",").append(this.elements[i].value).append("}"); if(i != this.elements.length - 1){ sb.append(","); } } sb.append("]"); return sb.toString(); }; } }
function Collection(){ this.elements = new Array(); } Collection.prototype.add = function(obj){ if(obj == null) throw TypeError(); this.elements.push(obj); return true; }; Collection.prototype.addAll = function(objs){ if(objs == null) throw TypeError(); this.elements = this.elements.concat(objs); return true; }; Collection.prototype.get = function(index){ this._checkIndex(index); if(this.isEmpty()) return null; return this.elements[index]; }; /** * 删除下表为index的元素 * */ Collection.prototype.removeIndex = function(index){ this._checkIndex(index); this.elements.splice(index, 1); //delete elements[i-1] }; /** * 删除某个元素 * 这里面会从下表0开始便利,删除第一个匹配的元素 * */ Collection.prototype.removeObj = function(obj){ if(this.isEmpty()) return false; for(var i=0; i<this.elements.length; i++){ if(this.elements[i] == obj){ this.elements.splice(i-1, 1); //delete elements[i] } } }; Collection.prototype.contains = function(obj){ if(obj == null) throw TypeError(); if(this.isEmpty()) return false; for(var i=0; i<this.elements.length; i++){ if(this.elements[i] == obj){ return true; } } return false; }; Collection.prototype.containsAll = function(objs){ if(objs == null) throw TypeError(); if(objs.length > this.elements.length) return false; for(var i=0; i<objs.length; i++){ if(this.contains(objs[i]) == false){ return false; } } return true; }; Collection.prototype.size = function(){ return this.elements.length; }; Collection.prototype.isEmpty = function(){ return this.elements.length == 0; }; Collection.prototype.clear = function(){ this.elements = new Array(); }; Collection.prototype._checkIndex = function(index){ if(index < 0 || index >= this.elements.length){ throw new Error("index must be in [0, " + this.elements.length + ")"); } }; /** * 默认输入的格式如下: * [element1,element2,element3,...,elementx] * * 如果客户端需要获取类型数组join的效果,直接传入分隔符即可 * 比如collection的内容为:a,b,c,默认的toString为[a,b,c],如果想要获得:abc,传入"";想要获得a,b,c,传入",",想要获得a b c,传入" " * * */ Collection.prototype.toString = function(separator){ if(this.isEmpty()) return ""; if(separator != null){ return this.elements.join(separator); }else{ var sb = new StringBuffer(); sb.append("["); for(var i=0; i<this.elements.length; i++){ sb.append(this.elements[i]); if(i != this.elements.length - 1){ sb.append(","); } } sb.append("]"); return sb.toString(); } };
/** * Collection的子类 * 提供了如下几个增强方法: * set(index, obj) * subList * */ function List(){ this.elements = new Array(); //这个属性不能使用Collection中的elements,因为原型对象都是collection,那样子会出现bug } //List.prototype修改为new Collection对象,等价于继承了Collection的所有//属性 List.prototype = new Collection(); /** * 在第几个位置插入元素 * */ List.prototype.set = function(index, obj){ this._checkIndex(index); this.elements.splice(index, 0, obj); //add elements[i] return true; }; /** * 获取子链表 * */ List.prototype.subList = function(start, end){ this._checkIndex(start); this._checkIndex(end); if(start >= end){ throw new Error("start must be less than end."); } var subList = new Array(); var index = 0; for(var i=start; i<end; i++){ subList[index++] = this.elements[start]; } return subList; };
/** * Collection子类 * 因为Set中,元素不能重复,所以这里面override了Collection的add,addAll方法 * */ function Set(){ this.elements = new Array(); //这个属性不能使用Collection中的elements,因为原型对象都是collection,那样子会出现bug } Set.prototype = new Collection(); //继承Collection /** * 因为Set不允许重复元素存在,所以这里面需要override add,addAll方法 * */ Set.prototype.add = function(obj){ if(this.contains(obj)){ return false; }else{ this.elements.push(obj); return true; } }; Set.prototype.addAll = function(objs){ if(objs == null) throw new TypeError("objs can't be null"); for(var i=0; i<objs.length; i++){ if(this.contains(objs[i])){ continue; }else{ this.elements.push(objs[i]); } } }; /** * 在第几个位置插入元素 * */ Set.prototype.set = function(index, obj){ this._checkIndex(index); if(this.contains(obj)){ return false; }else{ this.elements.splice(index, 0, obj); //add elements[i] return true; } };
每一个后端开发人员,都应该详细的解读下javascript,而不是大概知道怎样。在实战中,可以提高开发人员的开发效率,代码的可维护性也变得不再那么狼狈不堪。下次再写javascript代码时,当你一顺手写下function xxx(){}时,请再思考下吧。
关于面向对象技术,大家可以参考:
1 Jquery源码分析
http://www.cnblogs.com/aaronjs/p/3279314.html
2 w3c面向对象技术
http://www.w3school.com.cn/js/pro_js_object_oriented.asp
3.IBM关于javascript面向对象的讲解
http://www.ibm.com/developerworks/cn/web/1304_zengyz_jsoo/