ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
# JS 继承 ![](https://img.kancloud.cn/f0/9b/f09b21f8aa099bebfb74e31316b2e11b_732x335.png) ## 总结 - 原型链继承: 将父类的实例作为子类的原型---- Child.prototype = new Parent() 父类的属性或方法可复用-----引用类型的属性被所有实例共享、创建子类实例时无法向父类传参 - 构造继承: 使用父类的构造函数来增强子类实例--复制父类实例属性给子类--function Child(age) { Parent.call(this, age); } 优点:父类的引用属性不会被共享; 可向父类传参 缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。方法没有实现继承 - 组合继承 子类借助构造函数模式Parent.call(this), 父类的实例赋给子类的原型 Child.prototype = new Parent() 优点:父类的方法可以被复用; 父类的引用属性不会被共享; 子类构建实例时可以向父类传递参数; 缺点: 调用了两次父类的构造函数,子类的原型添加了父类属性,子类的构造函数添加了父类属性 - 原型式继承 用一个函数包装一个对象,然后返回这个函数的调用 -- object.create() -- 浅复制 function createObj(o) { function F() {}; F.prototype = o; return new F(); } 父类方法可以复用------父类的引用属性会被所有子类实例共享; 子类构建实例时不能向父类传递参数 - 寄生式继承 使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力 function createObj(o) { var clone = Object.create(o); clone.sayName = '11'; return clone; } 跟原型式继承的缺点一样,只是外面装个壳 - 寄生组合式继承 寄生:在函数内返回对象然后调用;组合:函数原型等于另一个实例。在函数中用apply或call引入另一个构造函数,可传参  组合继承有一个会两次调用父类的构造函数造成浪费的缺点,寄生组合继承就可以解决这个问题。 - ES6类继承extends ES5的继承,先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.call(this)), ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this; ## **原型链继承** 父类的实例赋给子类的原型, `Child.prototype = new Parent()` ```js function Parent() { this.name = 'czr';} Parent.prototype.getName = function () { console.log(this.name); } function Child() {} Child.prototype = new Parent(); var child1 = new Child(); child1.getName() console.log(child1.name) ``` 优点:父类的属性或方法可以复用 缺点: 1. 引用类型的属性被所有实例共享(最重要的) ,所以不能在不改变其他实例情况下改变。 2. 继承的属性是没有意义的 在创建 Child 的实例时,不能向 Parent 传参 ## **构造函数继承** 将父类构造函数的内容复制给了子类的构造函数: `function Child(age) { Parent.call(this, age); }` ```js function Parent(age) { this.age = age this.names = ['kevin', 'daisy']; this.say = function () { console.log(this.names) } } function Child(age) { Parent.call(this, age); } var child1 = new Child(22); child1.names.push('czr'); console.log(child1.age) console.log(child1.names); // ["kevin", "daisy", "czr"] var child2 = new Child(); console.log(child2.names); // ["kevin", "daisy"] ``` 优点: 父类的引用属性不会被共享; 子类构建实例时可以向父类传递参数 缺点:父类的方法不能复用,子类实例的方法每次都是单独创建的。方法没有实现继承 ```js function Parent(names) { this.say = function () { console.log(this.names) } } // Parent.prototype.say = function () { console.log(this.names) } function Child() { Parent.call(this); } var child1 = new Child('child1'); var child2 = new Child('child2'); console.log(child1.say === child2.say) // false, 如果say方法在父类的原型上则为true ``` ### **组合继承** 核心:原型链继承和构造函数继承的组合,兼具了二者的优点。 子类执行`Parent.call(this)`, 父类的实例赋给子类的原型 `Child.prototype = new Parent()` ```js var num = 0 function Parent(name) { num++ console.log('我是被调用的次数',num) // 1 2 this.name = name; this.colors = ['red', 'blue', 'green']; } Parent.prototype.getName = function () { console.log(this.name) } function Child (name,age) { Parent.call(this,name) this.age = age } Child.prototype = new Parent() // 此处是为了让新创建的实例能够找到创建它的构造函数。比如:child1.constructor // Child.prototype.constructor = Child; var child1 = new Child('czr') console.log(child1.constructor) // [Function: Parent] child1.colors.push('black') console.log(child1.colors) // [ 'red', 'blue', 'green', 'black' ] ``` 优点: 父类的方法可以被复用; 父类的引用属性不会被共享; 子类构建实例时可以向父类传递参数; 缺点: 调用了两次父类的构造函数,第一次给子类的原型添加了父类的name, arr属性,第二次又给子类的构造函数添加了父类的name, arr属性。这种重复创建情况造成了性能上的浪费。 ### **原型式继承** - 与原型链继承的不一致处:用一个空的构造函数去取代执行了 Parent 这个构造函数。 - 核心:原型式继承的object方法本质上是对参数对象的一个浅复制。 ```js // ES5 Object.create 的模拟实现: 将传入的对象作为创建的对象的原型。 function createObj(o) { function F() {} F.prototype = o; return new F(); } var person = { name: 'kevin', ages: [22, 44], foo: () => { console.log(this.name) } } var Child1 = createObj(person); var Child2 = createObj(person); Child1.name = 'heihei'; console.log(Child1.name); // heihei Child1.ages.push(66); console.log(Child1.ages); // [22, 44, 66] console.log(Child1.ages); // [22, 44, 66] ``` 优点:父类方法可以复用 缺点:父类的引用属性会被所有子类实例共享; 子类构建实例时不能向父类传递参数 ## **寄生式继承** 核心:使用原型式继承获得一个目标对象的浅复制,然后增强这个浅复制的能力。 ```js function createObj(o) { var clone = Object.create(o); clone.sayName = function () { console.log('hi'); } return clone; } var person = { name: "obj对象" }; var child1 = createObj(person) child1.sayName() // hi ``` - 优点: 没啥优点 仅是一种思路 - 缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。 ## 寄生组合继承 组合继承有一个会两次调用父类的构造函数造成浪费的缺点,寄生组合继承就可以解决这个问题。 ```js function Parent(name) { this.name = name; this.colors = ['red', 'blue', 'green']; } Parent.prototype.getName = function () { console.log(this.name) } function Child(name, age) { Parent.call(this, name); this.age = age; } function object(o) { function F() {} F.prototype = o; return new F(); } function prototype(child, parent) { var prototype = object(parent.prototype); prototype.constructor = child; child.prototype = prototype; } // 当我们使用的时候: prototype(Child, Parent); ``` ## **ES6 Class extends** - 核心: ES6继承的结果和寄生组合继承相似,本质上,ES6继承是一种语法糖。 - es5与es6的区别 > ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上(Parent.call(this)), > ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this; ```Javascript class A {} class B extends A { constructor(){super();} } ``` ES6实现继承的具体原理: ```Javascript class A {} class B {} Object.setPrototypeOf = function (obj, proto) { obj.__proto__ = proto; return obj; } // B 的实例继承 A 的实例 Object.setPrototypeOf(B.prototype, A.prototype); // B 继承 A 的静态属性 Object.setPrototypeOf(B, A); ``` ------- ------- ------- ------- ------- [参考blog](https://www.cnblogs.com/ranyonsue/p/11201730.html) [参考blog](https://blog.csdn.net/weixin_43606158/article/details/91489176) ```javascript // 首先定义一个父类: //构造函数 function Animal(name) { this.name = name || 'Animal'; this.sleep = function() { console.log(this.name + '正在睡觉!'); }; } //原型上面的方法: Animal.prototype.eat = function(food) { console.log(this.name + '正在吃:' + food); } ``` - ##### **1.原型链继承** ```javascript //核心:将父类的实例作为子类的原型 function Dog() { } Dog.prototype = new Animal(); //将Animal的实例挂载到了Dog的原型链上 //或://Dog.prototype = Object.create(Animal.prototype) Dog.prototype.name = 'dog'; var dog = new Dog(); console.log(dog.name); //dog dog.eat('bone'); //dog正在吃:bone dog.sleep(); //dog正在睡觉! console.log(dog instanceof Animal); //true console.log(dog instanceof Dog); //true ``` - 重点:让新实例的原型等于父类的实例。 - 特点: * 1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!) - 缺点: * 1、新实例无法向父类构造函数传参。 * 2、继承单一。 * 3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!) - ##### **2, 构造继承** ```javascript //核心:使用父类的构造函数增强子类实例,等于是复制父类的实例属性给子类(没用到原型) function Cat(name) { Animal.call(this); this.name = name || 'Tom'; } var cat = new Cat(); console.log(cat.name); //Tom cat.sleep(); //Tom正在睡觉! console.log(cat instanceof Animal); //false console.log(cat instanceof Cat); //true ``` - 重点: 用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制)) - 特点: * 创建子类实例时,可以向父类传递参数 * 可以实现多继承(call多个父类对象) - 缺点: * 实例并不是父类的实例,只是子类的实例 * 只能继承父类的实例属性和方法,不能继承原型属性/方法 * 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能 - ##### **3, 实例继承** ```javascript //核心:为父类实例添加新特性,作为子类实例返回 function Cat(name) { var instance = new Animal(); instance.name = name || 'Tom'; return instance; } var cat = new Cat(); console.log(cat.name); //Tom cat.sleep(); //Tom正在睡觉! console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //false ``` - 特点: * 不限制调用方式,不管是new子类()还是子类(),返回的对象都具有相同的效果 - 缺点: * 实例是父类的实例,不是子类的实例; 不支持多继承 - ##### **4, 拷贝继承** ```javascript function Cat(name){ var animal = new Animal(); for(let i in animal) { Cat.prototype[i] = animal[i]; } Cat.prototype.name = name || 'Tom'; } var cat = new Cat(); console.log(cat.name); //Tom cat.sleep(); //Tom正在睡觉! console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true ``` - 特点:支持多继承 - 缺点: 效率极低,内存占用高(因为要拷贝父类的属性) 无法获取父类不可枚举的方法(for in不能访问到的) - ##### **5, 组合继承(组合原型链继承和借用构造函数继承)(常用)** ```javascript //核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用 function Cat(name) { Animal.call(this); this.name = name || 'Tom'; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; var cat = new Cat(); console.log(cat.name); //Tom cat.sleep(); //Tom正在睡觉 console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true ``` 特点:1、可以继承父类原型上的属性,可以传参,可复用。    2、每个新实例引入的构造函数属性是私有的。 - 缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。 - ##### **6, 寄生组合继承** - 寄生:在函数内返回对象然后调用 - 组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参  ```javascript //核心:通过寄生方式,砍掉父类的实例属性,这样,在调用俩次父类的构造的时候,就不会初始化俩次实例方法/属性,避免了组合继承的缺点。 function Cat(name) { Animal.call(this); this.name = name || 'Tom'; } (function() { var Super = function() {}; //创建一个没有实例的方法类。 Super.prototype = Animal.prototype; Cat.prototype = new Super(); //将实例作为子类的原型。 })(); let cat = new Cat(); console.log(cat.name); //Tom cat.sleep(); //Tom正在睡觉 console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true Cat.prototype.constructor = Cat; //修复构造函数 ```