共计 4427 个字符,预计需要花费 12 分钟才能阅读完成。
继承的几种形式
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) 生成原型链的不同形式
- 应用语法结构创立的对象:
var a = ["yo", "whadup", "?"];
// 数组都继承于 Array.prototype
// (Array.prototype 中蕴含 indexOf, map 等办法)
// 原型链:
// a ---> Array.prototype ---> Object.prototype ---> null
- 应用结构器创立的对象:
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
- 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 个性兼容