常见的JS继承方式

79 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情

前言

继承是面向对象的特征之一,A 对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法。这对于代码的复用是非常有用的。

大部分面向对象的编程语言,都是通过“类”(class)实现对象的继承。传统上,JavaScript 语言的继承不通过 class(ES6 引入了class 语法),而是通过“原型对象”(prototype)实现。那么在JS中常见的继承方式有几种呢?

原型链继承

  • 核⼼:将⽗类实例作为⼦类原型
  • 优点:可以复用父类构造函数的⽅法
  • 缺点: 创建⼦类实例的时候,不能传⽗类的参数。 ⼦类实例共享了⽗类构造函数的引⽤属性,引⽤属性共享。 ⽆法实现多继承。
	// 父类
	function Parent (name){
	    this.name = name;
	    this.q = ['0'];
	}
	Parent.prototype.say = function (){
	    console.log('hi');
	}
	
	// 子类
	function Child (){}
	
	// 原型链继承
	Child.prototype = new Parent();
	
	// 子类实例
	const xiaohong = new Child('小红');
	const xiaoming = new Child('小名');
	xiaohong.q.push('1');
	console.log(xiaohong.name); //undefined
	console.log(xiaoming.name); //undefined
	//引⽤属性共享
	console.log(xiaohong.q); //["0", "1"]
	console.log(xiaoming.q); //["0", "1"]
	console.log(xiaohong.q === xiaoming.q); //true
	xiaohong.say();             //hi
	xiaoming.say();             //hi

构造函数

  • 核心:复制父类的实例给子类
  • 优点:实例之间独立, 引用属性不共享
  • 缺点:继承不了父类原型上的属性;
	// 父类
	function Parent (name){
	    this.name = name;
	    this.q = ['2'];
	}
	Parent.prototype.say = function (){
	    console.log('hi');
	}
	// 子类
	function Child (name, num){
	    // 拷贝父类实例的属性和方法
	    Parent.call(this, name);
	    this.number = num;
	}
	// 子类实例
	const xiaohong = new Child('小红', '01');
	const xiaoming = new Child('小名', '02');
	xiaoming.q.push('4');
	
	console.log(xiaohong.name);    //小红
	console.log(xiaohong.number);  // 01
	console.log(xiaohong.q);  	   // ["2"]
	console.log(xiaoming.name);    //小名
	console.log(xiaoming.number);  //02
	console.log(xiaoming.q);       //["2", "4"]
	
	//继承不了⽗类原型上的属性
	xiaohong.say();                // xiaohong.say is not a function
	xiaoming.say();                // xiaoming.say is not a function

组合式继承

  • 核⼼:通过调⽤⽗类构造函数,继承⽗类的属性并保留传参的优点;然后通过将⽗类实例作为 ⼦类原型,实现函数复⽤。
  • 优点: 保留构造函数的优点:创建⼦类实例,可以向⽗类构造函数传参数。 保留原型链的优点:⽗类的⽅法定义在⽗类的原型对象上,可以实现⽅法复⽤。 不共享⽗类的引⽤属性。
  • 缺点:调⽤了2次⽗类的构造⽅法,会存在⼀份多余的⽗类实例属性;
	// 父类
	function Parent (name){
	    this.name = name;
	}
	Parent.prototype.say = function (){
	    console.log('hi');
	}
	// 子类
	function Child (name, num){
	    // 拷贝父类实例的属性和方法
	    Parent.call(this, name);    //两次继承
	    this.number = num;
	}
	
	Child.prototype = new Parent(); //一次继承
	// 子类实例
	const xiaohong = new Child('小红', '01');
	const xiaoming = new Child('小名', '02');
	
	console.log(xiaohong.name);    //小红
	console.log(xiaohong.number);  // 01
	console.log(xiaoming.name);    //小名
	console.log(xiaoming.number);  //02
	xiaohong.say();                //hi 
	xiaoming.say();                //hi
	console.log(xiaohong.say === xiaoming.say) ;   //true   

ES6 class

	//父类
	class People {
	    constructor(name){
	        this.name = name;
	    }
	    say(){
	        console.log('hi'+ this.name);
	    }
	}
	
	// 父类实例
	const xiaohong = new People('小红');
	console.log(xiaohong.name);     //小红
	xiaohong.say();                 //hi小红
	
	// 子类继承父类
	class Student extends People {
	    constructor(name, number){
	        super(name);
	        this.number = number;
	    }
	    say(){
	        console.log(this.name + '学号:' + this.number);
	    }
	}
	
	// 子类实例
	const xiaoming = new Student('小明', '001');
	console.log(xiaoming.name);      //小明
	console.log(xiaoming.number);    // 001
	xiaoming.say();                  //小明学号:001

本文到此结束

如果大家还有什么其他想法,欢迎在评论区交流!