深入理解JS的继承方法

参考文献:
https://blog.csdn.net/yaojxing/article/details/73612483
https://juejin.im/post/58f94c9bb123db411953691b#heading-0
https://www.cnblogs.com/humin/p/4556820.html

原型链继承:
 - 同一子类的不同实例操作 子类.prototype原型对象中继承自父类的自定义引用属性是共享的(子类原型对象中另存储了父类的自定义属性)。 此时父类本身的自定义属性并不改变,关键点在于理解new一个对象时发生的事情。
 - 不同子类的实例操作各自继承过来的父类自定义引用属性是不共享的,因为这些属性在各自的子类原型对象中。
 - 所有子类的实例对父类原型对象的引用属性操作都是共享的,因为通过子类原型对象的proto指向父类原型对象了。
构造继承:
 - 所有实例操作继承过来的父类自定义引用属性是不共享的,因为每次new一个子类的实例都重新继承了父类的自定义属性。
 - 不能访问父类原型对象的属性。
原型继承:
 - 子类实例和当前父类实例(子类原型对象的__proto__指向父类实例)共享父类实例继承的父类自定义属性。
 - 所有子类的实例对父类原型对象引用属性的操作都是共享的。

1、原型链继承

实现方法:子类原型对象做为父类的实例。
原型链继承图

//**********原型链继承**********
function Animal(name) {
  this.name = name || 'Animal'; 
  this.colors = ['white','yellow']
  this.sleep = function () {
    return this.name + '正在sleep!'
  }
}
Animal.prototype.actions = ['吃','喝','玩']
Animal.prototype.eat = function (food) {
  return this.name + '正在吃:' + food
};
console.log(Animal.prototype)
console.log(typeof(Animal.prototype))
console.log(Animal.prototype.__proto__===Object.prototype)
// var animal = new Animal;
// animal.sex = 'male'
// console.log(animal)
function Cat(){ 
  this.name = "小花猫"
  this.play = function () {
    return this.name + '正在玩耍!'
  }
}
console.log(Cat.prototype) //__proto__指向Object的原型对象
Cat.prototype = new Animal(); 
// Cat.prototype.name = 'cat';
console.log(Cat.prototype) //为Animal对象,其__proto__属性指向其构造函数的prototype
console.log(Cat.prototype.__proto__ === Animal.prototype)

var cat = new Cat();  
console.log(cat.name); // cat属性->Cat属性->Cat原型对象->Cat原型对象的构造函数属性->构造函数的原型对象
console.log(cat.eat('fish'));
console.log(cat.sex)
console.log(cat instanceof Animal); //true 
console.log(cat instanceof Cat); //true
// console.log(cat.__proto__ === Cat.prototype)
// console.log(Cat.prototype.__proto__ === Animal.prototype)
// console.log(cat.__proto__.__proto__=== Animal.prototype)

cat.colors.push('black')
cat.actions.push('乐')
console.log('cat.colors:', cat.colors)
console.log('cat.actions:', cat.actions)
var cat1 = new Cat();
console.log('cat1.colors:', cat1.colors)
console.log('cat1.actions:', cat1.actions)
// function F(){}
// F.prototype = new Animal
// var f = new F();
// console.log('f.colors:', f.colors)
// console.log('f.actions:', f.actions)
  • 特点:

1、实例可继承的有:实例的构造函数(子类)的属性和构造函数原型对象的属性,父类构造函数属性,父类原型对象的属性。(不会继承父类实例的属性)

  • 缺点:

1、子类创建的实例无法向父类构造函数传参。

2、如果要新增添加子类原型对象的属性和方法,必须放在new Animal()这样的语句之后执行,否则新增的属性和方法被清除。

3、继承单一,无法实现多继承。

4、父类原型对象的所有属性被所有实例共享,一个实例修改了原型对象的引用属性,另一个实例的原型的引用属性也会被修改。

2、构造继承

实现方法: 用.call()和.apply()将父类构造函数引入子类函数,使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型) 。

