原型链和继承
原型链:
它是继承的主要方法,其基本思想是让一个引用类型继承另外一个引用类型的属性和方法。
每个构造函数都一有一个prototype属性,它指向它的原型对象
原型对象包含了 该构造函数所创建的 所有实例 共享的 属性和方法
假如让一个原型对象等于另外一个类型的实例,那么该对象的实例可以访问另外一种原型的属性和方法。从而形成原型链
当我们调用一个对象的属性和方法时,首先在该对象自身属性查找,如果不存在就去自己的原型对象里找,如果还不存在,就向原型对象所关联的另外的原型对象里寻找。。如果有就返回,如果找到原型链的终点null时还没有找到,就返回undefined。
Object.prototype是所有对象的原型。
继承共有6种方法
原型链继承就是让子类型的原型等于父类型的实例,这种方法无法向超类型传递参数并且存在超类型包含引用类型的属性的问题,所以这种方法很少单独使用。
还有一种就是构造函数继承,就是在子类型当中使用call()来调用超类型的构造函数,这种方法解决了上述两种问题,但是函数复用无从谈起。
接下来是最常用的一种方法,组合使用原型链继承和构造函数继承,它综合了上述两种方法的优点,还解决了函数复用,引用类型属性,传递参数的问题。
其基本思想就是,使用原型链继承超类型的方法,使用构造函数继承超类型的属性。
这样一来,既可以让两个子类型的不同实例拥有自己的属性,又可以使用相同的方法了。
还有一种就是原型式继承,这个继承方法和Object.create(obj)方法一样,参数是所要继承的原型,但是它只是对传入的对象进行了浅赋值,所有引用类型的属性被所有实例所共享,如果只想让一个对象和另外的对象保持类型相同的情况下可以使用。
1、原型链继承:让子类的原型等于父类的实例
function Father() this.lastname="li"; } function Child(){ this.age=20; } Child.prototype=new Father(); var child=new Child(); child.lastname; //"li"
缺点:
- 包含引用类型值所带来的问题:如果超类型包含引用类型值的属性,子类型通过原型继承后,子类型的实例都会共享这个包含引用类型值的属性,如果在一个实例中修改,就会反映到另外一个实例上。
function Father(){ this.colors=["red","blue"] } function Child(){ this.age=20; } Child.prototype=new Father(); var c1=new Child(); var c2=new Child(); c1.colors //["red", "blue"] c1.colors.push("yellow"); c1.colors //["red", "blue", "yellow"] c2.colors //["red", "blue", "yellow"]
- 子类无法给父类传递参数。
鉴于此,很少单独使用原型链继承。
2、构造函数继承:通过call()或者apply()在子类型构造函数的内部调用超类型的构造函数
function Father(firstname){ this.lastname="li"; this.firstname=firstname; this.colors=["red","blue"] this.sayName=function(){ console.log("my name is "+this.lastname+this.firstname) } } function Child(firstname){ Father.call(this,firstname); this.age=20; } var c1=new Child("furong"); c1.sayName();// my name is lifurong var c2=new Child("rui"); c1.sayName();//my name is lirui
- 优点:在子类型构造函数中向超类型传递参数
- 缺点:因为方法都在构造函数内定义,函数复用无从谈起。所以构造函数继承也很少单独使用
3、组合继承:将原型链和借用构造函数组合起来,发挥两者的长处。思路就是使用原型链实现对原型属性和方法的继承,通过构造函数实例对实例属性的继承。
这样一来,又可以使两个不同的子类型实例即分别拥有自己的属性,又可以使用相同的方法了,
//超类型的属性 function Father(firstname){ this.lastname="li"; this.firstname=firstname; this.colors=["red","blue"] } //超类型的方法 Father.prototype.sayName=function(){ console.log("my name is "+this.lastname+this.firstname) } //通过构造函数继承超类型的属性 function Child(firstname,age){ Father.call(this,firstname); this.age=age; } //通过原型链继承超类型的方法 Child.prototype=new Father(); //子类型自己的方法 Child.prototype.sayAge=function(){ console.log(this.age); } var c1=new Child("furong",24); c1.sayName();// my name is lifurong c1.sayAge() // 24 var c2=new Child("rui",18); c2.sayName();//my name is lirui c2.sayAge() //18
4、原型式继承
- 本质上讲,object函数是对传入的对象进行了浅复制:引用类型的属性被所有实例共享
- 这个和Object.create()方法一样,这个方法接收两个参数,第一个参数是原型对象,第二个是为新对象定义的额外属性。
function Object(o){ function F(){}; F.prototype=o; return new F(); } var father={ lastname:"li", colors:["red","blue"] } var c1=Object(father); c1.lastname; c1.colors.push("yellow"); var c2=Object(father); c2.lastname; c2.colors; //["red", "blue", "yellow"]
- 缺点:和原型链继承一样:包含引用类型值所带来的问题:如果超类型包含引用类型值的属性,子类型通过原型继承后,子类型的实例都会共享这个包含引用类型值的属性,如果在一个实例中修改,就会反映到另外一个实例上。
- 优点:如果只想让一个对象和另外一个对象保持类型的情况下可以用。
5.寄生式继承
寄生式继承就是创建一个仅用于封装继承过程的函数,该函数在内部用某种方式来增强对象
function createAnother(original){ var obj=Object.create(original);//通过调用函数创建一个新对象 obj.sayhi=function(){ console.log("hi"); //为这个对象添加方法 } return obj;//返回这个对象 } var person={ name:"furong", age:20 } var o1=createAnother(person) //继承person并且拥有自己的方法 Object.keys(o1); //["sayhi"] 自有的属性
缺点:函数复用问题。还有引用类型的属性共享问题。
6.寄生组合式继承
是组合继承的改良版,组合继承虽然是最常用的继承模式,但它最大的问题是无论在什么情况下,都会调用两次超类型的构造函数,一次是创建子类型原型的时候,一次是在子类型构造函数内部。两次调用造成的后果是,在调用子类型构造函数时,原型的属性会被实例的属性重写。
寄生组合的基本思路是:用寄生式继承来继承超类型的原型,再将结果指定给子类型的原型
function inheritPrototype(subType,superType){ var obj=Object.create(superType.prototype);//继承超类型的原型,返回一个新的对象(创建了一个超类型的副本) subType.prototype=obj; //将结果指定给子类型的原型 } //省略了subType.prototype=new superType()
优点:只调用了一次超类型的构造函数,因此避免了在子类型的原型上创建不必要的、多余的属性。而且原型链还能保持不变,因此能正常使用instanceof和isPrototypeOf()来检测实例和原型的关系。
开发人员普遍认为寄生组合式继承是继承的最佳方式。