对象A通过继承B对象,就能直接拥有B对象的所有属性和方法,这对于代码的复用是非常有用的

1.原型链 继承

  1. function Parent() {
  2. this.name = "chu"
  3. }
  4. Parent.prototype.getName = function () {
  5. console.log(this.name)
  6. }
  7. function Child() {}
  8. Child.prototype = new Parent()
  9. Child.prototype.constructor = Child
  10. const child = new Child()
  11. console.log(child.getName()) //chu

问题:
1.引用类型(对象)的属性被所有实例共享,例子

  1. function Parent() {
  2. this.names = ["chu", "chu1"]
  3. }
  4. function Child() {}
  5. Child.prototype = new Parent()
  6. var child1 = new Child()
  7. child1.names.push("yayu")
  8. console.log(child1.names) //["chu", "chu1", "yayu"]
  9. var child2 = new Child()
  10. console.log(child2.names) //["chu", "chu1", "yayu"]

2.在创建 Child 的实例时,不能向 Parent 传参

2.借用构造函数(经典继承 )

就是子类构造函数中使用 apply 或 call 调用父类构造函数
本来,父类构造函数中的 this 是父类的实例,但是这里,在子类构造函数中通过 call(this) 调用了父类构造函数,把上下文修改为了子类实例,相当于把父类实例的属性给子类实例复制了一份

  1. function Parent() {
  2. this.names = ["chu", "chu1"]
  3. }
  4. //子类本身没有实例属性 但是借用了父类实例
  5. function Child() {
  6. Parent.call(this)
  7. }
  8. var child1 = new Child()
  9. child1.names.push("yayu")
  10. console.log(child1.names) //["chu", "chu1", "yayu"]
  11. var child2 = new Child()
  12. console.log(child2.names) // ["chu", "chu1"]

优点:
1.避免了引用类型的属性被所有实例共享
2.可以在Child 中向Parent 传参

  1. function Parent(name) {
  2. this.name = name
  3. }
  4. function Child(name) {
  5. Parent.call(this, name)
  6. }
  7. var child1 = new Child("kevin")
  8. console.log(child1.name)
  9. var child2 = new Child("daisy")
  10. console.log(child2.name)

缺点:方法都在构造函数中定义,每次创建实例都会创建一遍方法


补充:
缺点:父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法。
只能继承父类的实例属性和方法,不能继承原型属性或方法。
见下图

  1. function Parent1() {
  2. this.name = "parent1";
  3. }
  4. Parent1.prototype.getName = function () {
  5. return this.name;
  6. };
  7. function Child1() {
  8. Parent1.call(this);
  9. this.type = "child1";
  10. }
  11. let child = new Child1();
  12. console.log(child); // 没问题
  13. console.log(child.getName()); // 会报错

image.png

image.png

3.组合继承

就是原型链继承+借用构造函数

  • 使用借用构造函数的方法,复制一份父类实例p的属性到子类实例c上
  • 使用原型链的方法,把子类实例挂到原型链上,使得子类实例也能够访问父类原型对象上的属性和方法 ```javascript function Parent(name){ this.name = name this.colors = [“red”,”blue”,”green”] }

Parent.prototype.getName = function(){ console.log(this.name) }

function Child(name,age){ //第二次调用Parent() Parent.call(this,name) this.age = age } //第一次调用Parent() Child.prototype = new Parent() //手动挂上构造器,指向自己的构造函数 Child.prototype.constructor = Child

var child1 = new Child(“kevin”,18) child1.colors.push(“black”) console.log(child1.name) console.log(child1.age) console.log(child1.colors)

var child2 = new Child(“daisy”,20)

  1. 优点:融合原型链继承和构造函数的优点<br />缺点:因为使用了原型链,一个子类实例将会持有两份父类实例的数据,一份是 Parent.call(this) 复制到子类实例c上的数据,一份是父类实例原本的数据,位于 c._ _proto__ 上。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/618347/1611618477032-94d01fc5-18df-47f4-8f9b-442ab8574b4a.png#align=left&display=inline&height=289&margin=%5Bobject%20Object%5D&name=image.png&originHeight=289&originWidth=449&size=19577&status=done&style=none&width=449)
  2. 补充:Parent执行了两次,第一次是改变Childprototype的时候,第二次是通过call方法调用Parent多构造一次就多进行了一次性能开销。
  3. <a name="yg6cP"></a>
  4. ## 4.原型式继承
  5. ES5 `Object.create` 的模拟实现,将传入的对象作为创建的对象的原型
  6. 创建一个空对象,并把它挂载到另一个对象的原型链上
  7. ```javascript
  8. function createObj(o){
  9. function F(){}
  10. F.prototype = o
  11. return new F()
  12. }