//**********构造函数继承**********
function Animal(name) {
  this.name = name || 'Animal';
  this.colors=['white','yellow']
  this.sleep = function () {
    return this.name + '正在sleep!'
  }
}
Animal.prototype.eat = function (food) {
  return this.name + '正在吃:' + food
};
function Cat(name){
  Animal.call(this,name);
  this.name = name || 'Tom';
}
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat.eat)
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

cat.colors.push("black");
console.log('cat.colors:', cat.colors)
var cat1 = new Cat();
console.log('cat1.colors:', cat1.colors)
  • 特点:

1、只继承了父类构造函数的属性,没有继承父类实例对象和原型对象的属性。

2、在子实例中可向父实例传参。

3、可以继承多个构造函数属性(call多个)。

4、保证了原型链中引用类型值的独立,不再被所有实例共享 。

  • 缺点:

1、实例并不是父类的实例,只是子类的实例,即只继承父类构造函数的属性,不能继承父类原型对象的属性。

2、无法实现构造函数的复用(每次用每次都要重新调用),父类原型对象上的方法也无法复用。

3、每个新实例都有父类构造函数的副本,臃肿。

3、组合继承:原型链+构造函数(常用)

实现方法: 调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将子类原型对象作为父类实例,实现函数复用。

//**********组合继承**********
function Animal(name) {
  this.name = name || 'Animal';
  this.colors = ['white','yellow']
  this.sleep = function () {
    return this.name + '正在sleep!'
  }
}
Animal.prototype.actions=['吃','喝','玩'];
Animal.prototype.eat = function (food) {
  return this.name + '正在吃:' + food
};
function Cat(name){
  Animal.call(this,name);
  this.name = name || 'Tom';
}
console.log(Cat.prototype)  //__proto__指向Object的原型对象
Cat.prototype = new Animal() 
Cat.prototype.play='play'
console.log(Cat.prototype)  //为Animal对象,其__proto__属性指向Animal.prototype
Cat.prototype.constructor = Cat;  
console.log(Cat.prototype)

var cat = new Cat();
var cat1 = cat.constructor; //cat通过Cat.prototype.constructor找构造函数,修复。
console.log('constructor:', cat1)
console.log(cat.name);
console.log(cat.play)
console.log(cat.sleep());
console.log(cat.eat('fish'))
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

cat.colors.push('black')
cat.actions.push('乐')
console.log('cat.colors:', cat.colors)
console.log('cat.actions:',cat.actions)
var cat1 = new Cat();
console.log('cat1.colors:', cat1.colors)
console.log('cat1.actions:', cat1.actions)
  • 特点:

1、可以继承父类原型上的属性,在父类原型上定义方法实现了函数复用。

2、既是子类的实例,也是父类的实例。

3、每个新实例引入父类构造函数属性是私有的,每个实例都有它自己的属性 。不存在引用属性共享问题。

4、可以传参,多继承。

  • 缺点:

1、调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。

4、原型式继承(对象的浅拷贝)

实现方法:通过父类的实例,拷贝父类的所有属性给子类的原型对象,作为子类原型对象的属性。

proto在原型结构上没有固定指向父类的原型对象,只是proto属性的值被赋值为父类的原型对象。

//**********原型继承(对象的浅拷贝)var cat = object.create(animal)**********
function Animal(name) {
  this.name = name || 'Animal';
  this.colors = ['white','yellow']
  this.sleep = function () {
    return this.name + '正在sleep!'
  }
}
Animal.prototype.actions=['吃','喝','玩'];
Animal.prototype.eat = function (food) {
  return this.name + '正在吃:' + food
};
function f(obj){
    function Cat(){}
    Cat.prototype = obj; //实现浅拷贝,首先清空了Cat.prototype对象。
    //前:Cat.prototype: ① __proto__, ② consturctor; obj: ① __proto__ 值为Animal.prototype, ② obj的其他属性;
    //后:Cat.prototype:__proto__(Animal.prototype: ① __proto__, ② constructor) Cat的原型对象为Animal的实例;
    Cat.prototype.howl = '喵'
    return new Cat();  //new Cat().__proto__ = Cat.prototype拷贝后的新增属性 + obj
}
var animal = new Animal();
var cat = f(animal); 
console.log(cat) //Cat对象,只有__proto__属性
console.log('cat.__proto__:',cat.__proto__) //Animal对象
//cat.__proto__的属性有:Cat.prototype拷贝后本身的新增属性 + animal对象的属性

