今日推荐歌曲:
pasta
一、ES6—CLASS类
1、介绍
来由
上篇讲了面向对象中以构造函数的形式创建类,而在ES6中,面向对象这种写法有了新的写法,也就是 类class 写法。
主要原因还是ES5中原型的写法不易被其他语言的人接受,于是基于 原型属性prototype 改良得到的一种语法糖。
什么是语法糖?
语法糖(Syntactic Sugar)是指编程语言设计者为了提高代码的可读性和简洁性而添加的一些非必需的、但易于理解的语法特性。它们并不改变程序的实际功能,而是通过提供更直观的表达方式,使得开发者能更快地编写出清晰的代码。例如在之前的学习中,箭头函数就是一个常见的语法糖,它简化了函数定义的形式,方便书写,但并没改变函数的实际功能。
ES6中的类class也是一种语法糖,有了前面 ES面向对象 学习的基础,其实就很好理解了,俗话说就是换了个皮,内在没变。
- 在ES6中,class 作为对象的模板被引入,可以通过 class 关键字定义类。
- 类是对象的抽象,对象是类的实例
- class 的本质是 function,依然是前面所讲的构造函数、原型链的语法糖而已。
2、对比说明
1、ES5面向对象写法
我们先回顾之前 以构造函数的形式创建类——菜品类:
function CreateObj(name,material){
//私有属性/方法
this.name = name //菜名
this.material = material //原料
}
//把共享函数和属性放在 prototype 原型对象中
//共有方法
CreateObj.prototype.method1 = function (){console.log("方法1",this.name)} //方法1
CreateObj.prototype.method2 = function (){console.log("方法2",this.material)} //方法2
//创建实例对象
let obj1 = new CreateObj("鱼香肉丝",["原料111","222","333"])
console.log(obj1)
2、ES6CLASS写法
对于只学了JS语法的人初学是比较容易接受的,而对于学了其他语法中类的人是难以接受的,这种写法看起来和普通函数没什么区别,而且内容也不是那么容易理解。下面以类的形式来写这个菜品类:
class CreateObj{
constructor(name,material) { //实例化后this指向实例化对象
//属性的定义——写在构造器函数constructor内
this.name = name
this.material = material
}
//方法的定义——直接写在内部
method1(){
console.log(this.name,"mehtod1")
}
method2(){
console.log(this.name,"mehtod2")
}
}
//类是对象的抽象,对象是类的实例。以类创建一个对象
var obj = new CreateObj("鱼香肉丝",["111","222","333"])
console.log(obj)
上述代码中,有类的声明(class CreateObj(){ }),类中有构造器函数constructor(){ },属性的定义就写在构造器函数中,而方法的定义就写在大括号里。
类既然是基于 原型属性prototype 改良得到的一种语法糖,那么结果必然是一样的,不出所料,两者的运行结果是一样的。说白了就是换了一种写法,写法不止一种,这里就不说了。
下面的链接可以看看:
二、面向对象继承
1、介绍
JavaScript中的面向对象继承是一种机制,它允许创建新的对象并自动地从现有的对象(称为原型或构造函数)继承属性和方法。
一共有三种:
- 构造函数继承
- 原型继承
- 组合继承(构造函数继承+原型继承)
2、构造函数继承
tips:只能继承属性
下面是我们以构造函数的形式创建的两个类:人类和学生类
//构造函数 人类——具有属性name、age和方法say
function Person(name,age){
this.name = name
this.age = age
}
Person.prototype.say = function (){
console.log(this.name,"Hello")
}
//构造函数 学生类——比人类多一个grade属性
function Student(name,age,grade){
this.name = name
this.age = age
this.grade = grade
}
Student.prototype.say = function (){
console.log(this.name,"Hello")
}
如果按照这种写法去创建新的学生类,并没有继承人类的任何东西,人类中可以被学生类用到的属性和方法学生类统统没有继承,完全重新写了一个,这无疑是一种浪费。下面以继承的方式来写:
function Student(name,age,grade){
// 在这里把 Person 当做普通函数执行一遍
// 并且在调用的时候, 使用 call 来调用, 改变 this 指向
// 在这个函数内, this 指向 Student 的实例
// 现在利用 call 把 Person 函数内的 this 指向了 Student 的实例
// Person 函数内添加的两个成员, 就加在了 Student 的实例 身上
Person.call(this,name,age) //继承了人类中可以用到的属性
//或者
//Person.apply(this,[name,age])
this.grade = grade
}
看似好像差不多,但可以想到,当属性很多的时候,一句话就可以把所有属性继承过来。原理就在上述代码注释中。
只把属性继承过来!!!(没有new say()函数,自然无法继承)
3、原型继承
tips:用来继承方法
想要继承方法还不简单吗?看下面一句代码:
Student.prototype = Person.prototype
我直接把Person的原型赋给Student的原型,这样Student类中不就有前者的属性和方法了吗?
但这种方法不可取,因为当你修改前者的原型会影响后者,反之亦然。
看看下一个方法:
Student.prototype = new Person()
通过new Person() 得到一个对象,把这个对象赋给Student类的原型对象,这样一来就有了Person类的方法了。但看一下按照这个方法创建的Student类的实例对象是什么样子:
显然多了一些我们不需要的东西。这是为什么呢?
看代码,我们new的时候没有传参数,所以name和age赋了undefined。但也的确获得了想要继承的方法。
还有个问题:这个多出来的name和age会影响到Student构造函数中的name和age吗?
不会的,根据原型链,每次调用Student这个构造函数,都会先去找自身的属性,找不到才会继续往下找。我们已经通过构造函数继承得到了属性,自然不会被影响。
当我们成功原型继承后,就可以在继承来的Person类的基础上对Student类进行操作:
基础增加方法:
Student.prototype.printGrade = function(){ } //在继承得到的基础上,又增加了“成绩打印”方法
对继承来的方法覆盖:
Student.prototype.say = function(){ console.log("覆盖了之前 say() 的内容") }
增强原来方法:
Student.prototype.say2 = function(){ this.say() //先执行say的内容 //对say()的内容进行补充加强 console.log("补充的内容") }
4、组合继承
即是 构造函数类型 + 原型继承,如上结合。
三、ES6继承
1、介绍
也是一种语法糖,先看代码:
//父类
class Person{
constructor(name,age) {
this.name = name
this.age = age
}
say(){
console.log(this.name,"1111111HELLO")
}
}
//extends :类似于原型继承,继承父类原型上的方法
//子类
class Student extends Person{
constructor(name,age,grade) {
//继承属性
super(name,age); //Person.call(this,name,age)
this.grade = grade
}
}
var obj = new Student("shuaige",100,100)
console.log(obj)
关键:
- extends:继承方法
- super:继承属性
2、一些方法
问题一:子类Student中的say()想要覆盖父类中的say()
想要覆盖父类的方法:重新起个名字
say(){
console.log("shuaige","你好") //再次打印,输出: shuaige 你好,而不是111111HELLO
}
问题二:在调用子类新方法前,先调用父类原来的方法
say(){
super.say() //找到调用父类中say()
console.log("shuaige","你好")
}