ES5和ES6分别是如何实现继承的


javascript的继承主要是依托其 原型与原型链的概念来实现的。
嗯…先来补习一下有关原型与原型链吧。

一、名词解释

function Cat(){};
const cat = new Cat();//new 操作都做了什么?
  • 构造函数:用来初始化新对象的函数叫做构造函数。如示例中的Cat函数就是构造函数
  • 实例对象:通过 new关键字创建出来的对象。如示例中的cat就是实例对象
  • 原型对象及prototype:构造函数有一个prototype属性,它指向实例对象的原型。常使用原型对象来实现继承
  • constructor:原型对象具有constructor属性,指向原型对象的构造函数
  • proto:__proto__属性类似是指针,指向构造函数的原型对象。
  • prototype__proto__的区别,简单理解为: 构造函数.prototype === 实例对象.__proto__,他们都指向同一个原型对象。
    在这里插入图片描述

原型链JavaScript 对象(除了 null)在创建的时候就会关联一个对象,这个对象就是原型,每一个对象都会从原型上继承属性,原型也是对象,所以原型也有原型对象,层层往上,直到 Object.prototype,这就是原型链。

new 操作都做了什么?

  1. 在内存中创建一个新的对象{}
  2. 将新对象内部的__proto__ 赋值为构造函数的prototype
  3. 将构造函数内部的this指向新对象
  4. 执行构造函数内部的代码,给新对象添加属性
  5. 如果构造函数返回非空对象,则返回该对象,否则返回this

模拟实现new操作:

function fakeNew() {
  // 创建新对象
  var obj = Object.create(null);
  var Constructor = [].shift.call(arguments);
  // 将对象的 __proto__ 赋值为构造函数的 prototype 属性
  obj.__proto__ = Constructor.prototype;
  // 将构造函数内部的 this 赋值为新对象
  var ret = Constructor.apply(obj, arguments);
  // 返回新对象
  return typeof ret === "object" && ret !== null ? ret : obj;
}

function Group(name, member) {
  this.name = name;
  this.member = member;
}

var group = fakeNew(Group, "hzfe", 17);

二、ES6中实现继承

ES6提供了Class关键字来实现类的定义,Class可以通过extends关键字实现继承,让子类继承父类的属性和方法;还可以通过 static关键字定义类的静态方法,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。 需要注意⚠️的是:class 关键字只是原型的语法糖, JavaScript 继承仍然是基于原型实现的。
例子:

	class Pet{
		constructor (name, age) {
			this.name  = name;
			this.age = age;
		}

		showName () {
			console.log('调用父类的方法');
			console.log(this.name, this.age);
		}
	}
	class Dog extends Pet {
		constructor(name,age,color) {
		// 通过 super 调用父类的构造方法
			super(name, age);
			this.color = color;
		}
		showName() {
			console.log('调用子类的方法');
			console.log(this.name, this.age, this.color);
		}
	}

缺点: 不是所有的浏览器都支持 class,存在兼容问题

接下来重点看一下ES5的四种常用的实现方式。

三、ES5中实现继承

1.原型链继承

原型链继承的原理很简单,直接让子类的原型对象指向父类实例,当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承。
举个🌰:

function Person () {
	this.name='lili';
}
Person.prototype.getName = function () {
	return this.name;
}

function Student () {};
Student.prototype = new Person();
// 根据原型链的规则,顺便绑定一下constructor, 这一步不影响继承, 只是在用到constructor时会需要
// 原型的实例等于自身
Student.prototype.constructor = Student;

const student = new Student();
console.log(student.name); // lili
console.log(student.getName()); // lili

缺陷:

  1. 由于所有Student实例原型都指向同一个Person实例, 因此对某个Student实例的来自父类的引用类型变量修改会影响所有的Student实例。
  2. 在创建子类实例时无法向父类构造传参, 即没有实现super()的功能

2.构造函数继承

构造函数继承,即在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,让父类的构造函数把成员属性和方法都挂到子类的this上去,这样既能避免实例之间共享一个原型实例,又能向父类构造方法传参

举个🌰:

function Person (name) {
	this.name = name;
}
Person.prototype.getName = function () {
	return this.name;
}
function Student () {
	Person.apply(this, arguments)
}
const student  = new Student('lili');
console.log(student.name); // lili

缺陷:

  1. 实例并不是父类的实例,只是子类的实例,
  2. 只能继承父类的实例属性和方法,不能继承原型属性和方法
    Students类实际上是调用Person类来生成的实例
  3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

3. 组合继承

组合是继承结合了原型继承和构造函数继承的特点:使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承

function Person (name) {
	this.name= name;
}
Person.prototype.getName = frunction () {
	return this.name
}
function Student () {
	// 构造函数继承
	Person.apply(this, arguments);
}
// 原型继承
Student.prototype = new Person();
// 原型的实例等于自身
Student.prototype.constructor = Student;
const strudent= new student('lili');
console.log(student.name); // lili
console.log(student.getName()); // lili

缺陷:

  1. 每次创建子类实例都执行了两次构造函数(Person.apply和new Person()),虽然这并不影响对父类的继承,但子类创建实例时,原型中会存在两份相同的属性和方法,这并不优雅。

4.寄生式组合继承(这是目前ES5中比较成熟的继承方式了)

解决构造函数被执行两次的问题, 我们将指向父类实例改为指向父类原型, 减去一次构造函数的执行。

function Person(name) {
    this.name = name;
}
Person.prototype.getName = function() {
    return this.name;
}
function Student() {
    // 构造函数继承
    Person.apply(this, arguments)
}
// 原型式继承
// Student.prototype = new Person(); 
Student.prototype = Object.create(Person.prototype);

// 原型的实例等于自身
Student.prototype.constructor = Student;

const student = new Student('lili');
console.log(student.name); // lili
console.log(student.getName()); // lili

5. 原型式继承

原型式继承,主要借助Object.create()方法实现对普通对象的继承。

let parent4 = {
    name: "parent4",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };

  let person4 = Object.create(parent4);
  person4.name = "tom";
  person4.friends.push("jerry");

  let person5 = Object.create(parent4);
  person5.friends.push("lucy");

  console.log(person4.name); // tom
  console.log(person4.name === person4.getName()); // true
  console.log(person5.name); // parent4
  console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
  console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

这种继承方式的缺点也很明显,因为Object.create 方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能。

参考地址: https://juejin.cn/post/7069403417906528264?utm_source=gold_browser_extension

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值