Code前端首页关于Code前端联系我们

js中的这些调情类继承操作

terry 2年前 (2023-09-08) 阅读数 159 #Vue
// 父类
function SuperClass() {
  // 公有属性
  this.a = 'I am super class!';
  this.arr = ['a', 'b', 'c'];
}
// 公有方法
// 一般不需要修改的方法可以放入原型链上,防止实例化时重复定义引用类型的内容,造成内存浪费
SuperClass.prototype.echoA = function () {
  console.log(this.a);
};

// 派生类
function SubClass() {
  this.b = 'I am sub class!';
}

// 将父类的实例绑定进原型链上
SubClass.prototype = new SuperClass();

// 派生类上的公有方法
SubClass.prototype.echoB = function () {
  console.log(this.b);
};

// 实例化派生类
const sub = new SubClass();
// 调用父类方法
sub.echoA(); // I am super class!
// 调用本类方法
sub.echoB(); // I am sub class!

 
介绍

在Js开发过程中,我们常常无法避免类的继承。在Es6时代,我们只需要使用extends语法糖就可以轻松实现类继承。在es5开始的时候,我们使用对象原型链来实现类的继承,本文主要介绍早期es5类的继承方法,让读者能够更好的理解js对象的原型链模型以及js程序的一些设计思想。 。

先决条件

  1. js变量类型:js变量可以分为:

    • 值类型(主类型):堆栈中存储的内容,如字符串、数字、布尔值、空、未定义和符号。
    • 引用类型:内容存储在堆中,栈存储的是堆索引的地址,如对象、数组、函数等。
  2. 每个

    js 函数(类)都有一个属性 prototype 指向函数(类)的原型。每个对象都可以通过__proto__访问该对象的构造函数原型的方法和属性。 js在访问对象的属性和方法的过程中,首先查看对象本身是否修改了方法和属性。如果没有找到,它会通过__proto__寻找更好的对象。通过__proto__连接的对象形成原型链。

原型链继承

根据原型链原理,我们很容易想到,只要将父类的实例链接到派生类的原型链上,就可以实现继承:

// 父类
function SuperClass() {
  // 公有属性
  this.a = 'I am super class!';
  this.arr = ['a', 'b', 'c'];
}
// 公有方法
// 一般不需要修改的方法可以放入原型链上,防止实例化时重复定义引用类型的内容,造成内存浪费
SuperClass.prototype.echoA = function () {
  console.log(this.a);
};

// 派生类
function SubClass() {
  this.b = 'I am sub class!';
}

// 将父类的实例绑定进原型链上
SubClass.prototype = new SuperClass();

// 派生类上的公有方法
SubClass.prototype.echoB = function () {
  console.log(this.b);
};

// 实例化派生类
const sub = new SubClass();
// 调用父类方法
sub.echoA(); // I am super class!
// 调用本类方法
sub.echoB(); // I am sub class!

 

最重要的是,SubClass.prototype = new SuperClass();将父类对象链接到派生类的原型上,以完全继承继承。但这种继承方式有以下缺陷:

  1. 因为父类属性链接在原型链上,所以修改后的属性就成为派生类的共享属性。当派生类引用类型的公共属性发生变化时,整个派生类都会发生变化对象发生变化:

    const sub1 = new SubClass();
    const sub2 = new SubClass();
    
    console.log(sub1.arr); // ['a', 'b', 'c']
    console.log(sub2.arr); // ['a', 'b', 'c']
    sub1.arr.push('d');
    console.log(sub1.arr); // [ 'a', 'b', 'c', 'd' ]
    console.log(sub2.arr); // [ 'a', 'b', 'c', 'd' ]
    
     

在上面的示例中,我们更改了 sub1 的 arr 属性,因此 sub2 的 arr 属性也更改了

  1. 构造派生类时,无法将参数传递给父类的构造函数。

构造函数继承

为了解决原型链继承缺陷,我们更改代码定义:

// 父类
function SuperClass(arg) {
  // 公有属性
  this.a = 'I am super class!';
  this.arr = ['a', 'b', 'c'];
  this.arg = arg;
}
// 公有方法
// 一般不需要修改的方法可以放入原型链上,防止实例化时重复定义引用类型的内容,造成内存浪费
SuperClass.prototype.echoA = function () {
  console.log(this.a);
};
// 派生类
function SubClass(arg) {
  this.b = 'I am sub class!';
  // 派生类调用父类构造函数
  SuperClass.call(this, arg);
}
// 派生类上的公有方法
SubClass.prototype.echoB = function () {
  console.log(this.b);
};