缺点:包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样

  1. function createObj(o){
  2. function F(){}
  3. F.prototype = o
  4. return new F()
  5. }
  6. var person = {
  7. name:"kevin",
  8. friends:["daisy","kelly"]
  9. }
  10. var person1 = createObj(person)
  11. var person2 = createObj(person)
  12. console.log(person1) //F {}
  13. //注意:修改person1.name的值,person2.name的值并未发生改变,
  14. 并不是因为person1person2有独立的 name 值,而是因为person1.name = 'person1'
  15. person1添加了 name 值,并非修改了原型上的 name 值。
  16. person1.name = "person1"
  17. console.log(person2.name) //值类型
  18. person1.friends.push("taylor")
  19. console.log(person2.friends) // ["daisy", "kelly", "taylor"]


5.寄生式继承

原型式继承的增强版。使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法。

创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。

  1. function createObj(o){
  2. //使用了前面的 createObject 函数,生成了一个子类实例
  3. var clone = Object.create(o)
  4. //先在子类实例上添加一点属性或方法
  5. clone.sayName = function(){
  6. console.log("hi")
  7. }
  8. //再返回
  9. return clone
  10. }
  1. let parent5 = {
  2. name: "parent5",
  3. friends: ["p1", "p2", "p3"],
  4. getName() {
  5. return this.name;
  6. },
  7. };
  8. function clone(original) {
  9. let clone = Object.create(original);
  10. clone.getFriends = function () {
  11. return this.friends;
  12. };
  13. return clone;
  14. }
  15. let person5 = clone(parent5);
  16. console.log(person5.getName());
  17. console.log(person5.getFriends());

6.寄生组合式继承(常用)

借用构造函数继承 + 寄生式继承

组合继承的缺点,会有两份父类实例的数据。这两份数据中,通过Parent.call(this) 复制到子类实例 c 上的这一份是真正需要的,而 c. proto 上的这一份是多余的,是原型链的副作用。

优化:把子类实例添加到父类实例的原型链上,同时又不让父类实例的属性和方法也在原型链上

  • 创建一个没有实例属性的父类实例
  • 让子类实例绕过父类实例,直接继承父类的原型对象
  1. function Parent(name) {
  2. this.name = name
  3. this.colors = ["red", "blue", "green"]
  4. }
  5. Parent.prototype.getName = function () {
  6. console.log(this.name)
  7. }
  8. function Child(name, age) {
  9. Parent.call(this, name)
  10. this.age = age
  11. }
  12. var child1 = new Child("kevin", 18)
  13. console.log(child1)

image.png

封装

  1. function object(o){
  2. function F(){}
  3. F.prototype = o
  4. return new F()
  5. }
  6. function prototype(child,parent){
  7. var prototype = object(parent.property)
  8. prototype.constructor = child
  9. child.prototype = prototype
  10. }
  11. prototype(Child,Parent)

例子

  1. function clone(parent,child){
  2. child.prototype = Object.create(parent.prototype)
  3. child.prototype.constructor = child
  4. }
  5. function Parent6(){
  6. this.name = 'parent6'
  7. this.play = [1,2,3]
  8. }
  9. Parent6.prototype.getName = function(){
  10. return this.name
  11. }
  12. function Child6(){
  13. Parent6.call(this)
  14. this.friends = 'child5'
  15. }
  16. clone(Parent6,Child6)
  17. Child6.prototype.getFriends = function(){
  18. return this.friends
  19. }
  20. let person6 = new Child6()
  21. console.log(person6)
  22. console.log(person6.getName())
  23. console.log(person6.getFriends())

ES6 extends

https://es6.ruanyifeng.com/#docs/class-extends

  1. class Person {
  2. constructor(name) {
  3. this.name = name;
  4. }
  5. // 原型方法
  6. // Person.prototype.getName=function(){}
  7. getName() {
  8. console.log(this.name);
  9. }
  10. }
  11. class Gamer extends Person {
  12. constructor(name, age) {
  13. //子类中存在构造函数 则需要在使用this之前调用super()
  14. super(name);
  15. this.age = age;
  16. }
  17. }
  18. const asuna = new Gamer("Asuna", 20);
  19. asuna.getName();

文章
https://github.com/mqyqingfeng/Blog/issues/16

https://lijing0906.github.io/post/jsInherit

https://segmentfault.com/a/1190000015727237