引言
作为一个前端应届生,最近在看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
})
}