继承的几种形式

1. 原型链继承

更改原型对象的指向实现继承
function Film(name) {  this.name = '你好,李焕英!';}Film.prototype.getName = () => this.name;function Comedy() {  this.type = '喜剧片';}// 指定父类实例为子类实例的原型Comedy.prototype = new Film();// new 创立新实例对象通过了以下几步:// 1.创立一个新对象// 2.将新对象的proto指向构造函数的prototype对象// 3.将构造函数的作用域赋值给新对象 (也就是this指向新对象)// 4.执行构造函数中的代码(为这个新对象增加属性)// 5.返回新的对象Comedy.prototype.constructor = Comedy;Comedy.prototype.getType = () => this.type;const comedy = new Comedy();console.log(comedy.type);  // '喜剧片'console.log(comedy.getName());   // '你好,李焕英!'console.log(comedy instanceof Comedy); // true console.log(comedy instanceof Film); // trueconsole.log(comedy instanceof Object); // true, js中所有类型都继承原型链顶端Object的实例

长处:
继承父类的属性(办法)
实例既是子类的实例,也是父类的实例

缺点:
繁多继承
所有实例共享父类实例的援用类型属性(办法),若属性被批改会影响所有实例
无奈向父类构造函数传参

2. 构造函数继承

借调父类构造函数实现继承
function Comedy(name, type) {  // 借调父类构造函数来增强子类实例  Film.apply(this, name);  this.type = type;}const comedy = new Comedy('你好,李焕英!', '喜剧片');console.log(comedy.name);  // '你好,李焕英!'console.log(comedy instanceof Comedy); // true console.log(comedy instanceof Film); // false

长处:
可实现多继承,借调多个构造函数
不存在援用类型属性共享问题,能够向父类构造函数传参

毛病:
无奈获取父类原型上的属性
实例只是子类的实例

3. 组合继承(罕用)

联合原型链继承和构造函数继承
function Comedy(name, type) {  // 借调父类构造函数来增强子类实例  Film.apply(this, name);  this.type = type;}Comedy.prototype = new Film();Comedy.prototype.constructor = Comedy;const comedy = new Comedy('你好,李焕英!', '喜剧片');console.log(comedy instanceof Comedy); // true console.log(comedy instanceof Film); // true

长处:
继承了父类原型的属性,实现了构造函数传参
实例及是子类的实例,也是父类的实例

毛病:
调用了两次父类构造函数(耗内存)

4. 原型式继承

借助原型基于已有的对象创立新对象(相当于浅拷贝)
function Film(obj) {    function Comedy() {};    Comedy.prototype = obj;    return new Comedy();}const obj = {    name: '国产电影',    types: ['喜剧片', '动作片', '爱情片'],};const comedy1 = Film(obj);comedy1.types.push('战争片');const comedy2 = Film(obj);console.log(comedy2.types); // ['喜剧片', '动作片', '爱情片', '战争片']// Es6 Object.create实现同样继承成果const comedy1 = Object.create(obj);comedy1.types.push('战争片');const comedy2 = Object.create(obj);console.log(comedy2.types); // ['喜剧片', '动作片', '爱情片', '战争片']

毛病:
无奈向父类构造函数传参
如果父类是一般对象,援用类型属性仍然被子例共享

5. 寄生组合式继承

解决组合继承两次调用父类的问题

function Comedy(name, type) {  // 借调父类构造函数来增强子类实例  Film.apply(this, name);  this.type = type;}(function() {    // 基于原型式继承,借助中间层构造函数实现继承,防止了组合继承中两次调用父类构造函数的问题    const Foo = function() {};    Foo.prototype = Film.prototype;    Comedy.prototype = new Foo();})()const comedy = new Comedy('你好,李焕英!', '喜剧片');console.log(comedy instanceof Comedy); // true console.log(comedy instanceof Film); // true

修复了组合继承的问题

6. ES6类继承

应用extends关键字,实现构造函数+原型的继承形式等同的成果
class Film {    constructor(name) {        this.name = name;    }    getName() {        console.log(this.name);    }}console.log(typeof Film); // 'function', 类申明只是自定义的类型创立的语法糖//ES5模仿类的实现(也称为自定义的类型创立)function Film(name) {    this.name = name;}Film.prototype.getName = () => this.name;class Comedy extends Film {    constructor(type) {        super(name); // 等同于Film.call(this, name);        this.type = type;    }    getType() {        console.log(this.type);    }    getName() {        super.getName(); // '你好,李焕英!', 调用父类被笼罩的办法        console.log('重写父类办法');    }}const comedy = new Comedy('你好,李焕英!', '喜剧片');console.log(comedy instanceof Comedy); // true console.log(comedy instanceof Film); // true

继承与原型链

js对象蕴含一个__proto__属性,大部分浏览器不反对拜访,操作不慎会扭转这个对象的继承原型链

应用Object.getPrototypeOf获取

(1) 基于原型链的继承:

/** 继承属性 **/let f = function() {   this.a = 1;   this.b = 2;}let o = new f();f.prototype.b = 3;f.prototype.c = 4;原型链如下: 如控制台打印后果// {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype ---> nullconsole.log(o.a);  // 1console.log(o.b);  // 2console.log(o.c);  // 4console.log(o.d);  // undefined/** 继承办法 **/const o = {  a: 2,  m: () => this.a + 1;};const p = Object.create(o);p.a = 4;console.log(o.m()); // 3console.log(p.m()); // 5, 当继承的函数被调用时,this 指向的是以后继承的对象

(2) 生成原型链的不同形式

  1. 应用语法结构创立的对象:
var a = ["yo", "whadup", "?"];// 数组都继承于 Array.prototype// (Array.prototype 中蕴含 indexOf, map 等办法)// 原型链:// a ---> Array.prototype ---> Object.prototype ---> null
  1. 应用结构器创立的对象:
function Graph() {  this.vertices = [];  this.edges = [];}Graph.prototype = {  addVertex: function(v){    this.vertices.push(v);  }};const g = new Graph();// 原型链:// g ---> Graph.prototype ---> Object.prototype ---> null
  1. Object.create()创立的对象
var a = {a: 1};// a ---> Object.prototype ---> nullvar b = Object.create(a);// b ---> a ---> Object.prototype ---> nullvar c = Object.create(b);// c ---> b ---> a ---> Object.prototype ---> nullvar d = Object.create(null);// d ---> null

总结:

原型链查找属性比拟耗费性能,查找不存在的属性时会遍历整个原型链,应防止这样的操作

if(!g.hasOwnProperty(addVertex)) {   不执行操作}

prototype 和 Object.getPrototypeOf()

prototype 是用于类的,而 Object.getPrototypeOf() 是用于实例的(instances),两者性能统一
const a1 = new A(); const a2 = new A();// Object.getPrototypeOf(a1).doSomething == Object.getPrototypeOf(a2).doSomething == A.prototype.doSomething// a1.doSomething() 相当于执行 Object.getPrototypeOf(a1).doSomething.call(a1) == A.prototype.doSomething.call(a1))

论断:

在应用原型继承编写简单代码之前,了解原型继承模型是至关重要的。此外,请留神代码中原型链的长度,并在必要时将其合成,以防止可能的性能问题。此外,原生原型不应该被扩大,除非它是为了与新的 JavaScript 个性兼容