因为本人水平有限,因此有些高手以为如今写的内容偏容易,要一点点来嘛,今天和你们学习或者复习一下javascript的继承。我也就是尽可能写吧······javascript
javascript的继承其实主要就是经过原型链来实现的,原型链咱们以前已经和你们一块儿学习过,这里就不浪费你们的时间了。javascript连类都没有,还说啥继承呢,这仍是模拟类的继承。《javascript高级程序设计》上分红了几个方式,有的书上分为类式继承,原型式继承,这就是模拟其余语言类的继承,还有什么用掺元类实现的,在这里都和你们说下。java
在这里在说一下原型链的概念,由于javascript的继承都是经过原型链来模拟的,因此在这里帮助你们理解一下。咱们知道,每个构造函数都有一个原型对象,这个原型对象中包含一个指向构造函数的指针,同时每个实例都有一个指向原型对象的内部指针。好好想一下这个关系,当咱们访问一个实例的属性时,如今实例中查找,没找到经过内部指针去原型中查找,仍是没有再经过原型的内部指针查找原型的原型对象,一直迭代下去。嗯,就是这样,app
如今咱们知道了原型链是这样的话,咱们想要继承的实现,咱们要把父类的属性和方法放在子类的原型对象中就能够了,这样new出来的实例就会查找原型中的属性和方法了,那这样就能够实现继承了,那咱们要怎样将父类的属性和方法放在子类的原型中呢?咱们重写子类的原型对象是否是就能够了,这里有个选择的问题,咱们可让子类的原型对象指向父类的原型对象,也能够指向一个父类的实例,假如如今咱们将它指向了父类的原型对象,咱们知道父类构造函数中的属性就不会在子类中获得继承,看个例子就知道了函数
//父类 function Animal(){ this.className = "动物"; } //父类原型 Animal.prototype.getClassName = function(){ console.log(this.className ); } //子类 function Cat(){} //重写子类原型 Cat.prototype = Animal.prototype; var Tom = new Cat(); Tom.getClassName();//undefined
其实这是另一种方式的雏形,寄生组合模式的雏形,下文我会讲到,这里暂且放过。学习
咱们如今再看看指向一个实例对象的状况this
//父类 function Animal(){ this.className = "动物"; } //父类原型 Animal.prototype.getClassName = function(){ console.log(this.className ); } //子类 function Cat(){} //重写子类原型 Cat.prototype =new Animal(); var Tom = new Cat(); Tom.getClassName();//动物
这下子你们会明白了,实例是把构造函数中this的属性和原型中的属性结合起来了,若是指向原型对象那么构造函数中的属性就不会被继承。spa
这就是继承的最基础和最核心的东西,这还不完善,从新原型对象咱们知道,要增长一个constructor属性,这里不添加了,不明白的看以前的原型与原型链的那篇文章。javascript用instanceof来判断实例与原型的关系,只要实例和原型链中出现过构造函数,就会返回trueprototype
console.log(Tom instanceof Cat);//true console.log(Tom instanceof Animal);//true console.log(Tom instanceof Object);//true
原型链的问题:其实这个和构造对象原型链的问题是同样的,主要是原型对象的属性是一个引用类型,会引发一些问题。这是由于全部的实例共用原型对象的属性,当属性为引用类型时,任何一个实例对这个对象的修改会影响全部的实例。例子来了设计
//父类 function Animal(){ this.className = "动物"; this.colors = ["black"]; } //父类原型 Animal.prototype.getClassName = function(){ console.log(this.className ); } //子类 function Cat(){} //重写子类原型 Cat.prototype = new Animal; var Tom = new Cat(); console.log(Tom.colors);//"black" Tom.colors.push("yellow"); var Garfield= new Cat(); console.log(Garfield.colors);//"black", "yellow"
原型链还有一个问题就是,不能向父类的构造函数中传递参数,就是这样的我想给每个子类起一个名字,这里是没法办到的,由于我给父类的名字都挂在了子类的原型上了。例如指针
//父类 function Animal(name){ this.name = name; } //子类 function Cat(){} //重写子类原型 Cat.prototype = new Animal("无名氏"); var Tom = new Cat(); console.log(Tom.name);//无名氏 var Garfield= new Cat(); console.log(Garfield.name);//无名氏
这里要起一个名字,全部实例都会影响,因此说没有办法在不影响全部实例的状况下给父类传递参数。
这个方式能够解决上面的问题,咱们知道上面的原型链的方法是子类的原型对象指向了父类的实例,就是把全部父类的属性都挂在了子类的原型对象上,全部就会出现全部实例共享同一个属性引起的问题,那咱们能够换一种思路,咱们把一些父类的属性放在子类的构造函数中,就是在子类的构造函数中的this添加属性,这样就不须要全部的属性都弄到子类的原型对象上了,这样每一个子类的实例都会有本身的属性和方法,不用共享原型中的属性了。这是一个简单的思路,咱们在子类的构造函数中给this添加父类的属性,咱们想到了以前的apply和call方法,看例子
//父类 function Animal(){ this.className = "动物"; this.colors = ["black"]; } //子类 function Cat(){ Animal.call(this);//至关于 this.className = "动物";this.colors = ["black"]; }var Tom = new Cat(); console.log(Tom.colors);//"black" Tom.colors.push("yellow"); console.log(Tom.colors);//"black", "yellow" var Garfield= new Cat(); console.log(Garfield.colors);//"black"
这时候你也能够给父类传参数了,由于这些属性都添加了子类构造函数中了,看例子
//父类 function Animal(name){ this.name = name; } //子类 function Cat(name){//传入参数 Animal.call(this,name); } var Tom = new Cat("Tom"); console.log(Tom.name);//Tom var Garfield= new Cat("Garfield"); console.log(Garfield.name);//Garfield
借用构造函数问题:这个又回归到了构造函数模式上出现的问题了,咱们全部的方法都是在构造函数上定义的,没法复用。
原型链和借用构造函数结合一块儿,使用原型链实现原型属性和方法的继承,使用借用构造函数实现对实例属性的继承,这样经过在原型上定义方法实现函数的复用,又能保证每一个实例都有本身的属性。上例子
//父类 function Animal(name){ this.name = name; this.colors = ["black"]; } //父类原型 Animal.prototype.getName = function(){ return this.name; } //子类 function Cat(name,age){//传入参数 Animal.call(this,name); this.age = age; } Cat.prototype = new Animal("无名氏"); Cat.prototype.constructor = Cat; Cat.prototype.getAge = function(){ return this.age; } var Tom = new Cat("Tom",20); console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20 Tom.colors.push("red"); var Garfield= new Cat("Garfield",21); console.log(Garfield.getName() +" : "+ Garfield.getAge());//Garfield : 21 console.log(Garfield.colors);//black
这是最经常使用的继承方式。有些书叫这种为类式继承,把这种经过构造函数方式来实现继承的叫作类式继承,上面的咱们能够把Animal当作一个类,经过构造函数原型链之间的关系实现继承。
这种没有方式类的概念,也就是没有使用构造函数来实现,就是使一个函数的原型指向一个原有的对象,经过这个函数来建立一个新的对象。上例子
function create(obj){ function F(){}; F.prototype = obj; return new F(); } var Animal = { name : "无名氏", colors : ["black"] } var Tom = create(Animal); console.log(Tom.name);//"无名氏"
这就是原型继承,能够看出它存在很多问题,只有在特定的状况下可使用该方式,没法判断类与实例之间的关系,共享引用类型属性的问题等等。
若是知道了上面的知识,这个很好理解了,咱们在建立对象那章的时候,就提到了寄生构造对象,所谓的寄生就是在函数的内部经过某种方式来加强对象以后,在返回这个对象,那么寄生式继承也相似
function create(obj){ function F(){}; F.prototype = obj; return new F(); } var Animal = { name : "无名氏", colors : ["black"] } function getCat(obj){ //建立对象继承自obj var newObj= create(obj); //增长方法 newObj.getName = function(){ return this.name; }; return newObj; } var Tom = getCat(Animal); console.log(Tom.getName());//"无名氏"
这个看看就知道是怎么回事了,在函数内部继承一个对象以后,又增长了方法,以后返回这个对象。
组合继承上面咱们说完了,组合继承还有一个问题就是,任什么时候候会调用两次父类的构造函数,一次是建立子类的原型的时候,另外一次是在子类的构造函数内部。看看就知道了
//父类 function Animal(name){ this.name = name; this.colors = ["black"]; } //父类原型 Animal.prototype.getName = function(){ return this.name; } //子类 function Cat(name,age){//传入参数 Animal.call(this,name);//第二次调用Animal() this.age = age; } Cat.prototype = new Animal("无名氏");//第一次调用Animal() Cat.prototype.constructor = Cat; Cat.prototype.getAge = function(){ return this.age; } var Tom = new Cat("Tom",20); console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20
咱们分析一下这个过程:第一次调用的时候,在Cat.prototype对象上添加了name和colors属性,添加到了子类的原型对象上,第二次调用父类的构造函数时,是将name和colors属性添加到了子类的实例上,也就是说子类的原型对象和实例中都有了这两个属性,实例中的属性屏蔽了原型中属性。
咱们想一下怎样才能解决这问题呢?咱们能够这样,让子类的原型对象直接指向父类的原型对象,就像文章开始咱们说的那么选择的问题,咱们此次使用父类的原型对象,这里可使用,是由于咱们结合使用了借用构造模式,能够继承父类构造函数中的属性了,看看例子先
//父类 function Animal(name){ this.name = name; this.colors = ["black"]; } //父类原型 Animal.prototype.getName = function(){ return this.name; } //子类 function Cat(name,age){//传入参数 Animal.call(this,name);//第二次调用Animal() this.age = age; } Cat.prototype = Animal.prototype;//指向父类的原型 Cat.prototype.constructor = Cat; Cat.prototype.getAge = function(){ return this.age; } var Tom = new Cat("Tom",20); console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20
这样是能够的,可是咱们这里就有问题了,咱们在给子类的原型指定constructor属性时,修改了父类的constructor属性,
console.log(Animal.prototype.constructor); /* function Cat(name,age){//传入参数 Animal.call(this,name);//第二次调用Animal() this.age = age; } */
因此咱们不能直接这样指向父类的原型,要经过一种中转,使子类的原型和父类原型指向不一样的对象,就是使用原型模式继承,建一个对象,这个对象的原型指向父类的原型,以后子类的原型对象再指向这个对象,这样就使子类的原型和父类原型指向不一样的对象。
//父类 function Animal(name){ this.name = name; this.colors = ["black"]; } //父类原型 Animal.prototype.getName = function(){ return this.name; } //子类 function Cat(name,age){//传入参数 Animal.call(this,name);//第二次调用Animal() this.age = age; } function F(){}; F.prototype = Animal.prototype; Cat.prototype = new F(); Cat.prototype.constructor = Cat; /*封装起来就是这样 function create(obj){ function F(){}; F.prototype = obj; return new F(); } function inheirt(sub,sup){ var pro = create(sup.prototype); pro.constructor = sub; sub.prototype = pro; }
inherit(Cat,Animal); */ Cat.prototype.getAge = function(){ return this.age; } var Tom = new Cat("Tom",20); console.log(Tom.getName() +" : "+ Tom.getAge());//Tom : 20
就这样按部就班,咱们就完成了javascript的继承的内容。
复制继承,顾名思义就是一个一个复制原型对象的属性,将给定的类的原型的属性循环复制到指定的原型中,
function inherit(subClass,supClass){ for(var name in supClass.prototype){ if(!subClass.prototype[name]){ subClass.prototype[name] = supClass.prototype[name] } } } function Animal(){} Animal.prototype.aname = "无名氏"; function Cat(){}; inherit(Cat,Animal); var Tom = new Cat(); console.log(Tom.aname);//无名氏
就是复制继承,参元类就是经过这种方式来实现的,参元类是包含了一系列的通用方法,若是哪一个类想用这些方法就适使用这种方式来继承参元类。
就这样按部就班,咱们就完成了javascript的继承的内容,继承这块的知识初学者要多看书,《javascript高级程序设计》的继承部分,多看几遍,本身好好想一想它们的优缺点,就知道该如何设计继承了,本身在谢谢实例就会明白这些方式是大神们怎么想出来的。