js原型以及class语法糖实现

在js中,原型,原型链及原型继承的重要性不言而喻,它不像java等语言通过class来继承:
以下以java中的继承为例:

	// 父类
	public class Person{
		private String name,
		private int age,
		public Person(String name,int age){
			this.name = name
			this.age = age
		}
		...
	}
	// 子类继承父类
	public class Teacher extents Person{
		public Teacher(String name,int age){
			super(name,age)
		}
	}

在es6之前,js是通过原型来继承;es6则借鉴java等语言推出了class语法糖,class只是语法糖,但本质上依旧是基于原型的继承。

既然es6推出了class语法糖,那还是先来看看class语法糖的使用吧,下面结合场景来使用:

 有这么一个场景,假设我们有一个People个人类,
	class People{
		// 
		constructor(name,age,gender){
			this.name = name
			this.age = age
			this.gender = gender
		}
		sayHi(){
		// 下面console.log中为模板字符串,等同于 "hello,"+this.name+',性别'+this.gender+",年龄"+this.age
			console.log(`hello,我是${this.name},性别${this.gender},年龄${this.age}`)
		}
	}
	// 通过上面的People类模板,我们可以实例化一个个people实例
	let p1 = new People('李四',18,'男')
	p1.sayHi()
	let p2 = new People('张三',18,'女')
	p2.sayHi()

输入结果如下:

hello,我是李四,性别男,年龄18
hello,我是张三,性别女,年龄18

假设现在,People类下又分为Teacher教师类,Student学生类,教师类有一个独有的subject学科属性,学生类有一个独有的studentId学生id号属性,那我们是不是还要给Teacher类,Teacher类再把重复的name,age属性再写一遍呢? 当然不是,我们可以抽取公共的属性和方法建立父类,子类直接继承父类的属性和方法,而在子类中去写独有的部分或者是重写父类的方法,如下:

	// People父类,包含子类中都存在的name,age,gender属性
	class People{
		constructor(name,age,gender){
			this.name = name
			this.age = age
			this.gender = gender
		}
		sayHi(){
			console.log(`hello,我是${this.name},年龄${this.age},性别:${this.gender}`)
		}
	}
	class Teacher extends People{
		constructor(name,age,gender,subject){
		    // super()调用父类的构造函数,而避免我们在这里还要this.age = age,虽然这里调用的是父类People的构造函数,但是this指向的依旧是当前子类Teacher类的实例对象
			super(name,age,gender)
			this.subject = subject	// subject是当前Teacher类独有的,所以需要自行构造, this.subject = subject
		}
		// Teacher类独有的方法,给学生上课,其他子类是不具有的
		attendClass(){
			console.log(`${this.name}给学生们上:${this.subject}`)
		}
		// 重写父类People的sayHi方法
		sayHi(){
			console.log(`{this.name}老师对学生们说:上课`)
		}
	}
	
	//学生类
	class Student extends People{
		constructor(name,age,gender,studentId){
			super(name,age,gender)
			// Student子类独有的studentId属性
			this.studentId = studentId
		}
		sayHi(){
			console.log(`名字:${this.name},性别:${this.gender},年龄:${this.age},学号:$this.studentId`)
		}
	}
	// 实例化
	let p = new People('王老头',60,'男')
	p.sayHi()
	let wangFang = new Teacher('王芳',30,'女','英语课')
	wangFang.attendClass()
	wangFang.sayHi()
	let liHua = new Student('李华',18,'男',1003)
	liHua.sayHi()

输入结果如下:
在这里插入图片描述
如上图,根据不同的构造函数创建出的实例对象,互不干扰,而且Teacher类的实例对象wangFang和Student类的实例对象liHua分别重写了父类People的sayHi方法。

注意:虽然es6语法提出了class相关规范,但还是基于原型的继承,class只是语法糖,目的是为了更容易理解继承关系。

如下图,虽然是上面的例子是通过class关键字创建类和继承,实际上还是基于原型的继承,这里仅列出Student构造出的实例对象liHua的原型链
在这里插入图片描述

以上,是es6新语法中的class面向对象思想和继承的使用。

