extends、 static、 super
在ES6中extends关键字,可以很方便的实现子类继承父类,同时static关键字可以为类指定静态方法,以及super关键字可以在子类继承父类的时候,方便的去调用父类原型身上的方法或者静态方法。
本节我们将通过具体的例子来阐述着三个关键字如何使用?
首先我们在html中写了一个简单的canvas画布以及一些简单的样式阴影,这样我们可以很方便的看到这个画布在文档中的位置。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title></title>
<style>
canvas {
box-shadow: 2px 2px 12px rgba(0, 0, 0, 0.5);
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./js/bundle.js"></script>
</body>
</html>
如图所示:
需求:
我们要写一个小球的类,通过实例化这个小球,将小球绘制在画布当中。
并且让小球做一个自由落体的运动。
// 1、获取这个画布
const canvas = document.querySelector('#canvas');
// 2、获取一下这个画布对应的绘图环境
const ctx = canvas.getContext('2d');
// 3、初始化两个常量,来指定这个画布的分辨率
const w = canvas.width = 600;
const h = canvas.height = 400;
// 4、现在我们通过class关键字声明一个Ball小球的类
class Ball {
/* 5、首先指定一下它的构造函数constructor,
同时化初始化三个参数x,y,r,分别对应小球的横坐标、纵坐标、以及小球的半径 */
constructor(x, y, r){
this.x = x;
this.y = y;
this.r = r;
/* 6、接下来再给小球添加一个color属性,让它随机的等于一个rgb值
这里需要用到一个产生随机数的方法,但是这个方法和绘制小球实际上并没有太多的关系,
所有我们把这个方法写成Ball类的静态方法,转至第7步
10、 接下来我们做一下字符串的拼接,这里我就使用Ball类下面的rpFn()函数获取随机数
使用两个~~号去掉小数部分,
*/
this.color = `rgb(${~~Ball.rpFn([55, 255])}, ${~~Ball.rpFn([55, 255])}, ${~~Ball.rpFn([55, 255])})`;
// 11、最后我们return this,这样方便在实例化的时候调到其它的方法
return this;
}
/* 12、 为了让小球绘制到画布当中,我们在写一个render方法
render方法接收一个参数,就是绘图的环境ctx
*/
render(ctx){
// 13、首先保存一下绘图环境
ctx.save();
// 14、绘制小球的坐标
ctx.translate(this.x, this.y);
// 15、指定一下小球的填充颜色
ctx.fillStyle = this.color;
// 16、起始一个新的路径
ctx.beginPath();
// 17、绘制小球的路径
ctx.arc(0, 0, this.r, 0, 2*Math.PI);
// 18、给小球填充颜色
ctx.fill();
// 19、恢复一下绘图环境
ctx.restore();
// 20、返回teturn this
return this;
}
/* 7、如何指定静态方法 前面我们已经提了,使用static关键字
这里只需要写上static,然后跟上方法的名字就可以了,这里叫rpFn
给它接收一个参数数组
使用它的时候,如:Ball.rpFn([1, 10]),那么它就可以返回一个1-10之间的随机数
*/
static rpFn(arr){ // Ball.rpFn([1, 10])
// 8、 接下来我们就来实现这个函数,首先声明两个变量
// 获取到这个数组的最大值和最小值,使用...作为扩展运算符
let max = Math.max(...arr),
min = Math.min(...arr);
// 9、返回一组固定区间的随机数 ,这样我们获取随机数的函数就写好了
return Math.random() * (max - min) + min;
}
}
/*
21、声明一个变量,实例测试一下
在坐标100,100的位置,绘制一个半径为30的小球
此时我们发现一个小球就绘制在画布当中了
*/
// const ball1 = new Ball(100, 100, 30).render(ctx);
/*
22、接下来我任然绘制一个小球在画布当中,并且让它做一个自由落体的运动。
那么我们可以肯定接下来绘制的这个小球依然有Ball所有的属性以及绘制
的render方法。
我们声明一个新的类,起名叫SuperBall,它可以先去继承Ball的类,
把它的属性和方法继承过来
*/
// 子类 继承 父类
class SuperBall extends Ball {
// 23、此处如果什么都不写,相当于复制了一个Ball类
// 24、接下来我们就给SuperBall指定一些它自己的属性和方法
// 首先写上它的构造函数constructor,这里面依然需要传入x,y,r三个参数
constructor(x, y, r){
/*
25、我们首先要继承x,y,r三个属性,
我们去调用一下父类的构造函数,发现报错了
*/
// Ball.call(this, x, y, r);
// 报错 找不到this,实际上通过class定义的类在ES6中它是不允许通过函数的方式去调用的,
// 也就是说这种写法本身就是错的
// this.color = 'red';
/*
26、那么如何在子类中调用父类的构造函数呢?
在ES6当中提供了一个新的关键字,super
super主要有两种用法
第一种:就是在子类继承父类的时候,
在子类的构造函数中可以当成一个函数去使用,
当它当作函数去使用的时候,就相当于调用了父类的构造函数
这里由于要继承x,y,r三个属性,所有要把这三个参数传进去
*/
super(x, y, r); //这个时候我们就发现一个小球就绘制在相同的位置了
/*
27、super(x, y, r);调用完成之后,实际上就相当于子类继承了父类构造函数当中的所有的属性,
在ES6中规定在没调用super()函数之前子类是没有自己的this的
*/
//纵向的速度vy
//28、也就是说当子类继承父类的时候不仅仅是继承属性,还会自动继承静态方法
this.vy = SuperBall.rpFn([2, 4]);
this.g = SuperBall.rpFn([0.2, 0.4]);
this.a = 0;
return this;
}
// 28、为了让小球动起来,我们再写一个move方法
move(ctx){
// super()在非构造函数中调用时会报错,super()只能在构造函数中调用
// super(); // 报错
this.y += this.vy;
this.vy += this.g;
let current = this.vy * -0.75;
if(this.y + this.r >= ctx.canvas.height){
this.y = ctx.canvas.height - this.r;
if(Math.abs(current - this.a) < 0.01) return false;
this.a = this.vy *= -0.75;
}
// 29、清除整个画布大小
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// 30、将绘图打印出来,这里render不是当作函数调用
// super的第二种使用情况:
// 如果在原型的方法里使用super,它实际上指向的是父类的原型对象
super.render(ctx);
return true;
}
};
// 我们发现一个小球就绘制在了相同的位置了
// const ball1 = new SuperBall(100, 100, 30).render(ctx);
/*
我们想鼠标点击哪里,哪里就出现一个小球,并且呈现自由落体运动
*/
let ball, timer;
canvas.onclick = function (e){
// 31、获取点击画布的鼠标坐标
let x = e.offsetX, y = e.offsetY;
// 通过Ball的静态方法来指定一个随机的半径
let r = ~~Ball.rpFn([25, 55]);
//33、为了每次点击值产生一个小球,故每次点击之前最画布进行一次清除
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// 32、接受一个实例对象
ball = new SuperBall(x, y, r).render(ctx);
// 33、调用一下小球掉下来的方法
ballMove();
}
function ballMove(){
// 34、用timer变量接收一下动画帧的返回值
timer = window.requestAnimationFrame(ballMove);
//35、如果ball.move(ctx)返回了一个false
//就清除这个动画帧
if(!ball.move(ctx)){
window.cancelAnimationFrame(timer);
}
}
我们做一下简单的总结回顾:
首先在Ball这个类中,我们通过static关键字来指定了一个镜静态方法,静态方法实际上就是挂载到类身上的方法,
接下来我们实现了用extends,用子类去继承父类,并且想给子类去指定一些额外的属性,首先需要通过super关键字调用一下父类的构造函数,这里面想继承那些属性就要把哪些属性当参数传到方法上,
接下来在子类的原型方法当中,如果是super()当作一个函数去调用的时候,就会报错,
此外,在构造函数当中在没使用super()之前是拿不到this 对象的,必须先调用super()才能调用子类的this对象。
接下来在原型的方法当中还有第二种情况,super可以当作一个对象去调用,它实际上指向的是父类原型的对象,并且在调用父类原型身上的方法的时候,会自动的绑定子类的this
子类继承父类 使用extends 关键字
为父类指定静态方法,使用 static 方法名字
super
- 在构造函数中可以当做一个函数来使用,相当于调用父类的构造函数
- 在原型方法中,可以当一个对象类使用,相当于父类的原型对象,并且会自动绑定this到子类上