1.原型链式继承
让子原型对象指向父的实例,构建原型链。
Father.prototype.LastName = "kang"
function Father(){
}
var father = new Father();
Son.prototype = father;
(Son,prototype = new Father)
function Son(){
}
var son = new Son()
console.log(son.LastName)
儿子继承了父亲原型上的方法LastName.
那么如果说我要给儿子添加一个新方法,或者给父亲重写方法呢?
Father.prototype.LastName = "kang"
function Father(){
}
var father = new Father();
Son.prototype = father;
function Son(){
}
给Son添加新的方法
Son.prototype.name = "jiahao";
var son = new Son();
console.log(son.name) //"jiahao"
给Father重写方法
Father.prototype.LastName = "wang"
var son = new Son();
console.log(son.LastName) //"kang"
阅读红宝书你会发现,这两种修改方法都放在了最后边,也就是说在放在把儿子原型指向父亲实例的后面。
如果说放在前面呢?
Father.prototype.LastName = "kang"
function Father(){
}
var father = new Father();
Son.prototype.name = "jiahao";
Son.prototype = father;
function Son(){
}
// 给Son添加新的方法
var son = new Son();
console.log(son.name) //"undefined"
这个时候访问son.name返回undefined。
为什么呢?
Father.prototype.LastName = "kang"
function Father(){
}
var father = new Father();
Son.prototype.name = "jiahao";
console.log(Son.prototype)
Son.prototype = father;
console.log(Son.prototype)
function Son(){
}
// 给Son添加新的方法
var son = new Son();
console.log(son.name) //"undefined"
分别打印两个son.prototype可以看出:
两个Son.prototype 返回的并不一样,指向空间发生改变。
因此为了不出现这种错误,添加新方法,和重写方法,都要放在最后面来进行。
不能用对象字面量的方法来添加方法:
对象字面量的写法相当于重写原型,为形成对比
1.普遍写法:
Cat.prototype.name = "jerry"
function Cat(){
}
var littleCat = new Cat();
Cat.prototype.name = "tom"
console.log(Cat.prototype.constructor == Cat) //true
console.log(Cat.prototype.isPrototypeOf(littleCat)) // true 原型对象在littleCat对象原型链上
2.对象字面量写法
Cat.prototype.name = "jerry"
function Cat(){
}
var littleCat = new Cat();
Cat.prototype = {
name:"tom"
}
console.log(Cat.prototype.constructor == Cat) //false
console.log(Cat.prototype.isPrototypeOf(littleCat)) //false //原型对象不在littleCat对象原型链上
再举个例子你很容易明白:
Cat.prototype.name = "jerry"
function Cat(){
}
var littleCat = new Cat();
Cat2.prototype = littleCat;
function Cat2(){
}
Cat2.prototype = {
age:"tom2"
}
var littleCat2 = new Cat2()
console.log(littleCat2.name) //undefined
对象字面量的写法相当于重写原型链,重写后意味着Cat.prototype指向了一个新的原型对象,切断了Cat 和 Cat2的联系。像本例一种一样,father和son的联系被切断了。
当然你可以在重写的过程中显示的修改constructor的指向。
原型链式继承的缺点:
1.过多继承了没有用的方法:
//假如说我只想继承Father的lastname,在原型链式继承上会自动继承了Father的sex方法;
Father.prototype.lastname = "kang"
function Father(){
this.sex = "man"
}
Son.prototype = new Father();
function Son(){
}
var son = new Son();
2.包含引用值类型的原型属性会称为所有实例的共享属性。
Father.prototype.jobs = ["tencent","alibaba","baidu"]
function Father(){
}
Son.prototype = new Father();
function Son(){
}
var son = new Son();
son.jobs.push('meituan');
console.log(son.jobs); //(4) ["tencent", "alibaba", "baidu", "meituan"] 没有问题
var son2 = new Son();
console.log(son2.jobs); //(4) ["tencent", "alibaba", "baidu", "meituan"] 这里son2直接共享了jobs属性。
3.在创建儿子类型的实例时,不能向父亲类型的构造函数传递参数。
2.借用构造函数实现继承。(伪造对象或者经典继承)
function Father(name,age,hobbit){
this.name = name;
this.age = age;
this.hobbit = hobbit;
}
function Son(name,age,hobbit,sex){
this.name = name;
this.age = age;
this.hobbit = hobbit;
this.sex = sex;
}
var son = new Son("kang","19","bianchen","man");
现在我想让儿子继承父亲的name,age,和hobbit。
function Father(name,age,hobbit){
this.name = name;
this.age = age;
this.hobbit = hobbit;
}
function Son(name,age,hobbit,sex){
Father.call(this,name,age,hobbit) //使用call()改变了this指向
this.sex = sex;
}
var son = new Son("kang","19","bianchen","man");
但是父亲的原型链上的属性,儿子继承不了。
Father.prototype.lastname = "kang";
function Father(name,age,hobbit){
this.name = name;
this.age = age;
this.hobbit = hobbit;
}
function Son(name,age,hobbit,sex){
// this.name = name;
// this.age = age;
// this.hobbit = hobbit;
Father.call(this,name,age,hobbit)
this.sex = sex;
}
var son = new Son("kang","19","bianchen","man");
console.log(son.lastname) //undefined
这种继承模式也实现了传递参数的功能。
缺点:相同方法在不同对象的构造函数中都要定义一遍,无法实现函数复用。在超类型原型中定义的方法,对子类型是不可见的,因此所有的超类型的原型属性都不能被继承。
3.组合继承
通过上面两种继承方式,我们可以发现,原型链式继承不能传递参数,而经典继承不能继承父类原型链上的方法,所以组合继承就时由这两种继承方式组成。
//组合继承
Father.prototype.sayName = function (){
return this.name
}
function Father(name){
this.name = name;
this.colors = ["red","black","white"]
}
Son.prototype = new Father();
Son.prototype.constructor = Son;
function Son(name,age){
Father.call(this,name);
this.age = age;
}
var son = new Son("dangyanlin",3);
son.colors.push('yellow');
console.log(son.colors) // (4) ["red", "black", "white", "yellow"]
console.log(son.sayName()) // dangyanlin
console.log(son.age) // 3
var son2 = new Son(); //没有传进去参数
console.log(son2.colors); //(3)["red", "black", "white"]
先使用原型继承方式,使得子类可以继承到父类原型上的方法,之后使用经典继承使得子类可以继承父亲身上的方法。为保证 son和 son2 拥有各自的父类属性副本,我们在 Son 构造函数中,还是使用了 Person.call ( this ) 方法,这样他们可以有属于自己的属性,也可以使用相同的方法了。
如此,结合原型链继承和借用构造函数继承,就完美地解决了之前这二者各自表现出来的缺点。
组合继承解决原型链继承的引用类型原型属性被实例共享问题。(call())
缺点:两次调用构造函数,效率低。
4.原型式继承
function Object(o){
function F();
F.prototype = o;
return new F();
}
原型式继承本质其实就是个浅拷贝,以一个对象为模板复制出新的对象
function Object(o){
function F(){}
F.prototype = o;
return new F();
}
var obj = {
name:'kang',
sex:'man',
saymymessage:function(){
return this.name + this.sex;
}
}
var obj2 = Object(obj);
console.log(obj2.name) //"kang"
console.log(obj2.sex) //"man"
console.log(obj2.saymymessage()) //"kangman"
因为是个浅拷贝的原因,这种继承模式的实例会共享引用数据。
function Object(o){
function F(){}
F.prototype = o;
return new F();
}
var obj = {
name:'kang',
sex:'man',
skills:['唱','跳','rap'],
saymymessage:function(){
return this.name + this.sex;
}
}
var obj2 = Object(obj);
var obj3 = Object(obj);
obj2.skills.push('篮球');
console.log(obj2.skills); // (4) ["唱", "跳", "rap", "篮球"]
console.log(obj3.skills) // (4) ["唱", "跳", "rap", "篮球"]
ES5中新增了Object.create()方法规范了原型式继承。
var obj1 = {
skills:['唱','跳','rap']
}
var obj2 = Object.create(obj1);
obj2.skills.push('篮球')
console.log(obj2.skills);
Object.create()有两个参数,第一个继承别人的方法,第二个设置自己的方法;第二种方法设置的属性会覆盖原型上的属性。
var obj1 = {
skills:['唱','跳','rap'],
name:“wang”
}
var obj2 = Object.create(obj1,{
name:{
value:'kang'
}
});
obj2.skills.push('篮球')
console.log(obj2.skills);
console.log(obj2.name) //"kang"
5.寄生式继承。
寄生式继承就是把原型式继承再次封装,然后在对象上扩展新的方法,再把新对象返回
//寄生式继承
function object(o){
function F(){} //创建了一个对象
F.prototype = o; //将这个对象原型指向传入的函数
return new F(); //返回一个对象的新实例 其实就是 传入对象的一个实例
}
function createAnother(original){
var clone = object(original); // clone是original的一个实例
clone.sayHi = function(){ //增强这个对象
alert("Hi");
}
return clone; //返回这个对象
}
var obj = {
name:'kang',
age:'19'
}
var obj2 = createAnother(obj);
缺点:类似于构造函数继承,不能实现函数复用。
6.寄生组合式继承
组合继承的缺点:
Father.prototype.sex = "man"
function Father(name){
this.name = name
}
Son.prototype = new Father(); //-->调用Father构造函数
Son.prototype.constructor = Son;
function Son(name,age){
Father.call(this,name) //-->调用Father构造函数
this.age = age
}
var son1 = new Son("kang",12);
两次调用父类构造函数,效率低。
寄生组合式:通过构造函数继承属性,通过原型链的混成形式来继承方法,不必为了指定子类型的原型而调用父类构造函数,本质上是使用寄生式继承来继承夫类型的原型。
//父类构造函数
function Super(name){
this.name = name;
this.funcName = "Super";
this.func = "super";
}
Super.prototype.sayName = function (){ alert(this.name); }
Super.prototype.sayFuncName = function (){ alert(this.funcName); }
//子类构造函数
function Sub(name,age){
Super.call(this,name);
this.age = age;
this.funcName = "Sub";
}
//继承函数
function inheritPrototype(Sub,Super){
//中间实例化对象
var a = Object(Super.prototype);
//重写该对象的构造函数指针,指向Sub函数
a.constructor = Sub;
//Sub的原型指向实例化对象a
Sub.prototype = a;
}
//调用继承函数,实现原型链构造
inheritPrototype(Sub,Super);
/*
----------一定要在调用继承函数之后添加,如果放在继承函数之前,随着原型的替换,函数会undefined。----------
*/
//添加子类方法
Sub.prototype.sayAge = function (){ alert(this.age); }
Sub.prototype.sayFuncName = function (){ alert(this.funcName); }
//测试代码
var subTest = new Sub("lulu",20);
subTest.sayName(); //lulu
subTest.sayAge(); //20
subTest.sayFuncName(); //Sub
alert(subTest.func); //super
里面有部分博主的个人理解,博主也是个萌新,错了希望大家指出来,我再接再厉继续改进。
thanks!