继承
在js
中继承是原型的继承,而不是改变构造函数的原型
改变构造函数的原型不是继承
function User(){}
// User有一个show方法,把方法定义到原型当中
User.prototype.name = function(){
console.log('show')
}
const hd = new User()
// 在构造的对象中是没有show方法的,但是在其原型是有这个方法的
hd.show() //show
function Admin(){}
// 改变构造函数的原型,但是不推荐,推荐使用继承
Admin.prototype = User.prototype
const admin = new Admin()
admin.name() // show
改变构造函数的原型的方法(让几个构造函数公用一个构造函数的原型)一般不去使用,因为
Admin
的原型继承了User
的原型,后面我们需要给Admin
增加其新的特有的方法,我们会同时将这个方法添加到User
的原型中去,这样显然是不合理的,如果后续的其他对象添加了新的同名称的方法,也会将前面的方法进行覆盖,这也是不行的
继承是指某人继承了财产后,自己的财产还是保留的,而不是自己的财产会消失,改变构造函数的原型的方法将本身的原型全部舍弃,就没有办法给其本身加上特有的方法了
继承是原型的继承
原型是一个对象,我们需要让原型进行继承,既要实现继承,达到对象复用的目的;又要实现重载,让不同的对象有不同的方法,共性、个性一个都不能少
function User(){}
// User有一个show方法,把方法定义到原型当中
User.prototype.name = function(){
console.log('show')
}
const hd = new User()
// 在构造的对象中是没有show方法的,但是在其原型是有这个方法的
hd.show() //show
function Admin(){}
// 通过原型进行继承
Admin.prototype.__proto__ = User.prototype
Admin.prototype.role = function(){
console.log('role')
}
const admin = new Admin()
admin.name() // show
admin.role() // role
使用Admin.prototype.__proto__ = User.prototype
进行原型的继承,进行原型继承和进行自定义对象方法的顺序是任意的,哪一个在前都可以
原型的继承还有一种方式:Admin.prototype = Object.create(User.prototype)
,这样的方式进行继承,进行原型继承和进行自定义对象方法的顺序是有要求的,必须先继承在进行自定义对象方法,最后在实例化新的对象,不然就会报错
function Admin(){}
// 通过原型进行继承
Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = function(){
console.log('role')
}
const admin = new Admin()
因此推荐使用第一种方式进行原型的继承
原型是继承的,Admin.prototype
对象的原型被设置成成了 User.prototype
,目的是使用 User.prototype
原型中的属性,这就是继承。 至于对 Admin.prototype
添加的属性方法,只是对Admin
类实例化出的所有对象的复用。其实就是一句话,改变原型(prototype
)就是为了继承
继承对constructor
属性的影响
对于定义的hd
的构造函数:function Hd() {}
,这个构造函数中会有一个原型prototype
,这个原型中有constructor
属性,该属性记录着这个构造函数:f Hd()
当我们使用这个构造函数创建一个新对象的时候:let obj = new Hd;
,就会自动绑定到构造函数的原型:Hd.prototype
,即:obj.__proto__ == Hd.prototype
或者obj.__proto__.constructor == Hd
因此给我们一个对象,我们就可以通过这个对象实例化出来一个新的对象
function Hd() {}
let obj = new Hd();
let obj2 = obj.__proto__.constructor;
如果将原型发生改变时,创建一个新的对象来作为构造函数的原型对象,使用Object.create()
实现继承会使constructor
属性丢失
function User() {}
function Admin() {}
// 通过Object.create()来改变Admin的原型
Admin.prototype = Object.create(User.prototype)
Admin.prototype.role = function() {
console.log("admin.role");
}
console.dir(Admin)
会发现使用Object.create()
实现继承会使constructor
属性丢失
注意:虽然继承后constructor
属性是丢失了,但是我们进行打印还是有值的,因为原型对象是继承User.prototype
的,其User.prototype
中是有constructor
属性的
console.log(Admin.prototype.constructor);// f User()
因此,如果通过一个对象来直接改变了我们的原型,我们需要将constructor
属性给添加上去:
Admin.prototype.constructor = Admin;
但是我们知道原型Admin.prototype
也是一个对象,上述代码我们给对象压入constructor
这个属性特征,压进去的属性特征是可以被遍历的,其enumerable
的值为true
遍历对象的属性方法:
let a = new Admin();
for (const key in a) {
console.log(key)
}
在对象中,自定义的属性往往是允许被遍历的,但是constructor
属性一般是不允许被遍历的,因此我们不采用Admin.prototype.constructor = Admin;
方式进行定义,我们在定义的时候需要对其设置不可遍历:
Object.defineProperty(Admin.prototype, 'constructor', {
value: Admin,
enumerable: false
})
方法的重写
在有的时候,如果父类的方法对于子类来说不太适用,子类可以进行方法的重写,我们一般在子类本身的原型上重写这个方法
function User() {}
User.prototype.show = function() {
console.log("user.show")
}
User.prototype.site = function() {
return "jlc";
}
function Admin() {}
// 通过Object.create()来改变Admin的原型
Admin.prototype = Object.create(User.prototype)
Object.defineProperty(Admin.prototype, 'constructor', {
value: Admin,
enumerable: false
})
Admin.prototype.show = function() {
console.log(User.prototype.site()+"admin.show");
}
let hd = new Admin();
hd.show(); // jlcadmin.show
面向对象的多态
多态是指对象面对不同的内容时,其体现出不同的状态(在不同的形态响应出不同的结果)多态实现可以使我们的代码更加优雅
function User() {}
User.prototype.show = function() {
console.log(this.description());
}
function Admin() {};
Admin.prototype = Object.create(User.prototype);
Admin.prototype.description = function () {
return '我是管理员'
};
function Member() {};
Member.prototype = Object.create(User.prototype);
Member.prototype.description = function () {
return '我是会员'
};
function Enterprise() {};
Enterprise.prototype = Object.create(User.prototype);
Enterprise.prototype.description = function () {
return '我是企业账户'
};
for (const obj of [new Admin(), new Member(), new Enterprise()]) {
obj.show();
}
// 我是管理员
// 我是会员
// 我是企业账户
多态特性实现的基础还是依赖于子级原型对父级原型方法的重写。其实现方式是,父级原型的指针,在实际调用中,指向实际调用者
使用父类构造函数
对于比较公用的方法,我们没有必要在自己的构造函数中进行编写,我们可以在父类的构造函数中去编写方法,对于实例化的对象直接去调用父类的构造函数即可
调用上一级,实现对象初始属性的构建:
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.show = function() {
console.log(this.name, this.age);
}
function Admin(...args) {
// 使用call或者apply来进行改变User函数的this,将新增对象的this传递过去
User.apply(this, args);
}
// 使用原型的继承方式,使Admin继承User
Admin.prototype = Object.create(User.prototype);
Admin.prototype.constructor = Admin;
let hd = new Admin('jlc', 24);
hd.show() // jlc 24
我们希望传参之后的过程可以在
User
中进行完成,因为继承User
的构造函数可能不止Admin
,后面可能有别的构造函数如果在
Admin
构造函数中编写相关的传参过程后,那么后面别的继承于User
的构造函数就不能进行使用,为了减少重复工作,我们可以直接在父类的构造函数中编写进行传参后的方法
通过原型工厂封装继承
我们知道原型继承的步骤和重复工作比较多,当进行一个继承时,都需要进行以下操作的重复工作:
Admin.prototype = Object.create(User.prototype);
Admin.prototype.constructor = Admin;
Object.defineProperty(Admin.prototype, 'constructor', {
value: Admin,
enumerable: false
})
对于这样的重复工作,我们可以进行封装:
// 继承封装方法1
// 第一个参数是我们的基类,第二个参数是我们要继承的父类
function extend(sub, sup) {
sub.prototype = Object.create(sup.prototype);
sub.prototype.constructor = sub;
Object.defineProperty(sub.prototype, 'constructor', {
value: sub,
enumerable: false
})
}
// 继承封装方法2
function extend(sub, sup) { Object.setPrototypeOf(sub.prototype,sup.prototype)
}
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.show = function() {
console.log(this.name, this.age);
}
function Admin(...args) {
// 使用call或者apply来进行改变User函数的this,将新增对象的this传递过去
User.apply(this, args);
}
// 使用封装好的原型工厂进行继承
extend(Admin, User);
let hd = new Admin('jlc', 24);
hd.show() // jlc 24
通过对象工厂实现继承
之前的对象都是通过new
一个构造函数进行创建的,但是创建一个对象还可以通过其他的方式进行创建:
let hd1 = {};
let hd2 = Object.create({});
对于上述两种方式进行对象的创建,创建的的对象直接指向Object
的原型:hd1.__proto__ == Object.prototype
,我们可以通过对象工厂实现对于其他原型的继承:
// 父级构造函数
function User(name, age) {
this.name = name;
this.age = age;
}
User.prototype.show = function() {
console.log(this.name, this.age);
}
// 对象工厂,仅仅只是一个函数,不是构造函数
function admin() {
// 声明出一个对象,使其继承User的原型
const instance = Object.create(User.prototype);
// 传递参数调用User的方法
User.call(instance, name, age);
// 添加自定义的方法
instance.role = function() {
console.log('role');
}
return instance;
}
let lisi = admin('lisi', 24);
lisi.show(); // lisi 24
lisi.role(); // role
对于继承写法:
jsconst instance = Object.create(User.prototype);
我们还可以使用以下的写法进行编写:
jsconst instance = {}; instance.__proto__ = User.prototype;
多继承
在Js
中是没有多继承的,是没有办法使一个原型在继承另一个原型的基础上,又继承其他的原型,由于Js
中没有办法实现多继承,在一些必要的情况下会进行逐级的继承,来调用该父级中的方法:
// 原型工厂
function extend(sub, sup) { Object.setPrototypeOf(sub.prototype,sup.prototype)
}
function Request() {}
Request.prototype.ajax = function() {
console.log('后台请求');
}
function User(name, age) {
this.name = name;
this.age = age;
}
extend(User, Request);
User.prototype.show = function() {
console.log(this.name, this.age);
}
function Admin(...args) {
User.apply(this, args);
}
extend(Admin, User);
let admin = new Admin('jlc', 24);
admin.ajax(); // 后台请求
这样的多级继承是比较繁琐的,也不是最佳的方法
mixin
实现多继承
我们一般是使用mixin
来实现多继承,mixin
可以理解为一个实现多继承的混合功能,将其他对象的属性直接压到最开始继承的原型里面:
// 原型工厂
function extend(sub, sup) { Object.setPrototypeOf(sub.prototype,sup.prototype)
}
const Request = {
ajax() {
console.log('后台请求');
}
}
function User(name, age) {
this.name = name;
this.age = age;
}
extend(User, Request);
User.prototype.show = function() {
console.log(this.name, this.age);
}
function Admin(...args) {
User.apply(this, args);
}
extend(Admin, User);
// 只压入其他对象的一个方法
Admin.prototype.ajax = Request.ajax;
// 一次性压入其他对象的所有属性,推荐使用
Admin.prototype = Object.assign(Admin.prototype, Request)
let admin = new Admin('jlc', 24);
admin.ajax(); // 后台请求
mixin
的内部继承和super
关键字
对于功能类的对象,如之前的Request
,我们也可以实现继承,去继承别的功能类对象,使用其他功能类对象里面的方法:
const Request, = {
ajax() {
return '后台请求';
}
}
const Credit = {
__proto__: Request,
total() {
console.log(this.__proto__.ajax() + '积分统计');
}
}
function User(name, age) {
this.name = name;
this.age = age;
}
extend(User, Request);
User.prototype.show = function() {
console.log(this.name, this.age);
}
function Admin(...args) {
User.apply(this, args);
}
extend(Admin, User);
Admin.prototype = Object.assign(Admin.prototype, Credit)
let admin = new Admin('jlc', 24);
admin.total(); // 后台请求积分统计
在调用其他功能类中的方法时,其调用语句比较长:
this.__proto__.ajax()
,因此,系统提供了一个简写的形式,需要使用super
关键字:super.ajax()
,super
是指当前这个类的原型,不是调用者(admin
)构造函数的原型
总之,功能类不是为了继承而使用的,而是合并到其他原型中,类似于多继承来解决问题,同时功能类之间也是可以相互继承的