cat.__proto__.__proto__.actions.push('乐')
console.log(animal.actions) //共享原型属性
console.log(cat.__proto__===animal) //浅拷贝
console.log(cat instanceof Animal); // true
  • 特点:

1、不同于原型链继承,原型继承支持多继承。

2、 类似于复制一个对象,用函数来包装 。只是复制了animal.proto的值即父类的原型对象。

3、可以访问父类原型对象的属性和父类构造函数的属性。

  • 缺点:

1、效率较低,内存占用高(因为要拷贝父类的属性)。

2、所有实例都会继承原型上的属性。

3、无法实现复用(新实例属性都是后面添加的)。

5、寄生式继承

//**********寄生式继承**********
function Animal(name) {
  this.name = name || 'Animal';
  this.sleep = function () {
    return this.name + '正在睡觉!'
  }
}
Animal.prototype.eat = function (food) {
  return this.name + '正在吃:' + food
};
function f(obj){
    function Cat(){}
    Cat.prototype = obj; //Cat的原型对象为Animal的实例,实现浅拷贝。
    return new Cat(); 
    console.log(new Cat()) //无howl方法
    console.log(new Cat().__proto__===Cat.prototype===obj)
}
function f2(obj){
  var clone = f(obj); //将Cat的实例复制给clone
  clone.howl = function(){ //以某种方式来增强这个对象
    console.log("miao~~~~~~~~~");
  };
  return clone;//返回这个对象,更新了cat的属性
}
var animal = new Animal();
var cat = f2(animal); 
console.log(cat) // 有howl方法
  • 特点:

1、 给原型式继承穿了个外套,看起来比较像继承。可以增强子类实例的属性。

  • 缺点:

1、同原型式继承

6、寄生组合式继承(最佳)

实现方法:组合式继承+原型式继承。

// **********寄生组合继承**********
function Animal(name) {
  this.name = name || 'Animal';
  this.sleep = function () {
    return this.name + '正在睡觉!'
  }
}
Animal.prototype.eat = function (food) {
  return this.name + '正在吃:' + food
};
function Cat(name){
  Animal.call(this,name); 
} 

function f(obj){
    function F(){}
    F.prototype = obj; 
    return new F();  //new F().__proto__= obj  
}
var f1 = f(Animal.prototype) // f1.__proto__ === F.prototype; Animal.prototype拷贝给F.prototype
// f1.__proto__:  Animal.prototype( ① __proto__, ② constructor, )
console.log(f1.__proto__===Animal.prototype) //f1(F实例)的原型继承了父类函数的原型对象
console.log(f1.__proto__)
console.log(f1 instanceof Animal)
Cat.prototype = f1; //拷贝了f1实例,通过以上拿到父类原型对象实现原型链继承的功能,但不需要再次调用父类的构造函数。
Cat.prototype.a = '---' 
// 前:Cat.prototype: __proto__, constructor
// 后:Cat.prototype: 1、f1的属性:__proto__( Animal.prototype( ① __proto__, ② constructor, ③自定义属性)) , 2、Cat.prototype新增属性a
f1.constructor = Cat;  // 新增constructor,修复Cat.prototype原型对象
console.log(Cat.prototype)
var cat = new Cat(); 
console.log(cat.__proto__)

function extend(subClass,superClass){
  var prototype = object(superClass.prototype);//创建对象
  prototype.constructor = subClass;//修复对象
  subClass.prototype = prototype;//指定对象
}
extend(Cat,Animal)
  • 特点:

1、 通过寄生方式,砍掉父类的实例属性,在调用父类的构造函数时,就不会初始化两次实例方法/属性。不必为了指定子类型的原型而调用超类型的构造函数。

2、 寄生组合式继承就是为了降低调用父类构造函数的开销而出现的,避免的组合继承的缺点。

  • 缺点:

1、实现复杂。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值