小结JS中的OOP(上)
前言:大家都知道,OOP有三大特性:封装,继承,多态。下面是自己对这三个特性的理解:
封装:把属性与方法整合到某种数据类型中。目的是让类的使用者按类的编写者的意愿去使用类。在封装过程中会一般会做两件事:
① 隐藏内部实现 ② 对外提供接口(访问权限控制)。
继承:共享父类的属性与方法
多态:不同对象执行相同的操作,可以产生不同的结果。关于多态要注意两点:
① 在子类以父类的形式存在时,不能使用子类的属性与方法
② 子类在工作时,采用自己的实现方式。
下面我们以java为例子看看OOP的三个特性。
/** * 定义类:Animal */ public class Animal { private String cate; public String getCate() { return cate; } public void setCate(String cate) { this.cate = cate; } public void shout() { System.out.println("Animal shouted"); } } /** * 定义类:Dog,并让其从Animal继承 */ class Dog extends Animal { @Override public void shout() { System.out.println("dog shouted"); } } /** * 定义类:Collie,并让其从Dog类继承 */ class Collie extends Dog { @Override public void shout() { System.out.println("collie shouted"); } public void graze() { System.out.println("collie graze"); } }
//测试代码 public class OOPTest { public static void main(String[] args) { //封装 Animal animal = new Animal(); animal.shout();//Animal shouted //继承: Dog中并没有声明cate域,也没有对应的setter/getter Dog dog = new Dog(); dog.setCate("dog"); System.out.println(dog.getCate());//dog //多态 Animal dog2 = new Collie(); dog2.shout();//collie shouted //dog2.graze();error! 在子类以父类的形式存在时,不能使用子类的属性与方法 ((Collie)dog2).graze();//进行向下转型后可以运行,打印出:collie graze } }
一: OOP在JS中的实现
但对于Js而言,目前还没有实现class关键字,也没有private,protected,public权限修辞符。但我们可以使用闭包来实现封装:
/** * 定义Animal类型 * @returns {Animal} * @constructor */ function Animal() { var _cate; //防止直接调用Animal构造函数在window上新增属性 if(this instanceof Animal) { this.setCate = cateSetter; this.getCate = cateGetter; this.shout = shout; } else { return new Animal; } //使用函数名cateSetter方便理解 function cateSetter(cate) { _cate = cate; } function cateGetter() { return _cate; } function shout() { console.log('Animal shouted'); } }
//使用: var animal = new Animal(); animal.shout();//animal shouted //直接不能访问到cate属性,只有通过对外提供的方法(setCate/getCate)去访问 animal.setCate('animal~~'); console.log(animal.getCate());//animal~~
JS中的继承是通过prototype来实现的,现在新增Dog类型,并从Animal继承:
/** * 定义Dog类型,并让其从Animal继承 * @constructor */ function Dog() {} Dog.prototype = new Animal(); //让instanceof运行符可以正常工作 Dog.prototype.constructor = Dog;
//测试: var dog = new Dog(); //dog具有了方法shout, cate的getter/setter dog.setCate('dog'); console.log(dog.getCate());//dog dog.shout();//animal shouted console.log(dog instanceof Dog);//true console.log(dog instanceof Animal);//true
接着定义类型Collie,并实现多态:
/** * 定义Collie类型,并让其从Dog继承 * @constructor */ function Collie(){} //让Collie从Dog继承 Collie.prototype = new Dog; Collie.prototype.constructor = Collie; //为Collie新增graze方法 Collie.prototype.graze = function() { console.log('collie graze'); } //重写Collie的shout方法 Collie.prototype.shout = function() { console.log('collie shouted'); }
//测试 var collie = new Collie(); collie.setCate('collie'); console.log(collie.getCate());//collie console.log(collie instanceof Collie);//true console.log(collie instanceof Dog);//true console.log(collie instanceof Animal);//true //多态:相同的方法,产生了不同的行为 collie.shout(); //collie shouted
完整的OOP实现代码:
/** * 定义Animal类型 * @returns {Animal} * @constructor */ function Animal() { var _cate; //防止直接调用Animal构造函数 if(this instanceof Animal) { this.setCate = cateSetter; this.getCate = cateGetter; this.shout = shout; } else { return new Animal; } //使用函数名cateSetter方便理解 function cateSetter(cate) { _cate = cate; } function cateGetter() { return _cate; } function shout() { console.log('animal shouted'); } } /** * 定义Dog类型,并让其从Animal继承 * @constructor */ function Dog() {} Dog.prototype = new Animal(); //让instanceof运行符可以正常工作 Dog.prototype.constructor = Dog; /** * 定义Collie类型,并让其从Dog继承 * @constructor */ function Collie(){} //让Collie从Dog继承 Collie.prototype = new Dog; Collie.prototype.constructor = Collie; //为Collie新增graze方法 Collie.prototype.graze = function() { console.log('collie graze'); } //重写Collie的shout方法 Collie.prototype.shout = function() { console.log('collie shouted'); }
二: 缺点
这种构造函数(闭包)+prototype的实现的缺点:
1. 使用闭包模拟私有属性时,造成同一类型的多个实例共享一个相同闭包变量(Dog.prototype = new Animal() 只生成了一个闭包变量)
2. 每次实例化一个子对象时,都先要实例化一个父对象
3. 不能在子对象上调用父对象上的同名方法
4. 引用类型的共享Bug(在prototype上面的引用类型都会有这个问题,因为各个function的prototype是一个指针,实际的prototype对象在堆中只有一份内存分配)
5. 封装不优雅,很散乱
缺点1的测试代码:
var dog = new Dog(); var dog2 = new Dog(); //dog1,dog2共享闭包变量_cate,明显这不合适 dog.setCate('dog1'); console.log(dog.getCate());//dog1 dog2.setCate('dog2'); console.log('+++'+dog.getCate());//dog2 console.log('+++'+dog2.getCate());//dog2