乐趣区

关于javascript:js中的继承实现

继承的几种形式

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); // true
console.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 ---> null
console.log(o.a);  // 1
console.log(o.b);  // 2
console.log(o.c);  // 4
console.log(o.d);  // undefined

/** 继承办法 **/
const o = {
  a: 2,
  m: () => this.a + 1;};
const p = Object.create(o);
p.a = 4;
console.log(o.m()); // 3
console.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 ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var 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 个性兼容

退出移动版