原型链继承
// 在 Father 构造函数中声明默认属性
function Father() {
this.age = 13
}
// 在 Father 的原型中定义方法
Father.prototype.getAge = function () {
console.log(this.age)
}
// 将 Son 的原型设为 Father 的实例 --- 继承
function Son() {}
Son.prototype = new Father()
// 继承后构建 Son 实例
var son = new Son()
// 结果打印 13
// 说明 Son 构造函数继承了 Father 的属性及方法
console.log(son.getAge())
- 缺点一 — 引用类型的属性被所有实例共享
// 1. 在 Father 构造函数中声明默认属性
function Father() {
this.property = ['car', 'house']
}
// 2. 将 Son 的原型设为 Father 的实例 --- 继承
function Son() { }
Son.prototype = new Father()
// 继承后构建 Son 实例,并打印 property 属性
var son1 = new Son()
console.log('son1.property: ' + son1.property)
// 向 property 中添加元素,并再次打印
son1.property.push('watch')
console.log('Modified son1.property: ' + son1.property)
// 3. 构建一个新实例 son2,并打印其 property
var son2 = new Son()
// 结果不是 Father 中的数组而是经 son1 修改过的数组
// 说明引用类型的属性会被所有实例共享
console.log('son2.property: ' + son2.property)
打印结果
- 缺点二 — Son 构建实例时不能向 Father 传参
因为在构建 Son 实例未曾调用过 Father 构造函数,故无法传参
借用构造函数 — 经典继承
// 1. 定义 Father 构造函数
// 利用 name、age 测试传参
// 利用 books 来测试引用类型属性继承情况
function Father(name, age) {
this.name = name
this.age = age
this.books = ['Java', 'Python']
}
// 2. 定义 Son 构造函数并在构造使调用 Father 的构造函数
function Son(name, age) {
Father.call(this, name, age)
}
// 3. 构建 Son 实例,并打印传参情况
// 说明确实调用了 Father 构造函数并进行了传参
var son1 = new Son('PPY', 3)
console.log(son1.name + ' ' + son1.age) // PPY 3
console.log(son1.books) // (2) ['Java', 'Python']
// 修改引用类型属性的值
son1.books.push('JavaScript')
console.log(son1.books) // (3) ['Java', 'Python', 'JavaScript']
// 4. 构建 Son 新实例,测试其引用类型属性
// 说明直接调用构造函数的方式会为每个实例配备一个数组属性的拷贝
// 一个同一个原型的实例不会互相影响
var son2 = new Son('APC', 4)
console.log(son2.name + ' ' + son2.age) // APC 4
console.log(son2.books) // (2) ['Java', 'Python']
- 优点
- 可以实现传参
- 引用类型属性不会互相影响
- 缺点
- 每次构建实例都会调用一次 Father 的构造函数
- 公用属性和方法被重复创建,增大内存消耗
组合继承 — 最常用
原型链继承及借用构造函数继承的结合
既可以使得每个实例有自己的属性,又可以实现一些属性和方法的复用
// 1. 定义构造函数 Mother
function Mother(name) {
this.name = name
this.color = ['red', 'green']
this.getName = function() {
console.log(this.name)
}
}
// 2. 调用 Mother 的构造函数实现拷贝父类属性
function Son(name, age) {
Mother.call(this, name)
this.age = age
}
// 3. 将 Mother 类设为 Son 类的原型
Son.prototype = new Mother()
// 上句会把 Son 的构造函数改为 Mother 需要改回来
Son.prototype.constructor = Son
// 4. 测试继承情况
// 从结果可以看出,son1 可以访问到 color,说明已继承
// son1 的原型也已经由 Object 改为 Mother
var son1 = new Son('PPY', 3)
console.log(son1.name + ' ' + son1.color) // PPY red,green
console.log(son1.__proto__) // Mother {name: undefined, color: Array(2) ...}
// 5. 测试拷贝情况
// 从结果可以看出,在实例中修改的 color 是副本,原型中的 color 没有发生变化
son1.color.push('black')
console.log('son1 color: ' + son1.color)
console.log('prototype color: ' + son1.__proto__.color)
- 优点
- 拥有两种继承方式的优点,既保留了原型的共有属性,由实现了实例的属性私有
- 缺点
- 仍然是内存浪费
- 调用了两次父类构造函数
原型式继承
ES5 Object.create 的模拟实现
function createObj(Origin) {
function F() { }
F.prototype = Origin
return new F()
}
- 缺点与原型链式继承相同
- 都不能处理好引用类型属性
寄生式继承
在
createObj()
中封装能处理引用类型的函数
- 缺点 — 每个父类的函数都会被重新定义一次
寄生组合式继承
TODO 不太懂 后面补
// 原型式继承
function creatObj(Origin) {
function F() {} // 临时构造函数
F.prototype = Origin // 使该构造函数的原型改为 Origin
return new F() // 返回该临时构造函数实例
}
// 寄生组合式继承
function inherent(Target, Origin) {
var proto = creatObj(Origin.prototype) // 获得原型为 Origin.prototype 的实例
proto.constructor = Target // 将该实例的构造函数改为 Target 的构造函数
Target.prototype = proto // 将 Target 的原型改为临时构造函数实例
}