目录
一、ES6的面向对象的语法(ES6创建类)
1、面向过程(了解)
核心是过程。即分析出解决问题的步骤,用函数将这些过程一一实现,然后依次调用。
2、面向对象
核心是对象。将需要解决的问题分解成一个个对象,这些对象不是解决问题的步骤。对象只是描述了在解决问题的过程中行为是什么(干了哪些工作)。
优点:
(1)封装性更强,模块化更深
(2)更容易实现复杂的业务逻辑
(3)容易维护、模块的复用率高
3、面向对象的特征
(1)封装性:对象是属性和行为的结合体
(2)多态性:同一个消息被不同的对象接收后,会产生不同的效果
(3)继承性:子类继承父类的部分属性和方法,子类中可以直接使用这些属性和方法
4、ECMAScript6(即ES6)的面向对象的语法
简介:ECMAScript6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了
(1)类:具有相同属性和行为的对象的集合
(2)对象:是类的实例化,是类的具体体现(类是对象的抽象,对象是类的具体体现)
(3)类的基本语法(ES6中定义类):
class 类名
{
constructor([参数]){ //构造方法:初始化类的成员变量
属性(成员变量)
}
行为(成员方法)
}
(4)ES6中创建对象(使用new运算符):
对象名 = new 类名([参数])//在内存中开辟了一个存储空间
详述:
注意:a、类名:首字母大写 b、成员方法名:动词开头.驼峰命名,表示某种行为。
构造方法: a、作用是初始化对象 b、调用:不能显式调用,而是通过类名调用.
this指针:指当前的对象
属性和方法的使用:a、属性:对象名.属性 b、方法:对象名.方法名([参数])
☀️举个例子,理解一下:
class Person{
constructor(name){//构造方法,初始化类的成员变量,
this.name=name//this:代表是当前对象,谁调用这个对象,this就是谁
}
}
//1.类的构造方法不会显式的调用,而是通过类名调用
//2.this:代表当前的对象t1
//3.this.name:它表示当前对象t1的属性name
var t1 = new Person('刘备')//这句话的意思是在内存中开辟了一个存储空间,new是申请了一块存储空间,s1就是复制了它的地址
var p1 = new Person('周瑜')
console.log(t1)
console.log(p1)
☀️举个例子,理解一下:
class Student{
constructor(id,name,sex){//构造方法,带有三个参数,但不是里面只有三个属性,是指给这三个属性(参数)进行赋值
this.id=id; //构造方法:①作用:初始化对象②调用:不能显式调用,而是通过类名对象
this.name=name; //初始化变量的这时候的this指针,指的是当前对象,这时候没有明确的对象,当var s1对象被创建时,this才有了明确的对象s1。
this.sex=sex;
this.age=18;//不是参数的属性值给死,固定的,后面可以更改
this.address=null;//不是参数的属性值给死,固定的,后面可以更改
}
show(){//成员方法(成员函数)
console.log('学号:',this.id)
console.log('姓名:',this.name)
console.log('性别:',this.sex)
console.log('年龄:',this.age)
console.log('地址:',this.address)
}
}
var s1 =new Student(1001,'郭靖','男')//s1就是一个对象,是Student类的一个实例化,
s1.show()
//属性的使用:对象名.属性名
s1.age=24
s1.address='蒙古'
//方法的使用:对象名.方法名([参数])
s1.show()
5、练习
1、定义学生类(Student),属性有name、gender、age、address、phone,
行为(方法)有showInfo,用于显示学生信息。创建对象测试类。
class Student{
constructor(name,gender){
this.name=name
this.gender=gender
this.age=18 //定义初始化成员变量
this.address=null
this.phone=null
}
showInfo(){//成员方法,
let info='姓名:'+this.name+'\n'+
'性别:'+this.gender+'\n'+
'年龄:'+this.age+'\n'+
'电话:'+this.phone
console.log(info)
}
}
var s1 =new Student('郭靖','男')
s1.age=24
s1.address='桃花岛'
s1.phone='1347890977'//访问属性并将其更改
s1.showInfo()//调用方法显示
2、创建一个电脑类(Computer) 有颜色(color)、重量(weight)、品牌(brand)、
型号(type)属性, 有看电影(watch)、听音乐(listen)、打游戏(play)和敲代码(coding)方法。
创建对象,在调方法时分别打印 “我在 看电影/听音乐/打游戏/敲代码”。
class Computer{
constructor(){
this.color=null
this.weight=0
this.brand=null
this.type=null
}
watchMovie(){
let watch='我在看电影'
console.log(watch)
}
listenMusic(){
let listen='我在听音乐'
console.log(listen)
}
playYouxi(){
let play='我在玩游戏'
console.log(play)
}
codingDai(){
let coding='我在敲代码'
console.log(coding)
}
show(){
var info='品牌:'+this.brand+'\n'+
'型号:'+this.type+'\n'+
'重量:'+this.weight+'\n'+
'颜色:'+this.color
console.log(info)
}
}
var s2=new Computer()
s2.color='grey'
s2.weight='100'
s2.brand='联想'
s2.type='core i7'
s2.show()
s2. watchMovie()
s2.listenMusic()
s2.playYouxi()
s2.codingDai()
3、创建类Temperature,包含成员变量degree(表示温度)以及成员方法,ToFahrenheit(摄氏温度转为华氏温度)和ToCelsius(华氏温度转摄氏温度)
其中: 摄氏度= (华氏温度-32)5/9,华氏度= 摄氏度9/5+32
创建对象,显示转换结果。
class Temperature{
constructor(){
this.degree=null
}
ToFaherenheit(){
var sheshi
sheshi = (this.degree-32)*5/9
console.log(sheshi)
}
ToCelsius(){
var huashi
huashi=this.degree*9/5+32
console.log(huashi)
}
}
var s3=new Temperature()
s3.degree=45
s3.ToFaherenheit()
s3. ToCelsius()
二、ES6中类的继承
ES6中类的继承:子类可以继承父类的部分属性和方法,在继承后子类还可以定义自己独有的属性和方法
(1)子类(派生类):继承的类
(2)父类(超类,基类):被继承的类
(3)语法格式:
class 子类名 extends 父类名{
构造方法(){}
成员方法
}
☀️举个例子:
class Father{
constructor(){}
money(){
console.log(100)
}
}
class Son extends Father{//子类Son继承Father
}
//创建子类对象
var s1 =new Son()
s1.money()//子类继承父类的方法
(4)子类访问父类的属性和方法
A、使用super关键字调用父类的构造方法: super([参数])
以下三个点需要注意:
(a)从父类继承过来的属性必须通过super调用父类的构造方法进行初始化
☀️举个例子:
class Father{
constructor(x,y){
this.x=x //this.x中的x表示父类Father的属性
this.y=y //this.y中的y表示父类Father的属性
}
getSum(){//获取x和y的值
console.log(this.x+this.y)
}
}
class Son extends Father{//子类Son继承Father
//调用父类的构造方法初始化x和y的属性
constructor(x,y){//是子类Son自己的构造方法
super(x,y)//调用父类的构造方法 //注意:从父类继承过来的属性必须通过super来初始化
}
}
var s1=new Son(10,20)
s1.getSum()
(b)super([参数])必须作为子类构造方法的第一条语句
(c)无论是父类或子类属性的初始化都必须通过构造方法来完成
class Father{
constructor(x,y){
this.x=x //this.x中的x表示父类Father的属性
this.y=y //this.y中的y表示父类Father的属性
}
getSum(){//获取x和y的值
console.log(this.x+this.y)
}
}
class Son extends Father{//子类Son继承Father
//调用父类的构造方法初始化x和y的属性
constructor(x,y,z){//是子类Son自己的构造方法
super(x,y)//调用父类的构造方法
//super([参数])必须作为子类构造方法的第一条语句,因为子类继承先调用的是父类构造方法,不然会报错。
this.z=z //this.z中的z是Son自己的属性
}
}
var s1=new Son(10,20)
s1.getSum()
报错显示:
正确显示:
B、使用super调用父类的普通成员方法:super.方法名([参数])
☀️举个例子
class Father{
constructor(x,y){
this.x=x //this.x中的x表示父类Father的属性
this.y=y //this.y中的y表示父类Father的属性
}
getSum(){//获取x和y的值
console.log(this.x+this.y)
}
}
class Son extends Father{//子类Son继承Father
//调用父类的构造方法初始化x和y的属性
constructor(x,y,z){//是子类Son自己的构造方法
super(x,y)//调用父类的构造方法
//无论子类还是父类,属性的初始化,都必须通过构造方法来完成
this.z=z //this.z中的z是Son自己的属性
}
Sum(){//子类Son自己的成员方法
super.getSum()//通过super调用父类的getSum()方法
}
}
var s1=new Son(10,20)
s1.Sum()
(5)方法覆盖(override)
方法覆盖(override):当子类的成员方法和父类的成员方法重名时,子类的方法会覆盖父类的同名方法。
小引申: 方法重载(OverLoad):给同一个方法(函数)传递不同的参数,其执行结果不同。
☀️举个例子:
class Father{
constructor(x,y){
this.x=x //this.x中的x表示父类Father的属性
this.y=y //this.y中的y表示父类Father的属性
}
getSum(){//获取x和y的值
// console.log(this.x+this.y)
return (this.x+this.y)
}
}
class Son extends Father{//子类Son继承Father
//调用父类的构造方法初始化x和y的属性
constructor(x,y,z){//是子类Son自己的构造方法
super(x,y)//调用父类的构造方法
//注意:①从父类继承过来的属性必须通过super调用父类的构造方法来进行初始化
//②super([参数])必须作为子类构造方法的第一条语句,因为子类继承先调用的是父类构造方法。
//③无论子类还是父类,属性的初始化,都必须通过构造方法来完成
this.z=z //this.z中的z是Son自己的属性
}
getSum(){//子类Son自己的成员方法
let l=super.getSum()+this.z
console.log(l)
}
}
var s1=new Son(10,20,30)
s1.getSum()
由上图运行结果可知,因为子类的方法和父类的方法同名,调用同名的方法时,子类的方法会覆盖父类。
(6)练习
请编码实现动物世界的继承关系: 动物(Animal)具有行为:吃(eat)、睡觉(sleep)
动物包括:兔子(Rabbit)、老虎(Tiger)、鱼(Fish) 这些动物吃的行为各不相同(兔子吃草,老虎吃肉、鱼吃浮游生物);睡觉的行为也不一样。
class Animals{//抽象的基类,不能实例化
constructor(){
}
Eat(){
}
Sleep(){
}
}
class Rabbit extends Animals{
constructor(){
super()
}
Eat(){
console.log('兔子吃草')
}
Sleep(){
console.log('兔子在睡觉')
}
}
class Tiger extends Animals{
Eat(){
console.log('老虎吃肉')
}
Sleep(){
console.log('老虎在睡觉')
}
}
class Fish extends Animals{
Eat(){
console.log('鱼再吃浮游植物')
}
Sleep(){
console.log('鱼在睡觉')
}
}
var s1=new Rabbit()
s1.Eat()
s1.Sleep()
var s2=new Tiger()
s2.Eat()
s2.Sleep()
var s3=new Fish ()
s3.Eat()
s3.Sleep()
三、ES5中创建类
1、创建构造函数,在构造函数中定义属性和方法
(1)构造方法名就是类名,更推荐ES6创建方式
(2)和使用class创建类的区别
a、构造方法创建类:属性和方法都在构造方法中定义
b、使用class创建类:属性在构造方法中定义,方法在构造方法外
☀️ES5定义类例子,便于理解:
function Person(name,sex){//构造函数,name,sex是构造函数的参数
this.name=name//this.name中的name是Person类的属性
this.sex=sex//this.sex中的sex是Person类的属性
this.sleep=function(){//sleep是成员方法,函数表达式
console.log(this.name+'我在睡觉')
}
this.eat=function(){//eat是成员方法,函数表达式
console.log(this.name+'我在吃饭')
}
}
//创建对象
var p1=new Person('黄蓉','女')
console.log('姓名:',p1.name)
console.log('性别:',p1.sex)
p1.sleep()
p1.eat()
☀️ES6定义类例子,便于理解:
class Person{
constructor(name,sex){
this.name=name
this.sex=sex
}
sleep(){//sleep是成员方法,函数表达式
console.log(this.name+'我在睡觉')
}
eat(){//eat是成员方法,函数表达式
console.log(this.name+'我在吃饭')
}
}
//创建对象
var p1=new Person('黄蓉','女')
console.log('姓名:',p1.name)
console.log('性别:',p1.sex)
p1.sleep()
p1.eat()
// 使用类表达式创建类
var Person=class{
constructor(name,sex){
this.name=name
this.sex=sex
}
sleep(){//sleep是成员方法,函数表达式
console.log(this.name+'我在睡觉')
}
eat(){//eat是成员方法,函数表达式
console.log(this.name+'我在吃饭')
}
}
//创建对象
var p1=new Person('黄蓉','女')
console.log('姓名:',p1.name)
console.log('性别:',p1.sex)
p1.sleep()
p1.eat()
四、ES5类的继承
ES6之前并没有给我们提供 extends 继承。我们可以通过构造函数+原型对象模拟实现继承,被称为组合继承。
1、call()
调用这个函数, 并且修改函数运行时的 this 指向
fun.call(thisArg, arg1, arg2, ...) // thisArg :当前调用函数 this 的指向对象
//arg1 , arg2 :传递 的其他 参数
call()的两个作用:
(1) call() 可以调用函数
fn.call();
(2)call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(第一个改变this指向,后面是普通实参)
// call 方法
function fn(x, y) {
console.log('我想喝手磨咖啡');
console.log(this);
console.log(x + y);
}
var o = {
name: 'andy'
};
// fn();
// 1. call() 可以调用函数
// fn.call();
// 2. call() 可以改变这个函数的this指向 此时这个函数的this 就指向了o这个对象
fn.call(o, 1, 2);
2、 借用构造函数继承父类型属性
核心原理: 通过 call() 把父类型的 this 指向子类型的 this ,这样就可以实现子类型继承父类型的属性。
// 父类
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
// 子类
function Student(name, age, sex, score) {
Person.call(this, name, age, sex); // 此时父类的 this 指向子类的 this,同时调用这个函数
this.score = score;
}
var s1 = new Student('张三', 18, '男', 100);
console.log(s1);
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
var son = new Son('刘德华', 18, 100);
console.log(son);
3、借用原型对象继承父类型方法
一般情况下,对象的方法都在构造函数的原型对象中设置,通过构造函数无法继承父类方法。
核心原理:
① 将子类所共享的方法提取出来,让子类的 prototype 原型对象 = new 父类 ()
② 本质:子类原型对象等于是实例化父类,因为父类实例化之后另外开辟空间,就不会影响原来父类原型对象
③将子类的 constructor 从新指向子类的构造函数.
// 借用父构造函数继承属性
// 1. 父构造函数
function Father(uname, age) {
// this 指向父构造函数的对象实例
this.uname = uname;
this.age = age;
}
Father.prototype.money = function() {
console.log(100000);
};
// 2 .子构造函数
function Son(uname, age, score) {
// this 指向子构造函数的对象实例
Father.call(this, uname, age);
this.score = score;
}
// Son.prototype = Father.prototype; 这样直接赋值会有问题,如果修改了子原型对象,父原型对象也会跟着一起变化
Son.prototype = new Father();
// 如果利用对象的形式修改了原型对象,别忘了利用constructor 指回原来的构造函数
Son.prototype.constructor = Son;
// 这个是子构造函数专门的方法
Son.prototype.exam = function() {
console.log('孩子要考试');
}
var son = new Son('刘德华', 18, 100);
console.log(son);
console.log(Father.prototype);
console.log(Son.prototype.constructor);
五、类的静态成员和实例成员:又称为类成员
1、实例成员(成员变量和成员方法):属于类的实例(具体的对象)
2、静态成员:包括静态的成员变量和静态的成员方法。不属于某个具体的对象,而是类的所有对象共享的成员。是通过类名(或构造方法名)访问的成员
☀️举个例子,便于理解:
//ES5
function Student(s,name,sex){
// this.school=school //this.school是实例对象,school定义成一个实例对象是不合适的,因为学校属于共享的,并不是一个人独有的。
Student.school=s //school是静态的成员变量,Student的所有对象共享该成员。
this.name=name//this.name是实例对象
this.sex=sex//this.sex是实例对象
this.display =function(){
let str ='学校:'+Student.school+'\n姓名:'+this.name+'\n性别:'+this.sex
console.log(str)
}
Student.sayHello=function(){ //静态成员方法
console.log('Hello'+Student.school)
}
}
var s1 = new Student('西安交通大学','周瑜','男')
var s2 = new Student('西安交通大学','小乔','女')
var s3 = new Student('西安交通大学','黄盖','男')
Student.school='西安邮电大学'
s1.display()
s2.display()
s3.display()
Student.sayHello()
强调:在ES6中定义静态成员方法时必须用static关键字修饰.
☀️例子如下:
//ES6
class Student{
constructor(s,name,sex){
// this.school=school //this.school是实例对象,school定义成一个实例对象是不合适的,因为学校属于共享的,并不是一个人独有的。
Student.school=s //school是静态的成员变量,Student的所有对象共享该成员。
this.name=name//this.name是实例对象
this.sex=sex//this.sex是实例对象
}
display(){
let str ='学校:'+Student.school+'\n姓名:'+this.name+'\n性别:'+this.sex
console.log(str)
}
static sayHello(){ //在ES6定义类的静态成员方法
console.log('Hello'+Student.school)
}
}
var s1 = new Student('西安交通大学','周瑜','男')
var s2 = new Student('西安交通大学','小乔','女')
var s3 = new Student('西安交通大学','黄盖','男')
Student.school='西安邮电大学'
s1.display()
s2.display()
s3.display()
Student.sayHello()
六、类的本质
-
class本质还是function.
-
类的所有方法都定义在类的prototype属性上
-
类创建的实例,里面也有__proto__ 指向类的prototype原型对象
4.所以ES6的类它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
5.所以ES6的类其实就是语法糖.
- 语法糖:语法糖就是一种便捷写法. 简单理解, 有两种方法可以实现同样的功能, 但是一种写法更加清晰、方便,那么这个方法就是语法糖
// ES6 之前通过 构造函数+ 原型实现面向对象 编程
// (1) 构造函数有原型对象prototype
// (2) 构造函数原型对象prototype 里面有constructor 指向构造函数本身
// (3) 构造函数可以通过原型对象添加方法
// (4) 构造函数创建的实例对象有__proto__ 原型指向 构造函数的原型对象
// ES6 通过 类 实现面向对象编程
class Star {
}
console.log(typeof Star);
// 1. 类的本质其实还是一个函数 我们也可以简单的认为 类就是 构造函数的另外一种写法
// (1) 类有原型对象prototype
console.log(Star.prototype);
// (2) 类原型对象prototype 里面有constructor 指向类本身
console.log(Star.prototype.constructor);
// (3)类可以通过原型对象添加方法
Star.prototype.sing = function() {
console.log('刘欢');
}
var d = new Star();
console.log(d);
// (4) 类创建的实例对象有__proto__ 原型指向 类的原型对象
console.log(d.__proto__ === Star.prototype);