面向对象编程(OOP)的主要原则就是使用class来创建一个类,这个类以某种方式实现并满足继承,多态和封装。在javascript的OOP中,没有类的概念,只有对象。对象必须能封装一些功能并继承方法和属性,在javasript中我们仅仅只关心继承和封装。
正如你已经知道,javascript中处处都是对象。我们使用对象字面量和构造函数来创建对象。同样,你必须要知道的是在javascript中函数也是对象。
封装就是把一个对象的所有功能包裹起来,这样对象的内部工作(它是如何构建-它的方法和属性)相对于后面的应用就隐藏了。这就使得我们能够从其他的应用中抽象(隔离)出指定对象的特殊功能集合。继承就是一个对象从父对象中继承方法和属性。
封装和继承这两个概念是很重要的,因为他们使得我们通过代码重用,可扩展结构和抽象化来构建应用。这样就使得应用是很容易维护的,高效的和可扩展的。
javascript中的OOP,它最重要的两个原则就是代码创建模式(封装)和代码重用模式(继承)。当我们创建应用时,你会构建很多对象。创建这些对象的方式有很多:你可以使用最基本的对象字面量方式,例如:
1 var myObj={name:"deng",profession:"developer"};
你也可以使用构造函数:
1 function People(name,profession){}//People()是一个构造函数,我们将使用new关键字来调用它 2 var deng=new People("deng","developer")//deng是一个通过构造函数新建的对象
当我们想要给自己的对象添加属性和方法时,当我们想要封装对象的功能时,我们该如何来创建对象?当你创建代码时。我们该如何从父对象中继承大部分方法和属性(有相似功能),而且他们要能够拥有自己的属性和方法?
javascript中的封装 |
为了实际的使用javascript中的OOP,我们将会使用在本文学到的方法和原则来创建一个面向对象的考试应用。我们的考试应用将会有一个user(user构造函数)来执行测试。执行测试的每一个user都会有许多相同的属性:每一个user都会有name,score,email。这些都是user对象的属性。此外,每一个对象应该能显示姓名,保存分数,变更邮件,这些都是对象的方法。因为,我们希望所有的user对象拥有这些属性和方法。
OOP的封装—把一个对象的内部运行方式放在对象中。为了实现javascript中的封装,我们必须定义这个对象的核心属性和方法。要想做到这些我们使用javascript中最好的模式:构造函数和原型的联合模式。
构造函数和原型模式的具体实现:
1 function User(name,email){ 2 this.name=name; 3 this.email=email; 4 this.scores=[]; 6 } 7 User.prototype={ 9 showScore:function(theScoreToAdd){ 10 this.scores.push(theScoreToAdd) 11 }, 12 showName:function(){ 14 return this.name; 15 }, 16 changeEmail:function(newEmail){ 17 this.emai=newEmail; 18 return "new email is:"+this.email; 19 } 20 21 }
创建user函数的实例:
1 1 // a User 2 2 firstUser=new user("deng","[email protected]"); 3 3 firstUser.changeEmail("[email protected]"); 4 4 firstUser.showScore(15); 5 5 firstUser.showScore(10); 6 6 firstUser.showNameAndScores();//deng scores:15,10
正如你看到的,我们在user函数中封装了User的所有功能。这样User的每一个实例都能使用原型中的方法(像changeEmail)并定义他们的实例属性(name和email)
javascript中的继承 |
继承的实现允许我们从父函数中继承功能,这样我们就能很容易的重用代码并扩展对象的功能。对象既能使用继承的功能也能拥有他们特定功能。在javascript中实现继承的最好模式莫过于构造函数和原型链的组合。在讨论继承的同时我们顺便讲解一下javascript中基于函数的面向对象成员和方法的访问域问题(也就是共有,私有,特权)
首先来看看javascript是如何实现这些访问域问题的:
- 共有成员包括共有方法和共有属性,在javascript中写在构造函数中的this属性为共有属性。写在原型链上的方法为共有方法。每一个实例和子对象都可以继承共有属性和方法。只不过方法可以公用,属性的值会随着传递给实例参数的不同而不同。
- 私有成员包含私有方法和私有属性,在javascript中写在构造函数中var变量为私有变量,独立函数为私有方法。在私有方法和特权方法中可以访问私有属性,原型中共有方法不可以访问。实例对象和子对象不可以访问私有属性和私有方法
- 特权方法是写在构造函数中的this函数,他可以访问私有变量和私有方法。实例对象和子对象可以访问。(子对象可以利用特权方法间接的访问父对象的私有成员)
- 静态成员包含静态属性和静态方法,在javascript中为直接绑定在父对象构造函数上的属性和方法。实例可以方法,子对象不可以访问
具体的代码如下:
1 function Animal(name){ 2 this.name=name;//共有属性 3 var that=this;//私有属性 4 function alertMessage(){//私有方法 5 alert(that.name); 6 } 7 alertMessage() 8 this.appendAge=function(age){//特权方法 9 this.name+=":"+age; 10 alertMessage(); 11 } 12 } 13 Animal.prototype.getName=function(){//共有方法 14 alert(this.name); 15 } 16 Animal.gender=male;//静态属性 17 Animal.getGender=function(){//静态方法 18 alert(this.gender); 19 }
下面我们来看看javascript是如何使用构造函数和原型链的组合来实现继承的
首先利用构造函数来构建一个父对象,并在其原型上添加方法:
1 function Animal(name){ 2 this.name=name;//共有属性 3 } 4 Animal.prototype.getName(){//共有方法 5 alert(this.name); 6 }
接着构建子对象,继承父对象的属性和方法并添加自己的属性和方法:
1 function Dog(name,color){ 2 Animal.call(this,name);//继承属性 3 this.color=color;//自己的属性 4 } 5 Dog.prototype=new Animal();//继承方法 6 Dog.prototype.constructor=Dog; 7 Dog.prototype.getColor=function(){//构建自己的方法 8 alert(this.color); 9 }
最后实例化子对象,调用父对象和自己的方法:
1 var dog1=new Dog("xiaohua","black"); 2 dog1.getName();//xiaohua 调用父对象方法 3 dog1.getColor();//black 调用自身方法
在这里我们不得不解释一下为什么要使用构造函数和原型相结合的方式?而不是单独使用构造函数或者原型链模式。
对于构造函数模式,所有的属性写在构造函数里面的同时,所有的方法也都写死在构造函数中,这样就无法抽象复用和重构这些方法了。
对于原型链模式,若果构建两个实例,那么两个实例都会共享父对象原型中的引用类型(如数组)。一个实例改变这个引用类型的值后会影响另一个实例。而实际上我们希望实例是独立的互不干扰的。此外不能向父对象的构造函数中传递参数,从而不能继承构造函数中的属性。
构造函数和原型链的组合模式,把共有属性写在构造函数里,把共有方法写在原型上。利用构造函数模式可以实现对父对象构造函数中属性的继承,利用原型链可以继承原型中的方法和属性,实现代码复用。