说来惭愧,对于 JavaScript 继承我一直都半知半觉,今天我好像突然茅塞顿开了......,也许未来的某一天我也会说这话。
我相信,对于前端开发人员来说,ES5 继承大家并不陌生,让你讲你也会娓娓道来,但是如果写下来,或许可能不太熟练。
我是基于 JavaScript 高级设计程序第三版来学习的,以下是我的一些笔记,与君共享,有不对的地方望大家指出。
ES5 继承
继承是面向对象语言一个重要特性,它允许创建子类时继承父类的属性和方法,从而实现代码的复用,减少冗余并支持模块化设计。JavaScript 中的继承主要是通过原型链实现的。
说起原型链, [[Prototype]] , prototype , proto , create , setPrototypeOf , getProtetypyOf...一系列 API 会自然浮现在脑海中, JavaScript 中每个对象都有一个私有属性指向另一个名为原型(prototype)的对象。原型对象也有一个自己的原型,层层向上直到一个对象的原型为 null。它们形成的一条称为原型链(prototype chain)的玩意。
ES5 中继承分为原型链继承 , 借用构造函数继承 , 组合继承 , 原型式继承 , 寄生式继承 , 寄生组合式继承六种。看着头就大, 对吧, 但是也有记忆技巧的。
- 原型链继承: 子类的原型指向父类的一个实例
- 借用构造函数继承: 子类在构造函数中调用父类的构造函数
- 组合继承: 原型链继承 + 借用构造函数继承
- 原型式继承: 创建一个临时性的构造函数,将传入的对象作为这个构造函数的原型,最后返回这个临时类型的一个新实例
- 寄生式继承: 创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象
- 寄生组合式继承: 集寄生式继承和组合继承的优点而存在的类
其中:
- 1 和 4 有同样的缺点(引用类型的属性值会被统一修改)
- 2 和 5 有同样的缺点(方法无法复用)
- 3 是 1 和 2 的结合版, 6 是 3 的优化版, 6 优化了 3 的会调用两次超类型构造函数的缺点(减少了一次)
原型链继承
缺点: 引用类型的值会被统一修改, 无法向超类传递参数(传递的参数会被统一影响)
js
function SuperType(){
this.colors = ["red", "blue", "green"];
}
function SubType(age){
this.age = age
}
SubType.prototype = new SuperType();
var instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // [red,blue,green,black]
var instance2 = new SubType();
console.log(instance2.colors); // [red,blue,green,black]
借用构造函数继承
缺点: 方法无法复用, 每次创建子类实例都会创建一遍方法
js
function superType(){
this.colors = ["red", "blue", "green"];
// 构造函数中定义方法,无法复用(如果把方法拿到外面,然后赋值引用,虽然可以解决问题,但是全局会被污染,捡了芝麻丢了西瓜的操作不可取)
this.showColors = function(){
console.log(this.colors.join(","));
}
}
function subType(){
superType.call(this);
}
var instance1 = new subType();
instance1.colors.push("black");
console.log(instance1.colors); // [red,blue,green,black]
var instance2 = new subType();
console.log(instance2.colors); // [red,blue,green]
组合继承
缺点: 使用 call 和 new 时, 父类构造函数都会执行
js
function SuperType(name, color) {
this.name = name;
this.color = color;
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name, color, age) {
// 构造函数继承:继承属性
SuperType.call(this, name, color);
this.age = age; // 子类特有的属性
}
// 原型链继承:继承方法
SubType.prototype = new SuperType(); // 将子类型的原型指向父类型的实例
SubType.prototype.constructor = SubType; // 修复构造函数指向
// 添加子类自己的方法
SubType.prototype.sayAge = function() {
console.log(this.age);
};
var instance = new SubType('Tom', ['red', 'blue'], 25);
instance.sayName(); // Tom
instance.sayAge(); // 25
console.log(instance.color); // [ 'red', 'blue' ]
原型式继承
缺点: 引用类型的值会被统一修改
js
function object(o){
var Fn = function(){};
Fn.prototype = o;
return new Fn();
}
var obj = {
name: 'obj',
colors:['red','blue']
}
var o1 = object(obj);
var o2 = object(obj);
o1.colors.push('black');
console.log(o2.colors); // ['red', 'blue', 'black']
寄生式继承
缺点: 方法无法复用
js
function createAnother(original){
var clone = Object.create(original); // 通过调用函数创建一个新对象
clone.sayHi = function(){ // 以某种方式来增强这个对象 (这个方法无法复用)
console.log('hi');
};
return clone; // 返回这个对象
}
var obj = {
name: 'obj',
colors:['red','blue'],
}
var o1 = createAnother(obj);
var o2 = createAnother(obj);
o1.colors.push('black');
console.log(obj.colors); // ['red', 'blue', 'black']
寄生组合式继承
ES6 class 类的语法糖
js
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 创建对象
prototype.constructor = subType; // 增强对象
subType.prototype = prototype; // 指定对象的原型
}
function SuperType(name){
this.name = name
this.colos = ['red','blue']
}
SuperType.prototype.sayName = function(){
console.log(this.name)
}
function SubType(name,age){
SuperType.call(this,name)
this.age = age
}
inheritPrototype(SubType,SuperType)
SubType.prototype.sayAge = function(){
console.log(this.age)
}
var instance = new SubType('Tom', 25);
instance.sayName(); // Tom
instance.sayAge(); // 25
instance.colos.push('yellow');
console.log(instance.colos); // ['red', 'blue', 'yellow']