系列文章
前端八股文-手写new、bind、call、apply的三两事
类式继承
原理:
Son.prototype = new Father(),即解释为将父类的实例化对象赋值给子类的原型
子类可以访问到父类原型上的属性和方法,也能访问构造函数中的属性和方法
实现:
// 声明父类
function Father() {
this.fatherValue = true;
}
// 父类添加原型方法
Father.prototype.getFatherValue = () => this.fatherValue
// 声明子类
function Son() {
this.sonValue = false;
}
// ********** 继承父类 **********
Son.prototype = new Father();
// 子类添加原型方法
Son.prototype.getSonValue = () => this.sonValue;
解惑:
new Son.()__proto__ => Son.prototype === new Father() // 即Father实例
new Father().__proto__ => Father.prototype
弊端:
- 父类中共有属性存在引用类型,子类更改会对父类产生影响,从而影响其他子类
- 子类初始化,无法向父类传递参数,因而实例化父类的时候无法对父类构造函数内的属性进行初始化
构造函数继承
原理:
申明子类,执行Father.call(this, ...agruments)
只继承父构造函数中的属性和方法
实现:
// 声明父类
function Father(id) {
this.id = id;
this.books = ['javascript', 'java', 'css'];
}
// 父类添加原型方法
Father.prototype.showBooks = () => this.books
// 声明子类
function Son(id) {
Father.call(this,id)
}
var instance1 = new Son(10);
var instance2 = new Son(11);
instance1.books.push('html');
console.log(instance1.books ) // ['javascript', 'java', 'css', 'html']
console.log(instance2.books ) // ['javascript', 'java', 'css']
instance1.showBooks() // TypeError
解惑:
弊端:
- 此类型的继承没有涉及原型prototype
- 若要继承,必须将方法属性放到构造函数中用this绑定
组合继承
原理:
申明子类,执行Father.call(this, ...agruments)
Son.prototype = new Father(),
// 声明父类
function Father(id) {
this.id = id;
this.books = ['javascript', 'java', 'css'];
}
// 父类原型共有方法
Father.prototype.getId = () => {
console.log(this.id)
}
// 声明子类
function Son(id, time) {
// 构造函数继承
Father.call(this,id)
this.time = time;
}
// 类式继承
Son.prototype = new Father();
Son.prototype.getTime = () => {
console.log(this.time);
}
// 测试用例
var instance1 = new Son(1, 2020);
instance1.books.push('html')
console.log(instance1.books)// ['javascript', 'java', 'css', 'html']
instance1.getId(); // 1
instance1.getTime(); // 2020
var instance2 = new Son(2, 2021);
console.log(instance2.books); // ['javascript', 'java', 'css'];
instance1.getId(); // 2
instance1.getTime(); // 2021
解惑:
弊端:
- 使用构造函数继承,调用了一次父类的构造函数,子类原型的类式继承又调用了一次父类构造函数。(父类构造函数调用了两次)
原型式继承
原理:
创建一个纯净的函数,将父类对象赋值给纯净函数的原型
实现:
function inheritObject(o) {
// 申明一个过渡函数
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F();
}
// 测试代码
var animals = {
name: '动物集合',
specialList: ['dog', 'cat', 'mouse']
}
var newAnimals = inheritObject(animals);
newAnimals.name = '神话动物';
newAnimals.specialList.push('dragon');
var otherAnimals = inheritObject(animals);
otherAnimals.name = '其他动物';
console.log(newAnimals.name) // 神话动物
console.log(otherAnimals.name) // 其他动物
console.log(newAnimals.specialList) // ['dog', 'cat', 'mouse', 'dragon']
console.log(otherAnimals.specialList) // ['dog', 'cat', 'mouse', 'dragon']
解惑:
弊端
- 与类式继承相同,父类中共有属性存在引用类型,子类更改会对父类产生影响,从而影响其他子类
寄生式继承
原理:
原型继承的二次封装
实现:
function inheritObject(o) {
// 申明一个过渡函数
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F();
}
// 申明基础对象
var animals = {
name: '动物集合',
specialList: ['dog', 'cat', 'mouse']
}
function createAnimals(obj) {
var o = inheritObject(obj);
o.getNmae = () => {console.log(name)}
return o;
}
解惑:
弊端:
寄生组合继承
原理:
复制一份父类的原型副本(将父类原型赋值给纯净的全新构造函数),然后再赋值给子类原型,同时修正增强constructor
实现方案一:
function inheritObject(o) {
// 申明一个过渡函数
function F() {}
// 过渡对象的原型继承父对象
F.prototype = o;
// 返回过渡对象的一个实例,该实例的原型继承了父对象
return new F();
}
function inheritPrototype(SonClass, FatherClass) {
// 干净的FatherClass实例,只带了原型上的方法和属性
let p = inheritObject(FatherClass.prototype)
console.log('检验p是不是FatherClass的实例', p instanceof FatherClass)
console.log('实例化后的洁净实例对象', p)
p.constructor = SonClass // 此时p为了作为SonClass的原型对象,需要更改constructor的指向,p的_proto_指向FatherClass的原型
SonClass.prototype = p
}
// 测试代码
// 声明父类
function Father(id) {
this.id = id;
this.books = ['javascript', 'java', 'css'];
}
// 父类原型共有方法
Father.prototype.getId = () => {
console.log(this.id)
}
// 声明子类
function Son(id, time) {
// 构造函数继承
Father.call(this,id)
this.time = time;
}
inheritPrototype(Son, Father);
Son.prototype.getTime = () => {
console.log(this.time)
}
实现方案二(Object.create)
function inheritPrototype(subType, superType){
var protoType = Object.create(superType.prototype); //创建对象
protoType.constructor = subType; //增强对象
subType.prototype = protoType; //指定对象
}
解惑:
与组合继承最大的不同在于,父类构造函数不执行两遍。