完成昨天的演示後,也许有人会觉得,处理落叶动画的流程很简单,就是「让落叶自然落下」然後「在画布上绘制落叶」两步骤而已,然而实作上总是比较复杂,可能还会有「被风吹起」、「被车撞到」、「被蜘蛛丝捕获」等等的不同状态,也有可能是同时发生,概念上像是这样:
随着功能越来越丰富,我们会逐渐开始遇到程序码维护上的困难,还有一个状况是,如果我们要做一个暂停效果呢?大概很直觉就会写成这样:
function MouseAnime(){
if(paused == false){
// ......
// 一大堆程序码
// ......
}
else{
// 在画布上绘制不动的落叶
}
}
这样写最大的问题是,中间程序码越来越多时,逐渐阅读困难,为了让它一目了然,就会需要包成几个函式/方法:
function 自然落下方法(){ ...... }
function 被风吹起方法(){ ...... }
function 被车撞到方法(){ ...... }
function 被蜘蛛网捕获方法(){ ...... }
fucntion MouseAnime(){
if(paused == false){
自然落下方法();
被风吹起方法();
被车撞到方法();
被蜘蛛网捕获方法();
}
else{
// 在画布上绘制不动的落叶
}
}
现在看起来乾净多了,但另一个问题开始出现,所有的变数(包含以上function)都在几乎都在最外层,乍看之下命名还算清楚,但是这些变数都暴露在外,这样的潜台词就相当於说,这些方法都是公开的,没有限制谁都可以使用,此时若有数十个上百个变数都在同一个范畴下,很快就会在读程序码时开始困惑「被车撞到方法是给谁用的?」或「这个那个是干嘛的」,必须Ctrl+F来回比对才知道这些变数是设计给谁用的,因为「不知道这些变数」属於谁。
要解决这个问题,首先我们要先理解一个苹果的本质,为此,JS引入了一个概念,称之为「物件」,可以看到,一个红色饱满的富士苹果长这个样子:
let Apple = {
'variety': 'Fuji',
'color': 'red',
'taste': 'juicy'
}
console.log(Apple.variety); // 'Fuji'
console.log(Apple.color); // 'red'
console.log(Apple.taste); // 'juicy'
反过来讲,也可以後天塑造而成:
let fakeApple = {};
fakeApple.variety = 'Fuji';
fakeApple.color = 'red';
fakeApple.taste = 'juicy';
console.log(fakeApple.variety); // 'Fuji'
console.log(fakeApple.color); // 'red'
console.log(fakeApple.taste); // 'juicy'
像是这个假货,看起来很好吃,实际上只是伪装成富士苹果
让我们检查一下刚刚的苹果是否受地心引力制约:
let Apple = {
'variety': 'Fuji',
'color': 'red',
'taste': 'juicy',
'gravity': 9.8,
'velocity': 0,
'height': 100,
'fall': function(){
this.velocity+= this.gravity*0.016;
this.height-= this.velocity;
console.log(this.height);
},
'manner': 'bad'
}
for(let N=0; N<40; N++){
Apple.fall();
}
console.log(Apple.manner) // bad
刚刚提到value可以是任何型别,也包括了函式!因此我们可以用Apple.fall呼叫它,是Apple专用的函式呢!还可以透过this呼叫自己来取得自己的其他属性。
格式相当於常见的函式命名法let fall = function(){......}
原来,地心引力确实存在,至於...有没有掉到牛顿头上呢?这颗苹果这麽没礼貌,也是有可能故意跑去砸牛顿,然後在他头上爆开,我只能说:不排除这个可能性!
确实,我们可以立刻开始着手修改我们昨天设计的落叶,改成像上面苹果的格式一样,然而,这边会一个问题,如果我今天要用两片叶子怎麽办,总不会同样的格式再写第二遍吧?然後要N片就跑N遍回圈...唉呀!用想的就累,其实,我们还缺少一个重要的步骤,就是替它设计一个建构式(Constructor)
概念上就是,我们可以设计一个物件产生器,然後需要落叶的时候,就用产生器创造一个出来。咦?鸠豆麻蝶,这段叙述有没有觉得有点熟悉呢?是不是跟这句话很像呢:「我们可以设计一个阵列产生器,然後需要阵列的时候,就用产生器创造一个出来」,写成代码如下:
let myGirlfriend = new Array(10);
let myMoney = new Number(1000000);
这样的写法是不是很熟悉呢?其实我们之所以能使用阵列的各种方法诸如splice、reduce、forEach,便是因为有这个称之为「Array」的建构式,它把阵列常用的方法全都定义好了,因此myGirlfriend就会有很多方法可以用,像是
三人行刚刚的Apple有fall这个方法可以用一样。
那麽,建构式要怎麽写呢?最简易的形式如下,当中的this所指涉的对象,是当你使用建构式时,它会回传的对象return this;
,那麽我们就是从一开始设计苹果时写apple.key=value
,改写成this.key=value
,就能成为一个模板,比如,我们拿昨天的落叶动画来修改,可以这样写:
function leafMaker(){
this.timestamp = Date.now();
this.lifeCycle = 6;
//......
// 省略(宽高、起始点、角速度等等昨天写的所有参数)
//......
this.fall = function(context ,timestamp){
let deltaTS = (timestamp - this.timestamp) / 1000;
if(this.lifeCycle > deltaTS){
let rotateNow = this.rotateTheta + this.rotateOmega * deltaTS;
let revolveNow = this.revolveTheta + this.revolveOmega * deltaTS;
let cursorX = this.originX + 500 * Math.sin(revolveNow);
let cursorY = this.originY + 200 * Math.sin(revolveNow)
+ 100 * deltaTS;
context.save();
context.translate(cursorX, cursorY);
context.rotate(rotateNow);
context.drawImage(leafImg, -this.width/2, -this.height/2, this.width, this.height);
context.restore();
}
}
// 这边JS省略了return this的写法
}
还有一个小重点是,这个leafMaker只是一个建构式,并没有leafMaker.fall这样的方法,就像平常都是用new Array建构式来建立阵列资料,那麽你就不会期待可以用Array.splice一样。
有了建构式後,我们可以在滑鼠点击的当下,产生一个落叶物件,并赋值给名为leaf的变数:
let leaf;
canvas.addEventListener('click', SetMouse);
function SetMouse(e){
leaf = new leafMaker();
}
原本初始化落叶的代码都塞在SetMouse里面,现在变得很乾净
并且在每一侦的动画循环,只需要这样写:
function MouseAnime(){
Clear(context);
leaf.fall(context);
}
原本计算落叶的方程序都塞在MouseAnime里面,现在变得很乾净
说到这,我想大家应该稍微明白了物件的魅力在哪里了吧?原本落叶的相关程序码四散各处:
这时候,若想要实现一开始的流程图绘制的各种落叶效果(被风吹起等等),是不是方便许多了呢?
还记得一开始谈到属於谁的概念吗?在人类世界,看待与理解每一件事情,都会一层又一层,并且和其他类似的产生关连,想到苹果,就会联想到一些属性,像是「长在树上」、「会掉到地上」、「表面有一层蜡」,接着又会联想到香蕉也长在树上、葡萄、蓝莓表皮也有果蜡,也因其错综复杂的关系,像是生物界就被归类为了「界门纲目科属种」。
要如何实现一环扣一环和共用相同的属性,便是物件诞生之初的使命和意义,接下来几篇我们将会前进到更深入的环节,试想,所有在地球上的物体都会受到地心引力的影响,也就是说,这是一个共用的属性,因此,我们不需要把重力公式写在Apple、Banana、Lemon每个物件的里面,能达成这一目的概念就叫做继承。
什麽是继承呢?请期待本章节後续的文章!
<<: Day_05 : 让 Vite 来开启你的Vue 之 前进Vite
漏洞利用 利用侦测到的现有漏洞取得初始控制权 上图取自台科大资安社课教材 1. Exploit-db...
今天我们用实际的例子来练习各种 RxJS operators 的组合运用!在一般的应用程序里面,资料...
Python实作 Request发送 如果你的Python环境没有requests模组 pip in...
HTML的注解是使用 <!-- --> 而CSS的注解是使用 /* */ 浏览器预设样式...
灯光绕圈圈 ( 数字函式 ) 教学原文参考:灯光绕圈圈 ( 数字函式 ) 这篇文章会介绍如何使用「函...