面向对象编程:便是把可以完成独立完成一部分功能的代码封装在一块儿,组成一个类。javascript
举个例子来讲:java
这里有一把枪, 枪的种类不少,有步枪,机关枪,阻击枪....。可是不管怎么说,这些都是枪的概念,若是把这种抽象的概念剥离出来,就是咱们常常说的“类”。那么枪有什么特色呢? 威力大小,型号,长度,子弹型号,能承载子弹的数量,枪口半径......ok! 这些一切的一切都是为了描素枪,把这些抽象出来,就组成了“枪类”的属性。枪又能干什么呢? 瞄准,开火,....这些描素都是枪的功能-----把这些抽象出来,即组成了一个类的方法。编程
因此,整体来讲,面向对象的编程便是把一个程序模块化,每一个模块承载一部分功能,各个模块协同合做,维持程序的正常执行;模块化
而所谓的类的组成,无外乎三个部分:这个类的名称(对应着例子中的“枪”),这个类的属性(对应着特色),这个类的方法(对应着功能)。函数
就像咱们描素一我的同样,无外乎,描素一我的的特色以及人的能力。因此,现实生活中的人,在程序中也能够抽象成类。this
举个例子来讲明三者之间的关系:spa
//1.Person是一个类的名字,定义的是一我的,对这我的的描述通常就是姓名,年龄。
var Person = function (name, age) {
//Person类的属性 this.name = name; this.age = age; }
//Person类的方法 Person.prototype.greet = function () { console.log('hello'); }
//这是一个类的实例化过程,lisi这里就是Person类的一个对象,也能够说其是Person类的一个实例
var lisi=new Person("lisi",18);
请记住:面向对象,全部的一些都是为了代码的复用。prototype
JS类的封装便是把类的属性和方法封装在类的内部. 若是只是简单的实现封装,那么能够有多种方法。好比下面的两种设计
//第一种方法
var Person = function (name, age) { this.name = name; this.age = age; this.greet = function () { console.log('hello'); } }
//第二种方法 var Person = function (name, age) { this.name = name; this.age = age; } Person.prototype.greet = function () { console.log('hello'); }
这两种方法,虽然在使用效果上是一致的,可是在第一种方法中,每次new 一个对象的时候都要为该对象添加一个函数greet----这样就没有作到代码的复用。因此在使用的时候,通常都是使用第二种方式---也就是所谓的组合式建立。因此通常咱们也推荐用第二种方式。3d
什么是重载呢? 重载的概念来源于强类型预言(C++,java,C#)中。咱们先来看一些java中的重载
class Person{ //java语言, 定义一个Person类,该类中存在greet方法的重载。 public String name; public int age; Person(String name,int age){ this.name=name; this.age=age; } public void greet(){ System.out.println("I am "+ this.name); } public void greet(String message){ System.out.println("I am "+ this.name+ "\n This is your"+message); } }
所谓的重载,就是一个同一个方法名在一个类中被出现了屡次。那么在该方法被调用的时候,编译器如何区分具体调用哪一个方法呢?
在强类型语言中,编译器先根据函数的名字选择函数,而后在根据调用时,形参和实参的类型,形参的个数和实参的个数是否一致来区分一个函数。
那么,问题来了....JS中的解释器是符合区分一个函数呢? ok...JS中解释器只是根据函数的名称来选择函数,而函数的形参并不在考虑的范围----由于在编译时没法根据肯定形参的类型,更没法肯定实参的类型。
既然,JS不支持重载,那么若是一个函数被重写了,会出现什么状况呢?
var Person = function (name, age) { this.name = name; this.age = age; this.greet = function () { console.log('hello'); } } var Person = function (name, age) { this.name = name; this.age = age; } Person.prototype.greet = function () { console.log('我被覆盖了'); } Person.prototype.greet = function (message) { console.log("我是重写的方法"); } var person=new Person("zhangsan",18); person.greet(); //我是重写的方法
根据上面的例子,能够看出,不管函数的参数是什么,只要函数同名,那么被调用的确定是最后一次被写的同名函数。
继承这个概念的来源也是面向对象的编程。JS引荐强类型预言中的继承作到这一点。因此咱们要从强类型语言中的继承来类推---JS中为何要这么设计。
在强类型语言中,在假设有两个类 A 、B....A是B的父类。实现以下:
class A{//父类的构造函数 protected int x; A(int x){ this.x=x; } } class B extends A{ protected int y; B(int x,int y){//子类的构造函数 super(x); //在子类的构造函数中,第一句话老是先调用父类的构造函数, //若是不写 则默认调用super();若是父类中不存在无参构造函数,则编译时会报错。
this.y=y;
}
public String getPoint(){
return "("+this.x+","+this.y+")"; //返回坐标(x,y)
}
}
从上面的这些咱们能够看出什么呢? 就是对象初始化的顺序...先初始化父类,在初始化子类。
初始化的时候顺序为: 父类的属性----》父类的方法-----》子类的属性-----》子类的方法。(咱们这里讲的是排除了类中静态数据和方法来讲,由于静态数据和方法的初始化,在类第一次被加载的时候就已经初始化完毕)
下面咱们看下,JS中是怎么实现和上述同样的功能的...
var A = function (x) { this.x = x; } var B = function (x, y) { A.call(this, x); //至关于第一种的super()函数。 this.y = y; }
//实现继承 function extend(subClass, superClass) { var prototype = Object.create(superClass); subClass.prototype = prototype; subClass.constructor = subClass; } extend(B, A); B.prototype.getPoint = function () { return '(' + this.x + ',' + this.y + ')'; }
上面这两段代码,撇开语言的特性来讲,他们实现的功能是等效的。只是第一种玩的是思想,第二种玩的是技巧。
OK!下面咱们开始详解JS的设计者为了JS语言能实现继承所作的努力。
全部的函数均有一个prototype属性。就是这个属性帮咱们作到了一些,首先要认识到一点这个属性是一个对象。
用上面咱们建立的一个Person函数详解,那么这个函数的prototype属性以下表示:
这是这个函数在刚开始被初始化时候的固有形式,后来执行了一句
Person.prototype.greet = function () { console.log('hello'); }
在这句执行完毕的时候,Person.prototype变化为
Person prototype | |
constructor | 指向Person函数 |
greet | (greet函数) |
解释了这么多,貌似并无解释继承是怎么实现的是吧....别慌...慢慢来!!!
来看一下,当一个函数被实例化的时候发生了什么?
var lisi=new Person("lisi",18); //看看Person实例化的对象发生了什么?
到这里,咱们看到了吧..当用new建立一个构造函数的对象的时候。这个对象会有一个【【__proto__】】内置属性,指向函数的prototype。------这就是对象lisi传说中的原型对象。
一个函数只有一个原型(prototype),这个函数在用new调用的时候会把这个原型赋值给当前对象的内置属性。
当查询一个对象的属性的时候,首先查询对象自己的属性,若是没有找到则根据对象内置属性层层向上查找。
因此一切的一切都归咎于,只要们修改一个函数的prototype属性,那么就能够实现继承。
下面图解,B继承A的过程。
//1. A类,和B类的构造函数
var A = function (x) { this.x = x; } var B = function (x, y) { A.call(this, x); //至关于第一种的super()函数。 this.y = y; }
//2.修改B的prototype,使其指向A的prototype
//实现继承 function extend(subClass, superClass) { var prototype = Object.create(superClass); subClass.prototype = prototype; subClass.constructor = subClass; } extend(B, A); B.prototype.getPoint = function () { return '(' + this.x + ',' + this.y + ')'; }
如此,便实现B类继承A类...关键点就在函数的prototype属性上。----在下一篇中会详解函数的prototype。
何谓多态?
首先必定要强调一点,只有在继承存在的状况下才可能出现多态! 这是为何呢..由于多态指的是子类覆盖父类的方法.....子类覆盖父类的方法,这种状况就是所谓的多态。
在java中的多态
public class Test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub B b=new B(1,2); String result=b.getPoint(); System.out.println(result); } } class A{ protected int x; A(int x){ this.x=x; } public String getPoint(){ return "我是父类"; } } class B extends A{ protected int y; B(int x,int y){ super(x); this.y=y; }
//多态,父类覆盖的方法 public String getPoint(){ return "我是子类"; } }
//输出结果 :我是子类
在JS中的多态状况,也是指的的子类的方法覆盖父类的方法。 上面的功能在JS中是这么实现的。
var A = function (x) { this.x = x; } A.prototype.getPoint = function () { return '我是子类'; } var B = function (x, y) { A.call(this, x); //至关于第一种的super()函数。 this.y = y; } //实现继承 function extend(subClass, superClass) { var prototype = Object.create(superClass); subClass.prototype = prototype; subClass.constructor = subClass; } extend(B, A); B.prototype.getPoint = function () { return '我是子类'; } var b = new B(1, 2); b.getPoint();
//输出结果 :我是子类
在上述代码执行完毕后,函数B的结构如图所示
B类在实例化的时候,B类的对象会拥有一个内部属性指向B.prototype.当该实例调用函数的时候,会先在该对象内部查询该函数是否存在,若是不存在则沿着内置属性查询原型对象,即B.prototype。若是找到此属性,则中止查询,不然会接着沿着内置属性所指向的对象,一直找到最上级为止。