-
原型链继承
核心:将父类的实例作为子类的原型
//父类型 function Person(name, age) { (this.name = name), (this.age = age), (this.play = [1, 2, 3]); this.setName = function () {}; } Person.prototype.setAge = function () {}; // 子类 function Student(price) { this.price = price; this.setScore = function () {}; } Student.prototype = new Person(); // 子类型的原型为父类型的一个实例对象 var s1 = new Student(15000); var s2 = new Student(14000); console.log(s1, s2);
-
子类的实例就可以通过proto访问到 Student.prototype 也就是 Person 的实例,这样就可以访问到父类的私有方法,然后再通过proto指向父类的 prototype 就可以获得到父类原型上的方法。于是做到了将父类的私有、公有方法和属性都当做子类的公有属性
优点:
- 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
- 父类新增原型方法/原型属性,子类都能访问到
- 简单,易于实现
缺点:
- 要想为子类新增属性和方法,必须要在 new Animal()这样的语句之后执行,不能放到构造器中
- 无法实现多继承
- 来自原型对象的引用属性是所有实例共享的
- 创建子类实例时,无法向父类构造函数传参
-
构造函数继承
核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Person(name, age) { (this.name = name), (this.age = age), (this.setName = function () {}); } Person.prototype.setAge = function () {}; function Student(name, age, price) { Person.call(this, name, age); // 相当于: this.Person(name, age) /*this.name = name this.age = age*/ this.price = price; } var s1 = new Student("Tom", 20, 15000);
优点:
- 解决了 1 中,子类实例共享父类引用属性的问题
- 创建子类实例时,可以向父类传递参数
- 可以实现多继承(call 多个父类对象)
缺点:
- 实例并不是父类的实例,只是子类的实例
- 只能继承父类的实例属性和方法,不能继承原型属性/方法
- 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能
-
原型链+借用构造函数的组合继承
核心: 通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
function Cat(name, age) { Animal.call(this, name); this.age = age; this.run = function () {}; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; // 注意:这里必须改变子类原型的构造函数 // Test Code var cat = new Cat("red", 27); console.log(cat.name); console.log(cat.sleep()); console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
优点:
- 可以继承实例属性/方法,也可以继承原型属性/方法
- 不存在引用属性共享问题
- 可传参
- 函数可复用
缺点:
- 调用了两次父类构造函数,生成了两份实例
-
组合继承优化 1:
核心: 这种方式通过父类原型和子类原型指向同一对象,子类可以继承到父类的公有方法当做自己的公有方法,而且不会初始化两次实例方法/属性,避免的组合继承的缺点。
function Person(name, age) { (this.name = name), (this.age = age), (this.setAge = function () {}); } Person.prototype.setAge = function () { console.log("111"); }; function Student(name, age, price) { Person.call(this, name, age); this.price = price; this.setScore = function () {}; } Student.prototype = Person.prototype; Student.prototype.sayHello = function () {}; var s1 = new Student("Tom", 20, 15000); console.log(s1);
优点:
- 不会初始化两次实例方法/属性,避免的组合继承的缺点
缺点:
- 没办法辨别是实例是子类还是父类创造的,子类和父类的构造函数指向是同一个
-
组合继承优化 2
核心: 借助原型可以基于已有的对象来创建对象,var B = Object.create(A)以 A 对象为原型,生成了 B 对象。B 继承了 A 的所有属性和方法
function Person(name, age) { (this.name = name), (this.age = age); } Person.prototype.setAge = function () { console.log("111"); }; function Student(name, age, price) { Person.call(this, name, age); this.price = price; this.setScore = function () {}; } Student.prototype = Object.create(Person.prototype); //核心代码 Student.prototype.constructor = Student; //核心代码 var s1 = new Student("Tom", 20, 15000); console.log(s1 instanceof Student, s1 instanceof Person); // true true console.log(s1.constructor); //Student console.log(s1);
优点:
- 堪称完美
缺点:
- 实现较为复杂
-
ES6 中的 Class 继承
核心: ES5 的继承,实质是先创造子类的实例对象 this,然后再将父类的方法添加到 this 上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this 上面(所以必须先调用 super 方法),然后再用子类的构造函数修改 this
class Person { //调用类的构造方法 constructor(name, age) { this.name = name; this.age = age; } //定义一般的方法 showName() { console.log("调用父类的方法"); console.log(this.name, this.age); } } let p1 = new Person("kobe", 39); console.log(p1); //定义一个子类 class Student extends Person { constructor(name, age, salary) { super(name, age); //通过super调用父类的构造方法 this.salary = salary; } showName() { //在子类自身定义方法 console.log("调用子类的方法"); console.log(this.name, this.age, this.salary); } } let s1 = new Student("wade", 38, 1000000000); console.log(s1); s1.showName();
优点:
- 语法简单易懂,操作更方便
缺点:
- 并不是所有的浏览器都支持 class 关键字,可以借助 Babel 工具语法降级