JavaScript 继承
文章目录
一,构造函数继承(对象冒充继承)
原理:调用父类构造函数,并改变其中的this指向(bind,call,apply)
局限:只继承了父类构造函数的属性,没有继承父类原型的属性
//创建一个猫的构造函数
function Cat(name, color) {
this.name = name;
this.color = color;
this.eating = function () {
console.log('吃东西');
}
}
Cat.prototype.shopping = function () {
console.log('购物');
}
function Pig(){
this.sleep=function(){
console.log('睡觉');
}
}
//狗类
function Dog(name, color, food) {
this.food = food;
//狗冒充猫,访问猫的属性或方法 (改变this指向)
Cat.call(this, name, color);
Pig.call(this);
}
var dog1=new Dog('大黄','黄','骨头');
console.log(dog1.name);//大黄
console.log(dog1.color);//黄
dog1.sleep();//睡觉
dog1.eating();//吃东西
dog1.shopping();//报错,无法继承父类原型上的方法
1.优点
- 可以继承多个构造函数的属性或方法(call多个)
- 在子实例中
2.缺点
- 只能继承父类构造函数的属性
- 每个新实例都有父类构造函数的副本,特别臃肿(根本原因是不能继承prototype上的属性或方法)
- 无法实现构造函数的复用.(每次用每次都要重新调用)
二.原型链继承
让新实例的原型等于父类的实例,实现原型链继承
//创建一个猫的构造函数
function Cat(name, color) {
this.name = name;
this.color = color;
this.eating = function () {
console.log('吃东西');
}
}
Cat.prototype.shopping = function () {
console.log('购物');
}
//狗类
function Dog(name, color, food) {
this.food = food;
}
//原型链继承
Dog.prototype=new Cat();//把狗的原型指向猫的实例对象
var dog1=new Dog('大黄','黄','骨头');
console.log(dog1.name);//undefined
console.log(dog1.color);//undefined
dog1.shopping();//睡觉
dog1.eating();//吃东西
1.优点
- 实例可以继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性(新实例不会继承父类实例的属性!)
2.缺点
- 新实例无法向父类构造哈数传参
- 继承单一,不能实现多继承
- 所有新实例都会共享父类实例的属性.(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型实例属性也会被修改)
三.组合继承(常用)
原理:组合原型链继承和借用构造函数继承,结合了两种模式的优点.传参和复用(常用的一种继承方式)
//创建一个猫的构造函数
function Cat(name, color) {
this.name = name;
this.color = color;
this.eating = function () {
console.log('吃东西');
}
}
Cat.prototype.shopping = function () {
console.log('购物');
}
//狗类
function Dog(name, color, food) {
this.food = food;
//构造函数继承
Cat.call(this, name, color);
}
//原型链继承
Dog.prototype = new Cat();
var dog1 = new Dog('大黄', '黄', '骨头');
console.log(dog1.name);//大黄
console.log(dog1.color);//黄
dog1.shopping();//睡觉
dog1.eating();//吃东西
1.优点
- 可以继承父类原型上的属性,可以传参,可以复用
- 每个新实例引入的构造函数属性是私有的.
2.缺点
- 调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数
四.拷贝继承
通过改变新实例对象的指针指向被继承的实例对象的地址,达到继承的效果
通过遍历复制前一个对象的属性和方法达到拷贝的效果
方法一:
//创建一个猫的构造函数
var Cat = {
name: "大黄",
color: "黄色",
eating: function () {
console.log('吃东西');
}
}
//将猫的属性给狗 实际上就是浅拷贝赋值
var Dog = Cat;
console.log(Dog.name);//大黄
console.log(Dog.color);//黄色
Dog.eating();//吃东西
方法二:
// 创建父对象
var superObj = {
name: 'Li',
age: 25,
friends: ['小明', '小李', '小赵'],
showName: function(){
alert(this.name);
}
}
// 创建需要继承的子对象
var subObj = {};
// 开始拷贝属性(使用for...in...循环)
for( var i in superObj ){
subObj[i] = superObj[i];
}
console.log(subObj)
console.log(superObj)
1.优点:
- 支持多继承
2.缺点
- 效率较低,内存占用高(因为要拷贝父类的属性)
- 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)
- 如果继承过来的成员是引用类型的话,那么这个引用类型的成员在父对象和子对象之间是共享的,也就是说修改了之后,父子对象都会受到影响。(实际上浅拷贝原理)
五.原型式继承
用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
就是给原型式继承外面套了个壳子。
方法一:
// 原型式继承
function createAnother(obj) {
function F() {
}
F.prototype = obj;//继承传入的参数
//返回函数对象
return new F();
}
var person = {
name: 'jia',
age: 18,
job: '打杂的',
show: function () {
console.log('显示');
}
};
var anotherPerson = createAnother(person);
console.log(anotherPerson.name);//jia
console.log(anotherPerson.job);//打杂的
anotherPerson.show();//显示
方法二:
Object.create()
接收的第二个参数,是为新对象定义额外的属性,指定的任何属性都会覆盖原型上的同名属性。
//创建一个猫的构造函数
var Cat = {
name: '大黄',
color: '黄色',
eating: function () {
console.log('吃东西');
},
getName: function () {
return this.name;
}
}
var dog = Object.create(Cat, {
name: {
value: '大黑'
},
color: {
value: '黑色'
}
});
console.log(dog.name);//大黑
console.log(dog.color);//黑色
dog.eating();//吃东西
1.优点
- 兼容性好,最简单的对象继承
2.缺点
- 所有实例都会继承原型上的属性
- 无法实现复用(新实例属性都是后面添加的)
六.实例继承
为父类实例添加新特性,作为子类实例返回
//创建一个猫的构造函数
function Cat(name, color) {
this.name = name;
this.color = color;
this.eating = function () {
console.log('吃东西');
}
this.getColor=function(){
console.log(this.color);
}
}
function Dog() {
var instance = new Cat();
instance.name = "大黄";
return instance;
}
var dog1 = new Dog();
console.log(dog1.name);
dog1.color = "黄色";
dog1.getColor();
dog1.eating();//吃东西
1.优点
- 不限调用方式,不管是 new 子类()还是子类() 返回的对象具有相同的效果
2.缺点
- 实例是父类的实例,不是子类的实例
- 不支持多继承
七.寄生继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象
使用场景:专门为对象来做某种固定方式的增强。
// 原型式继承
function objectM(obj) {
function F() {
}
F.prototype = obj;//继承传入的参数
//返回函数对象
return new F();
}
//寄生式继承,给原型式继承套个壳子
function createAnother(obj) {
var clone = objectM(obj);//通过调用函数创建一个新
clone.sayHello = function () {//给新对象添加方法
console.log("hello");
}
return clone;
}
var person = {
name: '大黄',
age: 18,
job: '吃屎的',
show: function () {
console.log('显示');
}
};
var anotherPerson = createAnother(person);
console.log(anotherPerson.name);//jia
console.log(anotherPerson.job);//吃屎的
anotherPerson.sayHello();//hello
anotherPerson.show();//显示
1.优点
- 没有创建自定义类型,因为只是套了个壳子返回对象,这个函数顺利成章的就成了创建的新对象
2.缺点
- 不能做到函数复用,即不能继承原型的方法
八.寄生组合继承(常用)
集寄生式继承和组合继承的优点与一身,是实现基于类型继承的最有效方式
比较完美的解决方式,不过就是实现复杂
// 定义一个动物类
function Animal(name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function () {
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function (food) {
console.log(this.name + '正在吃:' + food);
};
function Cat(name) {
Animal.call(this);
this.name = name || 'Tom';
}
(function () {
// 创建一个没有实例方法的类
var Super = function () { };
Super.prototype = Animal.prototype;
//将实例作为子类的原型
Cat.prototype = new Super();
Cat.prototype.constructor = Cat; // 需要修复下构造函数
})();
var cat = new Cat("大黑");
console.log(cat.name);//大黑
cat.sleep();//大黑正在睡觉!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true
1.优点
- 可以继承实例属性/方法,也可以继承原型属性/方法
- 即是之类的实例,也是父类的实例
- 不存在引用属性共享问题
- 可传参
- 函数可复用
2.缺点
- 实现起来比复杂
九.ES6 Class继承(不用想了,我就要用这个,妥妥的)
ES6继承的原理跟寄生组合式继承是一样的。但是Class是语法糖,写起来甜甜的
ES5继承与ES6继承的区别:
-
ES5的继承实质上是先创建子类的实例对象,再将父类的方法添加到this上。
-
ES6的继承是先创建父类的实例对象this,再用子类的构造函数修改this。
因为子类没有自己的this对象,所以必须先调用父类的super()方法。
class Person{
constructor(name, age){
this.name=name;
this.age=age;
}
show(){
alert(this.name);
alert(this.age);
}
}
class Worker extends Person{
constructor(name, age, job){
super(name, age);
this.job=job;
}
showJob(){
alert(this.job);
}
}
let me=new Worker('jia', 18, '前端攻城狮');
me.show();//jia
me.showJob();//前端攻城狮
1.优点
- 原理还是参照寄生组合继承,基本原理是一样,语法糖,写起来方便,比较完美