JavaScript中实现继承的几种方法

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 JavaScript中实现继承的几种方法,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

首先,定义一个类

function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉');
  }
}
// 原型方法
Animal.prototype.eat = function(food) {
  console.log(this.name + '正在吃:' + food);
};

1. 原型链继承

思想: 利用原型链来实现继承,超类的一个实例作为子类的原型

function Dog(){ }
Dog.prototype = new Animal();
Dog.prototype.name = '狗';

var dog = new Dog();

console.log(dog.name); // 狗
dog.eat('狗粮'); // 狗正在吃:狗粮
dog.sleep(); // 狗正在睡觉
console.log(dog instanceof Animal);  // true 
console.log(dog instanceof Dog);  // true

特点:

  1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例;
  2. 父类新增原型方法/原型属性,子类都能访问到;
  3. 简单,易于实现;

缺点:

  1. 可以在Dog构造函数中,为Dog实例增加实例属性。如果要新增原型属性和方法,则必须放在new Animal()这样的语句之后执行;
  2. 无法实现多继承;
  3. 来自原型对象的所有属性被所有实例共享;
  4. 创建子类实例时,无法向父类构造函数传参;

2. 构造函数继承

思想: 通过使用call、apply方法在新创建的对象上执行构造函数,用父类的构造函数来增加子类的实例

function Dog(name){
  Animal.call(this);
  this.name = name || '汪汪';
}

var dog = new Dog();

console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true

特点:

  1. 解决了方法1中,子类实例共享父类引用属性的问题;
  2. 创建子类实例时,可以向父类传递参数;
  3. 可以实现多继承(call多个父类对象);

缺点:

  1. 实例并不是父类的实例,只是子类的实例;
  2. 只能继承父类的实例属性和方法,不能继承原型属性/方法;
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能;

3. 实例继承

思想: 为父类实例添加新特性,作为子类实例返回

function Dog(name){
  var instance = new Animal();
  instance.name = name || '汪汪';
  return instance;
}

var dog = new Dog();

console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // false

特点:

  1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果;

缺点:

  1. 实例是父类的实例,不是子类的实例;
  2. 不支持多继承;

4. 拷贝继承

function Dog(name){
  var animal = new Animal();
  for(var p in animal){
    Dog.prototype[p] = animal[p];
  }
  Dog.prototype.name = name || '汪汪';
}

var dog = new Dog();
console.log(dog.name); // '汪汪'
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // false
console.log(dog instanceof Dog); // true

特点:

  1. 支持多继承;

缺点:

  1. 效率较低,内存占用高(因为要拷贝父类的属性);
  2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到);

5. 原型式继承

思想: 采用原型式继承并不需要定义一个类!!!,传入参数obj,生成一个继承obj对象的对象

var animal = {
  name: "汪汪",
  age: 2,
}

function F(o) {
  function C() {}
  C.prototype = o;
  return new C();
}

var dog = F(animal)
console.log(dog.name); // 汪汪
console.log(dog.age); // 2

特点:

  1. 直接通过对象生成一个继承该对象的对象;

缺点:

  1. 不是类式继承,而是原型式基础,缺少了类的概念;

6. 组合继承(推荐)

思想: 利用构造继承和原型链组合

function Dog(name){
  Animal.call(this);
  this.name = name || '汪汪';
}

Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;

var dog = new Dog();

console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // true

特点:

  1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法;
  2. 既是子类的实例,也是父类的实例;
  3. 不存在引用属性共享问题;
  4. 可传参;
  5. 函数可复用;

缺点:

  1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了);

7. 寄生式继承

思想: 原型式+工厂模式 ,解决了组合继承两次调用构造函数的问题; 创建一个仅仅用于封装继承过程的函数,然后在内部以某种方式增强对象,最后返回对象;

//临时中转函数
function obj(o) {
  function Animal() {}
  Animal.prototype = o;
  return new Animal();
}

//寄生函数
function create(o){
  var Dog = obj(o);
	// 可以对f进行扩展
  Dog.sleep = function(){
  	console.log(this.name + '在睡觉') 
  }
  return Dog;
}

var mydog = {
  name: '汪汪',
  age: 1,
};

var dog = create(mydog);

console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪在睡觉

特点:

  1. 原型式继承的一种拓展

缺点:

  1. 依旧没有类的概念

