ES6之前实现JS继承的方案

180 阅读4分钟

引言

作为一个前端应届生,最近在看coderwhy王红元老师的JS高级课程,其中讲到了JS实现继承的几种方案,在这里总结并说说自己实现的一个方案。

1.原型链继承方案

function Person(name,age){
  this.myName=name;
  this.age=age;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

function Student(){
  this.sno=0121
}
// 创建一个Person对象作为Student原型,让Student实例通过原型链查找到Person.prototype
Student.prototype=new Person()

Student.prototype.study=function(){
  console.log('studying~');
}

const Lihua=new Student()
const Mayang=new Person()
Lihua.sayHi() //Hi~
Lihua.study() //studying~
Mayang.study() //报错,函数不存在

/*问题
1.new了一个Person实例,存在许多不必要的属性,例如:name:undefined
2.无法传递person上的参数
3.多个Student实例的原型指向同一个Person对象,一变都变(但这里无法传递name,age参数,无影响)
*/

原型链继承方案问题2的解决思考

没办法传递参数,把Student.prototype=new Person()这行代码放到Student构造函数里面不就可以了? 于是我写成了这个样子

function Person(name,age){
  this.myName=name;
  this.age=age;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

function Student(sno,name,age){
  this.sno=sno
  Student.prototype=new Person(name,age)
  Student.prototype.study=function(){
  console.log('studying~');
}

const Lihua=new Student(0121,'Lihua',22)

Lihua.sayHi()
Lihua.study() 

然后就发现,两个函数全部无法被找到。 打印console.log(Student.prototype),发现确实已经指向这个新的Person对象Person { myName: 'Lihua', age: 22, study: [Function (anonymous)] }。
问题出在当执行const Lihua=new Student(0121,'Lihua',22)这一行代码时,Lihua.__proto__已经指向了Student原本的prototype,在执行Student.prototype=new Person(name,age)改变Student的prototype已经为时已晚,所以第一个创造的实例指向的是一个原始的prototype,不存在我们定义的任何属性和方法。 第二个创造的实例通过原型链可以正确查找到我们创造的Person实例

const Lihua=new Student(121,'Lihua',22)
const meimei=new Student(122,'meimei',23)
meimei.study() //studying
console.log(meimei.sno); //122
console.log(meimei.age); //23

(解决方案) 把Student.prototype=new Person(name,age)变成
Student.prototype__proto__=new Person(name,age)

2.借用构造函数解决以上问题

function Person(name,age,friends){
  this.myName=name;
  this.age=age;
  this.friends=friends;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

function Student(sno,name,age,friends){
  this.sno=sno
  // 借用构造函数
  Person.call(this,name,age,friends)
}
Student.prototype=new Person()
Student.prototype.study=function(){
  console.log('studying~');
 }
const Lihua=new Student(121,'Lihua',21,['aaa','bbb'])
const meimei=new Student(122,'meimei',22,['ccc'])
meimei.study() // studying~
Lihua.sayHi() // Hi~
console.log(Lihua.friends); // [ 'aaa', 'bbb' ]
console.log(meimei.friends); // [ 'ccc' ]

/*问题
1.Person函数多次调用
2.仍然存在许多不必要的属性在Student上,例如:name:undefined
*/

在Student构造函数利用Person.call(this,name,age,friends),相当与在这里放了

  this.myName=name;
  this.age=age;
  this.friends=friends;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }

Student构造函数就相当于变成了这样

function Student(sno,name,age,friends){
  this.sno=sno
  this.myName=name;
  this.age=age;
  this.friends=friends;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

解决了无法传递person上的参数的问题,但此时仍然执行了Student.prototype=new Person(),存在空间浪费问题。

Student.prototype=new Person()
Student.prototype.study=function(){
  console.log('studying~');
 }

上面提到了直接改变Student.prototype所遇到的挫折
我们可以改变思路,不直接改变Student构造函数的原型对象,而是改变其原型对象的隐式原型对象__proto__,即把 Student.prototype=new Person()这行代码写成Student.prototype.__proto__=Person.prototype,相当于在Student原型查找链中插入了Person这一环。
巧妙的是,因为我们在

function Person(name,age,friends){
  this.myName=name;
  this.age=age;
  this.friends=friends;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

把prototype的属性赋值写在了Person构造函数中,所以除非我们执行了一次Person函数,否则不存在sayHi方法,但我们恰巧在上方借用了构造函数Person.call(this,name,age,friends)
所以现在,我们可以把我们的代码优化成这样

function Person(name,age,friends){
  this.myName=name;
  this.age=age;
  this.friends=friends;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

function Student(sno,name,age,friends){
  this.sno=sno
  // 借用构造函数
  Person.call(this,name,age,friends)
  Student.prototype.__proto__=Person.prototype
  Student.prototype.study=function(){
    console.log('studying~');
   }
}
const Lihua=new Student(121,'Lihua',21,['aaa','bbb'])
console.log(Lihua);  结果 Student {
                          sno: 121,
                          myName: 'Lihua',
                          age: 21,
                          friends: [ 'aaa', 'bbb' ]
                        }

/*
1.不必要的Person实例
2.无法传递person上的参数
3.Person函数多次调用
等问题全部得到了解决,并且Lihua在打印中类型明确为Student
*/

3.原型继承解决方案

实现对象之间的继承

var obj={
  name:'cst',
  age:22
}

function createObject(o){
  var newObj={}
  //把newObj的原型设置为o
  Object.setPrototypeOf(newObj,o)
  return newObj
}

var info=createObject(obj)
console.log(info.__proto__); //obj对象

可以用Object.create(obj)替代下面代码

function createObject(o){
  var newObj={}
  //把newObj的原型设置为o
  Object.setPrototypeOf(newObj,o)
  return newObj
}

var info=createObject(obj)

4.寄生式继承

结合原型式继承和工厂函数实现,解决了原型式继承需要逐个添加对象属性的问题

var myObj={
  name:'cst',
  age:22,
  talking:function(){
    console.log('hi~');
  }
}

function createStudent(father,name){
  var stu=Object.create(father)
  stu.name=name
  stu.study=function(){
    console.log('study~');
  }
  return stu
}
var lilei=createStudent(myObj,'lilei')
lilei.talking() //hi~
lilei.study() //study~
console.log(lilei.name); //lilei

弊端:重复创建study函数,createStudent创建的对象类型不明

5.寄生组合式继承

function Person(name,age,friends){
  this.myName=name;
  this.age=age;
  this.friends=friends;
  Person.prototype.sayHi=function(){
    console.log('Hi~');
  }
}

function Student(sno,name,age,friends){
  this.sno=sno
  // 借用构造函数
  Person.call(this,name,age,friends)  
}
Student.prototype=Object.create(Person.prototype)
//解决打印Student实例却标识为Person类型的问题
Object.defineProperty(Student.prototype,'constructor',{
  enumerable:false,
  configurable:true,
  writable:true,
  value:Student
})

Student.prototype.study=function(){
  console.log('studying~');
}
 var cst=new Student(111,'cst',22,['aa','bb'])
 console.log(cst); //Student { sno: 111, myName: 'cst', age: 22, friends: [ 'aa', 'bb' ] }

将实现方法继承的实现方法封装

function inheritPrototype(subType,SuperType){
  subType.prototype=Object.create(SuperType.prototype)
  Object.defineProperty(subType.prototype,'constructor',{
    enumerable:false,
    configurable:true,
    writable:true,
    value:Student
  })
}