目录
前言
原型链继承
构造函数继承
组合继承
原型式继承
寄生式继承
寄生组合继承
类的继承
概念
类继承用extends关键字
es6的类同时拥有__proto__和prototype。
前言
首先学习继承之前,要对原型链有一定程度的了解。
不了解可以去先阅读我另一篇文章,里面对原型链有一个较为详细的说明:js 原型链详解。
不了解call方法的用法的话,提前阅读:js apply、call、bind一篇掌握
如果已经了解请继续。
原型链继承
父类实例作为子类的原型
子类创造的两个实例的隐式原型__proto__指向父类的那个实例
而父类的实例的隐式原型__proto__又指向父类的原型father.prototype
根据原型链的特性,所有子类的实例继承父类原型上的属性。
这张图理解清晰后续我就不再重复解释了。
//父类 function father() { this.fatherAttr = ["fatherAttr"]; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; //子类 function child() {} // 将father实例作为child这个构造函数的原型 child.prototype = new father(); child.prototype.constructor = child; //两个子类实例 const test1 = new child(); const test2 = new child(); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto);
特点:
两个实例对象都没有fatherAttr属性,但是因为父类的实例会拥有fatherAttr属性,且现在父类的实例作为child的原型,根据原型链,他们可以共享到自己的构造函数child的原型上的属性。(测试1)
因为只有一个父类的实例作为他们的原型,所以所有实例共享了一个原型上的属性fatherAttr,当原型上的属性作为引用类型时,此处是数组,test1添加一个新内容会导致test2上的fatherAttr也改变了。(测试2)(缺点)
child构造函数不能传递入参。(缺点)
实例可以访问到父类的原型上的属性,因此可以把可复用方法定义在父类原型上。(测试3)
构造函数继承
将父类上的this绑定到子类,也就是父类上创建的属性会拷贝到子类这个构造函数上,所以实例会继承这些属性。
//父类 function father(params) { this.fatherAttr = ["fatherAttr"]; this.params = params; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; //子类 function child(params) { father.call(this, params); } //两个子类实例 const test1 = new child("params1"); const test2 = new child("params2"); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto);
特点:
两个实例对象都拥有了拷贝来的fatherAttr属性,所以没有共享属性,创造一个实例就得拷贝一次父类的所有属性,且因为不能继承父类原型,所以方法不能复用,被迫拷贝方法。(测试1)(缺点)
test1添加一个新内容只是改变了test1自己的属性,不会影响到test2。(测试2)
child构造函数可以传递参数,定制自己的属性。(测试1)
实例不能继承父类的原型上的属性。(测试3)(缺点)
组合继承
结合原型链继承和构造函数继承,可以根据两种继承特点进行使用。
//父类 function father(params) { this.fatherAttr = ["fatherAttr"]; this.params = params; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; //子类 function child(params) { //第二次调用了父类构造函数 father.call(this, params); } // 将father实例作为child构造函数的原型 child.prototype = new father();//第一次调用了父类构造函数 child.prototype.constructor = child; //两个实例 const test1 = new child("params1");//从这里跳转去子类构造函数第二次调用了父类构造函数 const test2 = new child("params2"); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto); console.log("测试4:"); delete test1.fatherAttr console.log("test1:", test1); console.log("test1.fatherAttr:", test1.fatherAttr);
特点:
两个实例对象都拥有了拷贝来的fatherAttr属性,创造一个实例就得拷贝一次父类的所有属性(构造函数继承特点,测试1),但是能访问父类原型,可以把复用方法定义在父类原型上。(原型链继承特点,测试1)
test1添加一个新内容只是改变了test1自己的属性,不会影响到test2。(构造函数继承特点,测试2)
child构造函数可以传递参数,定制自己的属性。(构造函数继承特点,测试1)
实例能继承父类的原型上的属性。(原型链继承特点,测试3)
调用了两次父类的构造函数,生成两份实例,创建子类原型链一次,用子类创建实例时,子类内部里面一次,第二次覆盖了第一次。(缺点)
因为调用两次父类构造函数,如果用delete删除实例上拷贝来的fatherAttr属性,实例仍然拥有隐式原型指向的父类实例上的fatherAttr属性。(原型链继承特点,测试4)(缺点)
原型式继承
就是封装一个方法,在方法中创建一个临时的构造函数,入参为父类实例,将临时构造函数的原型绑定到传入的实例上并返回。
//父类 function father() { this.fatherAttr = ["fatherAttr"]; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; function create(obj) { function child() {} child.prototype = obj; return new child(); } const fatherObj = new father(); //两个实例 const test1 = create(fatherObj); const test2 = create(fatherObj); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto);
ECMAScript5新增了Object.create()方法规范化了原型式继承,这里的create函数中的代码就是Object.create()的核心部分,Object.create()还提供第二个参数,这里就不展开介绍了。
用Object.create()进行原型式继承:
//父类 function father() { this.fatherAttr = ["fatherAttr"]; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; // function create(obj) { // function child() {} // child.prototype = obj; // return new child(); // } const fatherObj = new father(); //两个实例 const test1 = Object.create(fatherObj); const test2 = Object.create(fatherObj); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto);
结果一致。
特点:
- 看测试结果,貌似与原型链方法特点一致。
寄生式继承
在原型式继承的基础上,再多封装一个函数,用于增强对象。
//父类 function father() { this.fatherAttr = ["fatherAttr"]; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; function create(obj) { function child() {} child.prototype = obj; return new child(); } function afterCreate(obj) { const clone = create(obj); clone.createAttr = "createAttr"; return clone; } const fatherObj = new father(); //两个实例 const test1 = afterCreate(fatherObj); const test2 = afterCreate(fatherObj); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto);
特点:
在原型式继承的基础上,可以拥有afterCreate构造方法内部自带的属性,创建实例时,afterCreate方法自带的属性就自动寄生在实例身上,也就是能让所有调用afterCreate继承于任何不同对象的实例都会拥有这么一个属性。
寄生组合继承
结合寄生式继承和组合式继承,与寄生式继承的区别是:寄生式继承继承的是父类的实例,目的无非是需要父类的原型,那么我们去掉创建父类实例的那一步,直接把父类原型拿去继承,用增强对象的方式去实现将子类的原型指向父类的原型,不再用父类的实例作为子类的原型。
//父类 function father(params) { this.fatherAttr = ["fatherAttr"]; this.params = params; } //父类的原型上的属性 father.prototype.checkProto = "checkProto"; function child(params) { father.call(this, params); } function create(obj) { function child() {} child.prototype = obj; return new child(); } function afterCreate(ch, fa) { const clone = create(fa.prototype); clone.constructor = ch; ch.prototype = clone; } afterCreate(child, father); //两个实例 const test1 = new child("params1"); const test2 = new child("params2"); console.log("测试1:"); console.log("test1:", test1); console.log("test2:", test2); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试2:"); test1.fatherAttr.push("newAttr"); console.log("test1.fatherAttr:", test1.fatherAttr); console.log("test2.fatherAttr:", test2.fatherAttr); console.log("测试3:"); console.log("test1.checkProto:", test1.checkProto); console.log("测试4:"); delete test1.fatherAttr; console.log("test1:", test1); console.log("test1.fatherAttr:", test1.fatherAttr);
特点:
拥有组合式继承的所有优点,且用寄生的方式修复了组合继承会调用两次父类构造函数的问题,也因为修复的这个问题,解决了删除对应实例上的属性,仍然能访问到原型链上属性的问题。
类的继承
概念
这是es6才推出的一个概念,原理也只是语法糖而已,用起来就容易得多了。
father中的constructor就是构造函数,father本身就是原型。
构造函数中的this指向实例。
class father { constructor(x, y) { this.x = x; this.y = y; } fatherAttr = "fatherAttr"; } const test1 = new father(1, 2); console.log(test1.x);//1 console.log(test1.y);//2 console.log(test1.fatherAttr);//"fatherAttr"
类继承用extends关键字
注意:
- 子类构造器constructor中必须要有super(),因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工,如果不调用super方法,子类就得不到this对象,会报错。
- 如果不写constructor,会默认存在,且调用了super()。
class father { constructor(x, y) { this.x = x; this.y = y; } fatherAttr = "fatherAttr"; } class child extends father { constructor(x, y, z) { super(x, y); this.z = z; } childAttr = "childAttr"; } const test1 = new child(1, 2, 3); console.log(test1.x);//1 console.log(test1.y);//2 console.log(test1.z);//3 console.log(test1.fatherAttr);//"fatherAttr" console.log(test1.childAttr);//"childAttr"
es6的类同时拥有__proto__和prototype。
class father {} class child extends father {} console.log(child.prototype.__proto__ == father.prototype); console.log(child.__proto__ == father);
因为继承实现模式大概是这样:
class father {} class child extends father {} Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; }; Object.setPrototypeOf(child.prototype, father.prototype); Object.setPrototypeOf(child, father);