const sub1 = new SubClass('sub1');
const sub2 = new SubClass('sub2');

console.log(sub1.arg); // sub1
console.log(sub2.arg); // sub2

console.log(sub1.arr); // ['a', 'b', 'c']
console.log(sub2.arr); // ['a', 'b', 'c']
sub1.arr.push('d');
console.log(sub1.arr); // [ 'a', 'b', 'c', 'd' ]
console.log(sub2.arr); // [ 'a', 'b', 'c' ]
 

其中,SuperClass.call(this, arg);通过在派生类中创建超类构造函数来实现继承。但是这种继承方式只能继承基类本身的属性和方法,而不能继承父类原型链的属性和方法。

合并继承

在构造函数继承方法中,我们发现派生类没有父类原型链的内容,所以聪明的你会想到将父类原型链直接链接到派生类SubClass.prototype = SuperClass.prototype;,以便派生类可以访问父类原型链中的属性方法。但是,由于prototype是引用类型,因此直接赋值是将派生类原型和基类原型引用到同一个对象。当您在派生类原型链中包含方法时,这会影响基类:

// 父类
function SuperClass(arg) {
  // 公有属性
  this.a = 'I am super class!';
  this.arr = ['a', 'b', 'c'];
  this.arg = arg;
}
// 公有方法
// 一般不需要修改的方法可以放入原型链上,防止实例化时重复定义引用类型的内容,造成内存浪费
SuperClass.prototype.echoA = function () {
  console.log(this.a);
};

// 派生类
function SubClass(arg) {
  this.b = 'I am sub class!';
  // 子类调用父类构造函数
  SuperClass.call(this, arg);
}

// 将父类的原型链绑定进派生类原型链上
SubClass.prototype = SuperClass.prototype;

// 派生类上的公有方法
SubClass.prototype.echoB = function () {
  console.log(this.b);
};

// 实例化父类
const superClass = new SuperClass('superClass');
// 打印子类原型链上方法
console.log(superClass.echoB); // [function]
 

另一种思路,不是直接将基类的原型链赋值给派生类,而是通过类似原型链继承的方式将基类的实例传递给派生类SubClass.prototype = new SuperClass('');这样,我们整合了原型链继承和构造函数继承的优点。但这种组合继承还是有点缺陷的:父类必须重复构造,绑定派生类的原型链必须构造一次,实例化的派生类必须再次构造。

寄生遗传

如果我们只是想继承父类原型链的属性和方法,我们可以通过构造“第三方”,然后绑定“第三方”实例来绑定父类原型链。派生类进入原型链为了节省资源,我们可以使用一个空类作为“第三方”:
// 定义空类为‘第三者’
function O() {}
// 绑定父类的原型链
O.prototype = SuperClass.prototype;
// 将带有父类原型链‘第三者’绑入派生类的原型链中
SubClass.prototype = new O();
 

寄生成分遗传

结合和寄生可以巧妙地避免在绑定派生类的原型链时调用冗余的父类构造函数:

// 父类
function SuperClass(arg) {
  // 公有属性
  this.a = 'I am super class!';
  this.arr = ['a', 'b', 'c'];
  this.arg = arg;
}
// 公有方法
// 一般不需要修改的方法可以放入原型链上,防止实例化时重复定义引用类型的内容,造成内存浪费
SuperClass.prototype.echoA = function () {
  console.log(this.a);
};

// 派生类
function SubClass(arg) {
  this.b = 'I am sub class!';
  // 子类调用父类构造函数
  SuperClass.call(this, arg);
}

// 定义空类为‘第三者’
function O() {}
// 绑定父类的原型链
O.prototype = SuperClass.prototype;
// 将带有父类原型链‘第三者’绑入派生类的原型链中
SubClass.prototype = new O();


// 派生类上的公有方法
SubClass.prototype.echoB = function () {
  console.log(this.b);
};
 

总结

寄生组合继承是es5最完美的继承方式,而现阶段很多es6和ts也使用这种方式来构建和分析extends的语法糖。下图是ts类继承的编译后的代码

ts类继承编译后代码ts类继承编译后代码

希望通过学习这篇文章,能够加深对js类本质的理解。请勿喷涂粗糙的印刷品。如果您有任何错误,请随时分享。谢谢阅读。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门