Chapter2 - 重构完了 还是觉得物件很复杂吗?直接上图,就明白物件让你更轻松

前言

前天谈到如何避免程序码散落四处、维护困难,带大家改写了物件格式,过了两天,我突然想到,当时花太多篇幅在解释观念和举例子,结果没有实际把程序码秀给大家看,果然还是会有点难懂,对吧?於是我以改写後的程序码和改写前的程序码摆在一起,让大家更直接看看物件模型的优势在哪里。

落叶的建构式

这一整段代码是改写後,可以针对落叶动画修改和维护的地方,并且在下方的五张比较图中,分别采用部分截图,拿去做比较,因此先上完整代码:

function leafMaker(x, y, lifeTime){
    // 基本属性
    this.pointX = x;
    this.pointY = y;
    this.img = leafImg;
    this.width = 200;
    this.height = 200 * this.img.height / this.img.width;
    this.isWaitng = false;

    // 落叶跟随滑鼠的属性
    this.originX = x;
    this.originY = y;
    this.period = 90;
    this.timer = 0;

    // 落叶自然落下的属性
    this.beginX = x;
    this.beginY = y;
    this.timestamp = Date.now();
    this.lifeTime = lifeTime;
    this.rotateTheta = 0 / 180 * Math.PI;
    this.rotateOmega = 40 / 180 * Math.PI;
    this.revolveTheta = 0 / 180 * Math.PI;
    this.revolveOmega = 90 / 180 * Math.PI;

    this.fall = function(context, dT){
        let rotateNow = this.rotateTheta + this.rotateOmega * dT;
        let revolveNow = this.revolveTheta + this.revolveOmega * dT;

        let A = Math.sin(revolveNow);
        let B = Math.cos(revolveNow);
        let C = Math.sin(revolveNow * 1.5);
        let D = Math.cos(revolveNow * 1.5);
        this.pointX = this.beginX + 500 * A;
        this.pointY = this.beginY + 200 * C
                                    + 100 * dT;
        if(this.img.complete){
            context.save();
            context.translate(this.pointX, this.pointY);
            context.font = '32px IBM Plex Sans Arabic';
            context.strokeStyle = 'rgba(179, 198, 213, 1)';
            let ts = Math.floor((this.lifeTime - dT)*10)/10;
            context.textAlign = 'center';
            context.strokeText(ts , 0, -150);
            context.rotate(rotateNow);
            context.drawImage(this.img, -this.width/2, -this.height/2, this.width, this.height);
            context.restore();
        }
    }
    this.Refollow = function(frames){
        this.originX = this.pointX;
        this.originY = this.pointY;
        this.timer = frames;
        this.period = frames;
    }
    this.follow = function(context, targetX, targetY){
        let dX = targetX - this.originX;
        let dY = targetY - this.originY;
        if(this.timer > 0){
            let t = this.timer;
            let p = this.period;
            let linear = 1/p;
            let easeout = Math.pow(t/p, 2) - Math.pow((t-1)/p, 2);
            let easein = Math.pow(1 - (t-1)/p, 2) - Math.pow(1 - t/p, 2);
            let a = input.linear;
            let b = input.easein;
            let c = input.easeout;
            this.pointX+= (a * linear + b * easein + c * easeout) * dX;
            this.pointY+= (a * linear + b * easein + c * easeout) * dY;
        }
        else{
            this.originX = this.pointX;
            this.originY = this.pointY;
        }
        context.drawImage(this.img, this.pointX-this.width/2, this.pointY-this.height/2, this.width, this.height);
        this.timer--;
    }
}

用物件方法(左) vs 单纯用变数(右)

1.初始变数定义

首先从初始值来看,并没有太大的不同,比较不明显的好处就是,每一个落叶的相关变数都是它的属性,比如说座标X就要用leaf.originX,可以一目了然,知道这个变数属於leaf,相比之下,右边的originX的名称无法和落叶产生联系,有机会让人混淆。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197Na5cX231W6.jpg

2.动画框架(流程)

