• 【无标题】


    总结一下JavaScript中的几种继承方案:

    1.原型链继承

    将父类的实例作为子类的原型

    1. // 父类
    2. function Person(name,friend) {
    3. this.name = name;
    4. this.friend = friend
    5. this.say = function () {
    6. console.log(`My name is ${this.name}.`);
    7. };
    8. }
    9. Person.prototype.listen = function () {
    10. console.log('I am listening...');
    11. }
    12. function Student(no) {
    13. this.no = no;
    14. }
    15. Student.prototype = new Person(); // 将父类的实例作为子类的原型
    16. Student.prototype.constructor = Student; // 修正
    17. Student.prototype.sayNo = function () {
    18. console.log(`My No. is ${no}`);
    19. }
    20. const stu = new Student(1);
    21. stu.sayNo(); // My No. is 1
    22. stu.say(); // My name is undefined.
    23. stu.listen(); // I am listening...
    24. console.log(stu instanceof Student); // true
    25. console.log(stu instanceof Person); // true

    缺点:

    • 从父类中继承的属性没有添加到子类实例自身上,而是需要通过原型链访问,打印stu对象, 继承的属性是看不到的
    • 在子类实例上,直接修改对象上继承的属性, 是给本对象添加了一个新属性(不论是基本类型还是引用数据类型)
    • 在子类实例上,访问继承的属性,如果是引用数据类型,通过引用修改该引用数据,则多个子类实例之间会互相影响
    1. var stu1 = new Student(1)
    2. var stu2 = new Student(2)
    3. // 1.直接修改继承的属性,是给本对象添加了一个新属性
    4. stu1.name = "kobe"
    5. console.log(stu1) // {no: 1, name: 'kobe'}
    6. console.log(stu2) // {no: 2}
    7. // 2.访问继承的引用数据类型,获取引用, 修改引用中的值, 会相互影响
    8. stu1.friends.push("kobe")
    9. console.log(stu1.friends) // ['kobe']
    10. console.log(stu2.friends) // ['kobe']
    • 无法实现多继承(即一个子类继承多个父类的属性或方法)?
    • 创建子类实例时,不能向父类构造函数传参数,因为这个对象是一次性创建的没有办法定制化

    2.借助构造函数继承

    在子类型构造函数的 内部调用父类型构造函数 使用父类的构造函数来增强子类 实例
    • 因为函数可以在任意的时刻被调用;
    • 因此通过apply()和call()方法让父类构造函数内部的this指向子类实例;
    1. function Person(age) {
    2. this.name= name;
    3. this.age = age
    4. }
    5. Person.prototype.say = function(){
    6. console.log('啦啦啦~')
    7. }
    8. function Student(no, name, age) {
    9. Person.call(this, name);
    10. this.no = no;
    11. }
    12. const stu = new Student(233, 'Ben', 18);
    13. stu.say();// 无法继承到父类原型的方法
    14. console.log(stu2 instanceof Student2); // true
    15. console.log(stu2 instanceof Person); // false

    缺点:

    • 只能继承父类实例上的属性,不能继承原型上的属性

    3.组合式继承

    原型链继承借用构造函数继承方法的结合,用原型链实现对原型属性和方法的继承,用借用构造函数技术来实现实例属性的继承。

    1. // 父类: 公共属性和方法
    2. function Person(name, age, friends) {
    3. this.name = name
    4. this.age = age
    5. this.friends = friends
    6. }
    7. Person.prototype.eating = function() {
    8. console.log(this.name + " eating~")
    9. }
    10. // 子类: 特有属性和方法
    11. function Student(name, age, friends, sno) {
    12. Person.call(this, name, age, friends) //1.借用构造函数
    13. this.sno = 111
    14. }
    15. var p = new Person()
    16. Student.prototype = p //2.原型链继承
    17. p.constructor = Person
    18. Student.prototype.studying = function() {
    19. console.log(this.name + " studying~")
    20. }
    21. const stu = new Student("why", 18, ["kobe"], 111)
    22. console.log(stu) // {age:18,name:'why',sno:111,firends:['kobe']}

    缺点:

    1.无论在什么情况下,都会调用 两次父类构造函数
    •  一次在创建子类原型的时候
    •  另一次在子类构造函数内部(也就是每次创建子类实例的时候)
    2.所有的子类实例事实上会 拥有两份父类的属性
    • 一份在当前的实例自己里面(也就是person本身的),
    • 另一份在子类对应的原型对象中(也就是 person.__proto__里面);
    当然,这两份属性我们无需担心访问出现问题,因为默认一定是访问实例本身这一部分的;

    4.原型式继承

    为了理解这种方式,我们先再次回顾一下JavaScript想实现继承的目的:重复利用另外一个对象的属性和方法.
    • 这种继承方法不是通过构造函数来实现的,而是通过一个原型式继承函数实现。
    • 最终的目的:student对象的原型指向了person对象。
    1. // 原型式继承函数1
    2. function createObject1(o) {
    3. var newObj = {}
    4. Object.setPrototypeOf(newObj, o)
    5. return newObj
    6. }
    7. // 原型式继承函数2
    8. function createObject2(o) {
    9. function Fn() {}
    10. Fn.prototype = o
    11. var newObj = new Fn()
    12. return newObj
    13. }
    14. // 原型式继承3
    15. // Object.create可以替代上述两个函数的功能
    16. var obj = {
    17. name: "why",
    18. age: 18
    19. }
    20. var info = Object.create(obj)
    21. console.log(info)
    22. console.log(info.__proto__===obj) //true

    优点:

    创建一个新的对象(子类实例),这个对象的原型是传入的对象(父类构造函数的实例),子类实例就可以通过原型链访问到父类实例的属性。

    缺点

    • 继承的属性和方法只能通过原型链访问,并没有在子类实例上添加属性和方法,
    • 具有和原型链继承一样的缺点,继承的引用数据类型,修改了之后会影响其他实例

    5.寄生式继承

    • 寄生式(Parasitic)继承是与原型式继承紧密相关的一种思想
    • 寄生式继承的思路是结合原型式继承和工厂模式的一种方式
    • 即创建一个封装继承过程的函数, 该函数在原型式继承的基础上,以某种方式来增强对象(在对象上新增属性和方法),最后再将这个对象返回
    1. function createAnother(original){
    2. var clone = object(original); // 通过调用 object() 函数创建一个新对象
    3. clone.sayHi = function(){ // 以某种方式来增强对象(相比原型式继承多出的部分)
    4. alert("hi");
    5. };
    6. return clone; // 返回这个对象
    7. }
    8. var person = {
    9. name: "Nicholas",
    10. friends: ["Shelby", "Court", "Van"]
    11. };
    12. var anotherPerson = createAnother(person);
    13. anotherPerson.sayHi(); //"hi"

    缺点:和原型式继承一样

    6.寄生组合式继承 

    借用构造函数继承寄生式继承的组合,通过借用构造函数继承父类实例上的属性,寄生式继承父类原型的属性。

    1. function createObject(o) {
    2. function Fn() {}
    3. Fn.prototype = o
    4. return new Fn()
    5. }
    6. function inheritPrototype(SubType, SuperType) {
    7. SubType.prototype = Object.create(SuperType.prototype)
    8. Object.defineProperty(SubType.prototype, "constructor", {
    9. enumerable: false,
    10. configurable: true,
    11. writable: true,
    12. value: SubType
    13. })
    14. }
    15. function Person(name, age, friends) {
    16. this.name = name
    17. this.age = age
    18. this.friends = friends
    19. }
    20. Person.prototype.running = function() {
    21. console.log("running~")
    22. }
    23. Person.prototype.eating = function() {
    24. console.log("eating~")
    25. }
    26. function Student(name, age, friends, sno, score) {
    27. Person.call(this, name, age, friends) //借用构造函数继承
    28. this.sno = sno
    29. this.score = score
    30. }
    31. inheritPrototype(Student, Person) //寄生式继承
    32. Student.prototype.studying = function() {
    33. console.log("studying~")
    34. }
    35. // 继承的属性都添加在子类实例自身上
    36. console.log(stu) //{name:'why',age:18,score:100,sno:111,friends:['kobe']}
    37. stu.studying() //studying~ 可以通过原型链访问到父类方法
    38. stu.running() //running~
    39. stu.eating() //eating~
    40. console.log(stu.constructor.name) //Student
    现在我们来回顾一下之前提出的比较理想的组合继承 :组合继承是比较理想的继承方式, 但是存在两个问题:
    • 问题一: 构造函数会被调用两次: 一次在创建子类型原型对象的时候, 一次在创建子类型实例的时候.
    • 问题二: 父类型中的属性会有两份: 一份在原型对象中, 一份在子类型实例中.
    事实上, 我们现在可以利用寄生式继承将这两个问题给解决掉.
    你需要先明确一点:
    • 当我们在子类型的构造函数中调用父类型.call(this, 参数)这个函数的时候, 就会将父类型中的属性和方法复制一份到了子类型中,所以父类型本身里面的内容, 我们不再需要.
    • 这个时候, 我们还需要获取到一份父类型的原型对象中的属性和方法.
      能不能直接让子类型的原型对象 = 父类型的原型对象呢?
    不要这么做, 因为这么做意味着以后修改了子类型原型对象的某个引用类型的时候, 父类型原生对象的引用类型也会被修改.
    我们使用前面的寄生式思想就可以了

    没有使用父类实例作为子类原型来继承原型上的属性,采用寄生式继承,将一个原型为父类原型的对象直接赋值给子类原型,可以避免多次调用父类构造函数和保存两份父类属性。

    这是最成熟的方法,也是现在库实现的方法

    7.混入方式寄生多个对象

    1. function SuperClass(){}
    2. SuperClass.prototype.saySuper=function(){
    3. console.log('hellow super')
    4. }
    5. function OtherSuperClass(){}
    6. OtherSuperClass.prototype.sayOther=function(){
    7. console.log('hellow other')
    8. }
    9. function MyClass() {
    10. // SuperClass.call(this);
    11. // OtherSuperClass.call(this);
    12. }
    13. // 继承一个类
    14. MyClass.prototype = Object.create(SuperClass.prototype);
    15. // 重新指定constructor
    16. MyClass.prototype.constructor = MyClass;
    17. // 混合其它
    18. Object.assign(MyClass.prototype, OtherSuperClass.prototype);
    19. MyClass.prototype.myMethod = function() {
    20. // do something
    21. };
    22. console.log(MyClass.prototype)

    Object.assign会把 OtherSuperClass原型上的函数拷贝到 MyClass原型上,使 MyClass 的所有实例都可用 OtherSuperClass 的方法。 

    8.ES6类继承

    在ES6中新增了使用 extends 关键字,可以方便的帮助我们实现继承
    1. class Person {
    2. constructor(name, age) {
    3. this.name = name
    4. this.age = age
    5. }
    6. running() {
    7. console.log(this.name + " running~")
    8. }
    9. eating() {
    10. console.log(this.name + " eating~")
    11. }
    12. personMethod() {
    13. console.log("处理逻辑1")
    14. console.log("处理逻辑2")
    15. console.log("处理逻辑3")
    16. }
    17. static staticMethod() {
    18. console.log("PersonStaticMethod")
    19. }
    20. }
    21. // Student称之为子类(派生类)
    22. class Student extends Person {
    23. // JS引擎在解析子类的时候就有要求, 如果我们有实现继承
    24. // 那么子类的构造方法中, 在使用this之前,必须显式通过super调用父类的构造函数
    25. constructor(name, age, sno) {
    26. super(name, age)
    27. this.sno = sno
    28. }
    29. studying() {
    30. console.log(this.name + " studying~")
    31. }
    32. // 类对父类的方法的重写
    33. running() {
    34. console.log("student " + this.name + " running")
    35. }
    36. // 重写personMethod方法
    37. personMethod() {
    38. // 复用父类中的处理逻辑
    39. super.personMethod()
    40. console.log("处理逻辑4")
    41. console.log("处理逻辑5")
    42. console.log("处理逻辑6")
    43. }
    44. // 重写静态方法
    45. static staticMethod() {
    46. super.staticMethod()
    47. console.log("StudentStaticMethod")
    48. }
    49. }
    50. var stu = new Student("why", 18, 111)
    51. console.log(stu)
    52. console.log(Object.getOwnPropertyDescriptors(stu.__proto__))
    53. console.log(Object.getOwnPropertyDescriptors(stu.__proto__.__proto__)) //父类原型

    参考:

    JavaScript常用八种继承方案 - 掘金 (juejin.cn)

    图解JavaScript常用八种继承方案 - 掘金 (juejin.cn)

  • 相关阅读:
    【vue】如何安装vue 脚手架以及创建脚手架项目_10
    MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作
    【理论】车辆双轴振动模型(一)
    大语言模型在天猫AI导购助理项目的实践!
    使用 Sa-Token 解决 WebSocket 握手身份认证
    python FastAPI 文件下载
    UNITY与安卓⭐二、AndroidStudio中关于通讯的使用教学
    全真模拟题!PMP提分必练
    uni-app 经验分享,从入门到离职(五)——由浅入深 uni-app 数据缓存
    gitlab 简单优化 gitlab cpu高,内存高 gitlab 负载飙高
  • 原文地址:https://blog.csdn.net/ICanWin_lll/article/details/133909812