js的继承方式一、原型链继承二、构造函数继承三、组合继承四、原型式继承五、寄生式继承六、寄生组合式继承一、原型链继承1.原型链继承是“预先打包”在代码加载时就强行用默认值或硬编码值生成了一个父类实例作为模板后续所有子类实例只能共用这个“死”模板。2需要传参的继承是“按需定制”只有在真正创建子类实例new Child(…)的那一刻才拿着当前的参数去调用父类构造函数生成专属的数据。// 1. 定义超类型functionParent(){this.nameparent1;// 引用类型属性数组this.play[1,2,3];}// 2. 定义子类型functionChild(){this.typechild2;// 注意这里没有调用 Parent.call(this)所以 name 和 play 不会在实例自身上创建}// 3. 实现原型链继承 (关键步骤)// 将 Child 的原型指向 Parent 的实例Child.prototypenewParent();// --- 演示潜在问题引用类型共享 ---// 创建两个子类型实例vars1newChild();vars2newChild();console.log(--- 初始状态 ---);console.log(s1.play:,s1.play);// [1, 2, 3]console.log(s2.play:,s2.play);// [1, 2, 3]console.log(s1.play s2.play?,s1.plays2.play);// 输出: true (证明它们共享同一个数组引用)// 修改 s1 的 play 数组s1.play.push(4);console.log(\n--- 修改 s1.play 后 ---);console.log(s1.play:,s1.play);// [1, 2, 3, 4]console.log(s2.play:,s2.play);// ❌ 问题爆发输出 [1, 2, 3, 4]// s2 完全没有操作过 play 数组但它的数据也被改变了console.log(\n--- 结论 ---);console.log(因为 s1 和 s2 共享了原型对象上的同一个 play 数组修改其中一个会影响另一个。);缺点1.在包含有引用类型的数据时会被所有的实例对象所共享容易造成修改的混乱2.创建子类型的时候不能向超类型传递参数。eg:Child.prototype new Parent() 这行代码意味着“现在、立刻创建一个父类实例作为原型”。在这个“现在、立刻”的时刻未来的子实例s1, s2还不存在。未来的子实例将要携带的参数“Alice”, “Bob”也还不存在。因此无法获取也无法传递将来实例化时才会产生的参数二、构造函数继承1.核心思想是在子类型的构造函数内部通过 call 或 apply 方法调用父类型的构造函数。2.目的是让父类构造函数中的代码在子类实例的上下文中执行一次从而将父类的实例属性“复制”到子类实例上。// 1. 定义父类型functionParent(name,age){// 实例属性this.namename;this.ageage;// 引用类型属性 (数组)this.hobbies[reading,coding];console.log(Parent 构造函数执行了名字是:${name});}// 父类型的原型方法 (注意构造函数继承无法继承这个)Parent.prototype.sayHellofunction(){returnHello, I am${this.name};};// 2. 定义子类型functionChild(name,age,school){// 【核心步骤】借用父类构造函数// 将 Parent 函数体内的 this 指向当前正在创建的 Child 实例// 同时传递参数 name 和 ageParent.call(this,name,age);// 子类特有的属性this.schoolschool;}// 3. 创建实例constchild1newChild(Alice,10,No.1 Middle School);constchild2newChild(Bob,12,No.2 Middle School);// 4. 验证结果console.log(--- 实例属性测试 ---);console.log(child1.name);// Alice (成功继承)console.log(child1.age);// 10 (成功继承)console.log(child1.school);// No.1 Middle School (自有属性)console.log(child1.hobbies);// [reading, coding]console.log(\n--- 引用类型隔离测试 (核心优势) ---);child1.hobbies.push(swimming);// 修改 child1 的爱好console.log(child1 hobbies:,child1.hobbies);// [reading, coding, swimming]console.log(child2 hobbies:,child2.hobbies);// [reading, coding]// ✅ 成功child2 没有受到影响因为每个实例都有自己独立的 hobbies 数组console.log(\n--- 原型方法测试 (核心缺陷) ---);try{console.log(child1.sayHello());}catch(e){console.log(❌ 报错:,e.message);// TypeError: child1.sayHello is not a function// ❌ 失败构造函数继承无法继承原型上的方法}1.父类的引用属性不会被共享优化了第一种继承方式的弊端2.解决了不能向超类型传递参数的缺点3.只能继承父类的实例属性和方法不能继承原型属性或者方法三、组合继承// 1. 定义父类functionParent(name){this.namename;this.colors[red,blue];// 引用类型属性}// 父类原型方法Parent.prototype.sayNamefunction(){console.log(this.name);};// 2. 定义子类functionChild(name,age){// 【步骤一】借用构造函数继承属性// 这一步确保了每个 Child 实例都有独立的 name 和 colors// 同时也解决了传参问题Parent.call(this,name);this.ageage;}// 【步骤二】原型链继承方法// 这一步让 Child 的实例可以访问 Parent.prototype 上的 sayName 方法Child.prototypenewParent();// 【步骤三】修正 constructor 指向最佳实践// 因为步骤二重写了 prototypeconstructor 默认指向了 Parent需要指回 ChildChild.prototype.constructorChild;// 3. 测试constchild1newChild(Alice,10);constchild2newChild(Bob,12);// --- 验证属性隔离 (构造函数继承的优点) ---child1.colors.push(green);console.log(child1.colors);// [red, blue, green]console.log(child2.colors);// [red, blue]// ✅ 成功互不影响每个实例都有自己的数组// --- 验证方法复用 (原型链继承的优点) ---child1.sayName();// 输出: Alicechild2.sayName();// 输出: Bob// ✅ 成功都能调用父类原型上的方法// --- 验证 instanceof ---console.log(child1instanceofParent);// trueconsole.log(child1instanceofChild);// true四、原型式继承原型式继承本质直接利用一个已有的对象作为新对象的原型。在 ES5 之前这通常通过一个临时构造函数来实现ES5 之后标准化为 Object.create() 方法。// 1. 定义原型对象lethero{type:Warrior,skills:[Slash],// 引用类型数组getInfo:function(){return${this.type}:${this.skills.join(, )};}};// 2. 创建两个实例lethero1Object.create(hero);lethero2Object.create(hero);// 3. 修改基本类型属性 (隔离)hero1.typeMage;// hero1 自己身上有了 type: Mage// hero2 依然沿原型链读取 hero.type (Warrior)// 4. 修改引用类型属性 (共享 - 灾难!)hero1.skills.push(Fireball);// hero1 没有 skills找到 hero.skills直接修改了那个共享数组// 5. 验证结果console.log(hero1.type);// Mage (隔离成功)console.log(hero2.type);// Warrior (隔离成功)console.log(hero1.skills);// [Slash, Fireball]console.log(hero2.skills);// [Slash, Fireball] ❌ 污染hero2 莫名其妙学会了火球术console.log(hero.skills);// [Slash, Fireball] ❌ 连原型对象都被污染了console.log(hero1.getInfo());// Mage: Slash, Fireballconsole.log(hero2.getInfo());// Warrior: Slash, Fireball (技能共享了但类型没变)五、寄生式继承寄生式继承 原型式继承 (Object.create) 增强对象 (添加新属性/方法)// 1. 原始对象letbook{title:JavaScript Guide,authors:[Author A],getTitles:function(){returnthis.title;}};// 2. 寄生式继承函数functioncreateSpecialBook(originalBook){// 【第一步】原型式继承创建副本letcloneObject.create(originalBook);// 【第二步】“寄生”增强添加新功能// 注意这个方法是在每次调用函数时重新创建的不是共享的clone.printInfofunction(){console.log(Book:${this.title}, Authors:${this.authors.join(, )});};// 还可以修改某些属性如果是引用类型要注意共享问题clone.editionFirst Edition;// 【第三步】返回增强后的对象returnclone;}// 3. 使用letmyBookcreateSpecialBook(book);myBook.printInfo();// Book: JavaScript Guide, Authors: Author Aconsole.log(myBook.edition);// First Editionconsole.log(myBook.getTitles());// JavaScript Guide (继承自 book)六、寄生组合式继承// 【核心工具函数】实现寄生组合式继承的关键functionclone(parent,child){// 1. 使用 Object.create 创建一个新的对象其原型指向 parent.prototype// 这一步替代了 child.prototype new parent()// 好处不会执行 parent 构造函数避免了属性重复初始化和性能浪费child.prototypeObject.create(parent.prototype);// 2. 修正 constructor 指针保持严谨性child.prototype.constructorchild;}// 父类functionParent6(){this.nameparent6;this.play[1,2,3];// 引用类型}Parent6.prototype.getNamefunction(){returnthis.name;};// 子类functionChild6(){// 3. 借用构造函数继承属性只调用一次// 确保每个实例都有独立的 name 和 play 数组Parent6.call(this);this.friendschild5;}// 4. 建立原型链继承方法clone(Parent6,Child6);// 5. 添加子类特有方法Child6.prototype.getFriendsfunction(){returnthis.friends;};letperson6newChild6();es6的class类继承classPerson{constructor(name){this.namename;}getName(){console.log(Person:,this.name);}}classGamerextendsPerson{constructor(name,age){super(name);// 等价于 Parent6.call(this, name)this.ageage;}}