接着我们从框架去看这件事,因为在阅读程序码时,一定从流程步骤开始,去了解「动画会怎麽循环?」以及「步骤分别有哪些?」,因此这个区域的可读性相当重要,我们可以看到左侧位於32-47行,内容相当简洁,另一方面单纯用变数的做法,右侧却占据了32-91行,阅读上较花时间,虽然可以再包成一个函式拉出去写,然而,有一部分的落叶动画被写在这里面,未来每每要修改落叶的动画,就必须到框架这一个区块的程序码,寻找它,会有额外的搜寻成本。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197lbTwQTDcqQ.jpg

3.自然落下方法

这边,我们可以看到,左侧的方法是紧接着初始变数定义後,查找方便,而右侧是写在刚刚说的「动画框架」里面,相对凌乱,即使如刚刚所说的再包成一个函式,若要查找初始变数的定义,还得去翻开另一份js档案。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197ZeB8HPoWml.jpg

4.跟随滑鼠方法

同上所述,左侧的方法还是在物件的定义里面,占据优势
https://ithelp.ithome.com.tw/upload/images/20210919/20135197w3WPlx64pM.jpg

5.事件监听

事件监听也是流程的一环,在UX的阶段可能去探讨User story、设计functional map等等,会有很丰富的使用者的体验等着去处理,因此对於流程的设计,若能一眼看出每个部件代表的意义,可以避免浪费时间在探讨细节。左侧可以看到mousemove对应了leaf.Refollow,而click又对应了new leafmaker,相当简洁;而右侧只看到了一大串对於变数的赋值。
https://ithelp.ithome.com.tw/upload/images/20210919/20135197U9zwx8atLy.jpg

结论

左侧相当漂亮的利用物件的设计,把落叶的相关程序码集中在一起,还保留了扩充的弹性;而右侧把落叶的动画分别散布到各处,每次要修改,就要一次打开三个文件,可谓图穷匕见,更不用说现在还只是一个落叶、两种动画,未来如果有一个角色、两个宠物、三个道具、四种动画,不知道右边的程序码会有多乱了!

虽然说是重构,不过我简化了滑鼠的监听事件(取消长压的功能),主要功能都一样,可参考:
https://jerry-the-potato.github.io/Chapter2-demo2-object/

後记

至此,第二章顺利落幕了,其实一开始就带大家用物件撰写程序码也不是不行,只是过往的经验告诉我,一开始就给予太多,只会是揠苗助长,毕竟写程序响往的是一种自由,若要求大家从物件学起,会成为一种限制,结果就是大家背下物件的用法,却不知道这麽做有什麽意义,还觉得很麻烦,这样也是有可能的。

很多人写文章都认为他懂得你也懂,是有预设条件的,当然这样也没问题,毕竟就是程序领域的彼此作交流,只是对於入门者来说,就不太友善了,在我的自学阶段,也是中间某个时候开始,我才逐渐知道可以去看mdn文件,学习读文件的报酬率更大,因为他会预设你还不太懂,讲解得清楚又相对容易,如果看不懂,建议去问人,虽然没有人教怎麽读文件,但是问着问着,就会找到方法了!

这10天以来也不确定最後我能帮到谁,也许,是那个,花时间复习官方文件和仿间说法的我自己吧!


<<:  Day12-"二维阵列"

>>:  Day4 Redis组态档设定-GENERAL

02 | WordPress 编辑器的进化起源,认识「传统编辑器」

传统编辑器的作用,就是把 HTML 转换为我们所见的网页的过度性产品 Classic Editor...

Day 8 MITRE ATT&CK for ICS

MITRE ATT&CK 纪录资安攻击与情资分享资料库,可先参考从红队角度看 MITRE ...

Day19 该如何发问问题?

大家好,我是乌木白,今天想和大家聊聊,如果在该如何去发问问题? 遇到问题该怎麽办? 在现今网路发达...

安装防盗系统的重要性

今天,我们来探讨一下安装防盗系统的重要性吧。 在疫情期间,仍然有不入室盗窃的罪状发生。为了保障人身及...

[Day26] 求值策略

Call by Value 传值 Call by Reterence 传参照 Call by Sh...