继承,js面向对象的三大特点之一
1、何谓继承
- 定义: 继承就是子类可以使用父类的所有功能,并且能够对这些功能进行扩展。
- 继承的优点: 继承可以
使得子类具有父类的各种属性和方法
,而不需要再次编写相同的代码,同时,还可以重新定义某些属性、方法,即覆盖父类原有的属性和方法,使其获得与父类不同的功能
2、实现继承的方式
- 构造函数实现继承 ----------
call/apply
- 原型链实现继承 ---------
prototype
- 组合继承 --------
prototype + call/apply
- 寄生继承-----
通过 Object.create() 实现继承,并对其进行扩展
- es6的extends实现继承------
extends方法
2.1、借助构造函数实现继承(call、apply)---- 数据私有化
借助 call调用Parent函数
优点: 实例化后,被继承的构造函数内的属性与方法,相互独立,互不影响 (私有对私有)
缺点: 被继承的构造函数的原型对象(Parent.prototype)上的方法和属性,不会被子类不会被继承。
总结:
child私对parent:只能继承私有的,无法继承公有的;
function Parent() {
this.age = 100
this.arr = []
}
Parent.prototype.say = function() {
alert(this.age)
};
function Child() {
this.love = 'learn English'
Parent.call(this)
}
let obj0 = new Child()
let obj1 = new Child()
obj0.age = 51;
obj1.age = 10000;
obj0.arr.push(100);
obj1.arr.push(100000);
console.log(obj0.age,obj1.age) // 51 10000 相互独立
console.log(obj0.arr,obj1.arr) //[100] [100000] 相互独立
obj0.say() //报错:obj0.say is not a function
上面代码相当于函数Child继承了函数Parent的私有属性,通过改变父类的this实现继承
2.2、借助原型链实现继承(prototype)---- 数据公有化
优点: 被继承的构造函数的原型对象(Parent.prototype)上的方法和属性,可以被子类继承。
缺点: 由于是通过prototype实现继承,所以被继承的构造函数上的引用类型的属性是共有的,改变一个,另一个也跟着改变,eg:obj2.info,obj3.info
总结:
child公对parent公: 公;
child公对parent私:基本类型的数据是私,引用类型的数据是公;
function Parent1() {
this.age = 30;
this.arr = [];
}
Parent1.prototype.getName = function() {
return '你好'
}
Parent1.prototype.info = {};
function Child1() {
this.name = 'Tony'
}
Child1.prototype = new Parent1() //把父类的实例挂载到子类的原型链上
Child1.prototype.constructor = Child1 //把该构造函数的构造方法指向自身
let obj2 = new Child1()
let obj3 = new Child1()
obj2.age = 70;
obj3.age = 100;
obj3.arr.push(100);
obj2.arr.push(500);
console.log(obj2.age,obj3.age) // 70 100 基本类型相互独立
console.log(obj2.arr,obj3.arr) // [100, 500] [100, 500] 引用类型的属性则是共有的,一个改变,别一个也改变
obj2.info['name'] = 'test';
obj3.info['sex'] = 'man';
console.log(obj2.info,obj3.info) // {name: "test", sex: "man"} 二者相等
console.log(obj2.getName()) // 你好 可以访问到被构造函数的原型对象上属性和方法
原型继承通过将父类的实例赋值给子类的原型,这种方法子类可以继承父类的私有属性也可以继承父类的私有方法
2.3、组合继承( prototype + call/apply ) — 私对私,公对公(推荐!!!)
实例化对象后,
公有数据通过prototype继承(改变一个,另一个也跟着改变,不过只适用于引用类型的数据),
私有数据通过call/apply继承,在其被继承的构造函数内创建后被继承
function Parent5() {
this.name = 'name';
this.list = []
}
Parent5.prototype.arr = [];
function Child5() {
Parent5.call(this); //私有对私有
this.type = 'child5';
}
Child5.prototype = new Parent5(); // 使用原型链实现继承,公有对公有
Child5.prototype.constructor = Child5; //修改其构造函数为其自身
var s7 = new Child5();
var s8 = new Child5();
s7.name = 's7-name';
s8.name = 's8-name';
console.log(s7.name,s8.name); // s7-name s8-name
s7.list.push(100)
s8.list.push(1000);
console.log(s7.list,s8.list); // [100] [1000]
s7.arr.push(100);
s8.arr.push(1000);
console.log(s7.arr,s8.arr); // [100, 1000] [100, 1000]
寄生组合继承
就是
1、使用call继承改变this,实现私有继承私有,
2、使用原型链实现公有继承公有
这种方式看起来就没什么问题,方式一和方式二的问题都解决了,但是从上面代码我们也可以看到Parent5执行了两次,造成了多构造一次的性能开销
2.4、寄生组合式继承
是对组合继承的优化,通过 Child.prototype 间接访问到 Parent.prototype,减少一次构造函数执行。
function Parent(name, actions) {
this.name = name;
this.actions = actions;
}
Parent.prototype.eat = function () {
console.log(`${this.name} - eat`);
};
function Child(id) {
Parent.apply(this, Array.from(arguments).slice(1));
this.id = id;
}
//封装写法
function inheritPrototype(child, parent){
// 通过Object.create() 实现:
// 将现有的对象挂载到新创建对象的__proto__上,从而实现新创建的对象与原对象的隔离,
let prototype = Object.create(parent.prototype)//object(parent.prototype)
prototype.constructor = child
child.prototype = prototype
}
inheritPrototype(Child, Parent)
const child1 = new Child(1, "c1", ["hahahahahhah"]);
const child2 = new Child(2, "c2", ["xixixixixixx"]);
为什么一定要通过桥梁的方式让 Child.prototype 访问到 Parent.prototype? 直接 Child.prototype = Parent.prototype 不行吗? 答:不行!!在给 Child.prototype 添加新的属性或者方法后,Parent.prototype 也会随之改变。
这里通过桥梁的方式就是为了让Child.prototype 与 Parent.prototype 彻底隔离开来。
如果直接Child.prototype = Parent.prototype,就会如下产生影响
function Parent(name, actions) {
this.name = name;
this.actions = actions;
}
Parent.prototype.eat = function () {
console.log(`${this.name} - eat`);
};
function Child(id) {
Parent.apply(this, Array.from(arguments).slice(1));
this.id = id;
}
Child.prototype = Parent.prototype;
Child.prototype.constructor = Child;
console.log(Parent.prototype); // Child { eat: [Function], childEat: [Function] }
Child.prototype.childEat = function () {
console.log(`childEat - ${this.name}`);
};
const child1 = new Child(1, "c1", ["hahahahahhah"]);
console.log(Parent.prototype); // Child { eat: [Function], childEat: [Function] }
2.5、es6 extends 继承
继承:继承可以使得子类具有父类的属性和方法并重新定义、追加属性和方法等。
class Parent {
name_= null
constructor() {
this.name = 'aaa';
}
getName() {
return this.name
}
static get(){ // class的静态方法
return 'good'
}
//支持获取和设置访问器
//使用get和set关键字对某个属性设置存值函数和取值函数,拦截该函数的存取行为
set name(value){
this.name_ = value
}
get name(){
return this.name_
}
}
Parent.get() //good
//通过类继承
class Child extends Parent {
constructor() {
super();
}
}
const p1 = new Child();
p1.getName();
这里通过关键字extends实现子类继承父类的私有和公有
这里需要注意如果子类里面写了constructor,就必须写super否则会报错
例子如下:
class parent{
constructor(){
this.a=1
}
name(){
return 2
}
}
class child extends parent{
constructor(){
super(); // 这里不写super会报错,就会报错,报错信息如下 Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
}
}
let A = new child()
console.log(A.a) // 1
console.log(A.name()) // 2
参考链接:https://www.cnblogs.com/mengxiangji/p/10399066.html
参考链接:https://mp.weixin.qq.com/s/mnQde8T6frvYautX4Ajdxg