8. 寄生组合继承(推荐)

思想: 结合寄生式继承和组合式继承,完美实现不带两份超类属性的继承方式

// 该实现没有修复constructor
function Dog(name){
  Animal.call(this);
  this.name = name || '汪汪';
}
(function(){
  // 创建一个没有实例方法的类
  var Super = function(){};
  Super.prototype = Animal.prototype;
  //将实例作为子类的原型
  Dog.prototype = new Super();
})();

var dog = new Dog();

console.log(dog.name); // 汪汪
dog.sleep(); // 汪汪正在睡觉
console.log(dog instanceof Animal); // true
console.log(dog instanceof Dog); // true

补充:

// 定义一个类
function Animal (name) {
  // 属性
  this.name = name || 'Animal';
  // 实例方法
  this.sleep = function(){
    console.log(this.name + '正在睡觉');
  }
  //实例引用属性
  this.features = [];
}

function Dog(name){}
Dog.prototype = new Animal();

var tom = new Dog('Tom');
var kissy = new Dog('Kissy');

console.log(tom.name); // "Animal"
console.log(kissy.name); // "Animal"
console.log(tom.features); // []
console.log(kissy.features); // []

tom.name = 'Tom-New Name';
tom.features.push('eat');

//针对父类实例值类型成员的更改,不影响
console.log(tom.name); // "Tom-New Name"
console.log(kissy.name); // "Animal"

//针对父类实例引用类型成员的更改,会通过影响其他子类实例
console.log(tom.features); // ['eat']
console.log(kissy.features); // ['eat']

// 原因分析:

// 1. 执行tom.features.push,首先找tom对象的实例属性(找不到),那么去原型对象中找,也就是Animal的实例。发现有,那么就直接在这个对象的features属性中插入值。
// 2. 在console.log(kissy.features); 的时候。同上,kissy实例上没有,那么去原型上找。
// 3. 刚好原型上有,就直接返回,但是注意,这个原型对象中features属性值已经变化了。

特点:

  1. 堪称完美;

缺点:

  1. 实现较为复杂;

9. ES6中的继承(强烈推荐)

思想: ES6中通过 Class ‘类’ 这个语法糖实现继承和Java等面向对象的语言在实现继承上已经非常相似,当然只是语法层面相似,本质当然依旧是通过原型实现的。
ES6实现继承是通过关键字extends、super来实现继承,和面向对象语言Java一样。

class Animal{
  constructor(name,age){
    this.name = name || 'Animal';
    this.age = age
    this.hobbies = ['睡觉','吃饭']
  }
  /* 下面的写法就等同于把方法挂在原型上 */
  static say(){ // 加了static 静态方法,只给类用的方法
    console.log('你好');
  } // 方法和方法之间不用加逗号 
  getname(){
    console.log(this.name);
  }
}

class Dog extends Animal{
	/*
    在子类constructor中添加属性的小技巧 
    专属于子类的属性写在参数的前面,父类的属性放在后面
    这样一来,就能直接使用...arg
 		...arg
  	把函数中的多余的参数放入数组中体现出来。
  */
  constructor(food,...arg){
    super(...arg); // 等同于调用父类,把多余的参数(和父类一样的属性)放到super中,达到继承父类属性的目的
    /*
      在继承里,写constructor必须写super
      super下面才能使用this,super有暂存死区(super上面不能使用this,用了就报错)
    */
    this.food = food
  }
  static sayState(){
    console.log("我在睡觉")
  }
  sayAge(){
    console.log("我今年" + this.age + '岁');
  }
  sayFood(){
  	console.log('我在吃'+this.food)
  }
}

var dog1 = new Dog('骨头',"汪汪",2);

Animal.say() // 你好
Dog.sayState();  // 我在睡觉

dog1.getname();  // 汪汪
dog1.sayAge(); // 我今年2岁
dog1.sayFood(); // 我在吃骨头

var dog2 = new Dog('狗粮',"旺财",1);
dog2.hobbies.push("玩耍");

dog2.getname();  // 旺财
dog2.sayAge(); // 我今年1岁
dog2.sayFood(); // 我在吃狗粮

console.log(dog1.hobbies)  // ['睡觉','吃饭']
console.log(dog2.hobbies)  // ['睡觉','吃饭','玩耍']

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/141449.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!