那么,在es6之前,如何定义构造对象模板,想要继承该如何实现呢?

es6之前创建类的模板有几种方法,这里以构造函数为例:

function People(name,age,gender){
		// 属性可以照常构建
		this.name = name
		this.age = age
		this.gender = gender
}
// 但是要定义构造函数(对象)的方法,就不能直接在People中写,而是需要把方法挂载到对象的原型对象上,如下:
People.prototype.sayHi = function (){
	console.log(`姓名:${this.name},年龄:${this.age},性别:${this.gender}`)
}
let p1 = new People('lisi',18,'男')
let p2 = new People('wf',20,'女')
p1.sayHi()
p2.sayHi()
代码运行结果:
         姓名:lisi,年龄:10,性别:男
         姓名:wf,年龄:20,性别:女

分析上述代码,可画出一个原型链图,如下:
在这里插入图片描述 People构造函数通过prototype获取的对象就是原型对象,简称原型,而通过People构造函数构造出的实例对象通过.__proto__就可以获取到这个原型对象。

显式原型:即通过prototype获取的原型

隐式原型:即通过.__proto__获取的原型

现在原型的概念知道了,原型链就是如上图所示,由一个个原型连起来的链式原型称为原型链。

	// 构造函数、实例对象,原型对象之间的关系
	let p2 = new People('lisi',20,'男')
    console.log(p2.__proto === People.prototype) // true
    console.log(p2.__proto__.__proto__ === People.prototype.__proto__)  // true
    console.log(p2.__proto__.proto__ === Object.prototype)	//true
    console.log(People.prototype.__proto__.__proto__) 	//null
    // 查找规则
    console.log(p2.age)		// 20,当查找p2的age属性时,首先会在p本身寻找,如果找到就不会在继续查找;而如果没找到则会去p的原型上找age属性,如果没有,继续往p原型的上一级原型中查找,知道顶级object原型上没找到age属性,则返回undefined
    // 简单来说,会沿着原型链一级级往上

instanceof作用也很明显了:判断某个引用类型的具体类型

	let p2 = new People('lisi',20,'男')
	console.log(p2 instanceof People)	// true
	console.log(p2 instanceof Object)	// true
	console.log([] instanceof People)	//false ,因为[]不在该原型链上

hasOwnProperty的作用?

for(let key in p2){
	// 循环p2实例对象中的每一个属性,并且判断该属性是不是在p2自身对象上
	console.log(`${key} + ${p2.hasOwnProperty(key)}`)
}

输出结果如下
在这里插入图片描述
通过代码分析可知:hasOwnProperty的作用是判断对象自身属性中是否有指定的属性

如果想要实现继承,怎么做呢?

以代码为例:

	function People(name,age,gender){
		this.name = name
		this.age = age
		this.gender = gender
	}
	// 往People的原型上绑定一个方法
	People.prototype.sayHi = function (){
		console.log(`hello,我是${this.name},性别${this.gender},年龄${this.age}`)
	}

	// Student类中也有name,age,gender,还有一个独有的studentId属性
	function Student(name,age,gender,studentId){
	// 将People构造函数中的this指向为Student(准确说是通过Student构造的这个对象),相当于People构造函数中的this.name = name 就实际上转变成Teacher.name = name
		People.call(this,name,age,gender)
		// 将Student的显示原型对象的隐式原型指向People的原型对象,即在原型链上加了一个People原型
		Student.prototype.__proto__ = People.prototype
		this.sudentId = studentId
	}
	// 通过People构造出的实例对象
	let p1 = new People('李四',18,'男')
	// 通过Student构造出的实例对象
	let s1 = new Student('王芳',18,'女',1003)
	console.log(p1)
    console.log(s1)
	p1.sayHi()
	s1.sayHi()

打印结果如下:
在这里插入图片描述
基于原型的继承,其实就是在原型链上加了一环,如下图:
在这里插入图片描述

注意:class是es6语法规范,是ECMA指定的语法规范,实现方式是v8引擎的实现方式,也是主流的。
关于原型继承,参考可见:基于原型的继承实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值