首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >帆布绘画/动画

帆布绘画/动画
EN

Code Review用户
提问于 2011-04-18 04:12:47
回答 3查看 967关注 0票数 9

我正在努力学习如何编写好的、干净的和高效的JavaScript。我认为自己充其量是JavaScript的新手。如有任何意见,敬请见谅。

为了成为一个更好的JavaScript程序员,我尝试用HTML5 5‘S新的画布元素编写一个简单的JavaScript动画。从功能上讲,当单击画布时,在画布上添加一个新的圆圈(或球),并开始移动到画布的右下角。当球撞到墙上或与另一个球相撞时,x坐标和y坐标的移动速度是倒转的。

修改this对象是个好主意吗?例如,下面的模式有意义吗?

代码语言:javascript
复制
function Point(I) {
    this.x = I.x;
    this.y = I.y;
    this.z = I.z;
    return this;
}

为什么我不应该发送一个对象并从输入对象中提取变量呢?我喜欢创建的语法,但是这个模式有什么问题吗?

代码语言:javascript
复制
new Point({
    x: 10,
    y: 30
});
new Point({
    x: 50,
    y: 25
    z: 3
});

全码

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Ball Collision</title>
<script src="js/modernizr-1.7.js"></script>
<script>
    window.onload = function () {
        //"use strict";

        // Source: http://www.html5rocks.com/tutorials/canvas/notearsgame/
        Number.prototype.clamp = function(min, max) {
            return Math.min(Math.max(this, min), max);
        };


        if (Modernizr.canvas) {
            var canvas = document.createElement('canvas'),
                ctx = canvas.getContext('2d');


            canvas.width = 500;
            canvas.height = 375;
            canvas.style.border = "3px solid red";

            function Point(I) {
                console.log("Point created");
                this.x = I.x;
                this.y = I.y;
                this.z = I.z;
                return this;
            }

            function Ball(I) {
                console.log("Ball created");

                var SPEED_LIMIT = 10;

                this.point = new Point(I.point);
                this.radius = (I.radius > 10 ? I.radius : 10);
                this.color = I.color;
                this.speed = new Point(I.speed);
                this.collision = false;
                this.mass = I.mass || 1;

                this.draw = function() {
                    this.point.x = this.point.x.clamp(0 + this.radius, canvas.width - this.radius);
                    this.point.y = this.point.y.clamp(0 + this.radius, canvas.height - this.radius);
                    ctx.beginPath();
                    ctx.arc(this.point.x, this.point.y, this.radius, 0, Math.PI*2, false);
                    ctx.closePath();
                    ctx.fillStyle = this.color;
                    ctx.fill();
                };

                this.update = function() {
                    this.clear();

                    if ( this.point.x <= this.radius || this.point.x >= (canvas.width - this.radius)) {
                        this.speed.x *= -1;
                    }

                    if ( this.point.y <= this.radius || this.point.y >= (canvas.height - this.radius)) {
                        this.speed.y *= -1;
                    }

                    if ( this.speed.x > SPEED_LIMIT ) {
                        this.speed.x = SPEED_LIMIT;
                    }
                    if ( this.speed.x < SPEED_LIMIT * -1 ) {
                        this.speed.x = SPEED_LIMIT * -1;
                    }
                    if ( this.speed.y > SPEED_LIMIT ) {
                        this.speed.y = SPEED_LIMIT;
                    }
                    if ( this.speed.y < SPEED_LIMIT * -1 ) {
                        this.speed.y = SPEED_LIMIT * -1;
                    }

                    this.point.x += this.speed.x;
                    this.point.y += this.speed.y;
                };

                this.clear = function() {
                    ctx.beginPath();
                    ctx.arc(this.point.x - 1, this.point.y - 1, this.radius + 2, 0, Math.PI*2, false);
                    ctx.closePath();
                    ctx.fillStyle = "#fff";
                    ctx.fill();
                };

                return this;
            }

            var balls = [];

            function handleCollisions() {
                function collide(ball1, ball2) {
                    ball1.speed.x *= -1;
                    ball2.speed.x *= -1;
                    ball1.speed.y *= -1;
                    ball2.speed.y *= -1;
                }


                function checkCollisions() {
                    var i, j, a, b;

                    for ( i = 0; i < balls.length; i++ ) {
                        for ( j = i+1; j < balls.length; j++ ) {
                            a = balls[i];
                            b = balls[j];

                            var dx = Math.abs(a.point.x - b.point.x);
                            var dy = Math.abs(a.point.y - b.point.y);
                            var d = (dx*dx)+(dy*dy);
                            var r = (a.radius + b.radius) * (a.radius + b.radius);

                            if ( d < r && ( !a.collision || !b.collision ) ) {
                                a.collision = true;
                                b.collision = true;
                                collide(a, b);
                            } else if ( d > r ) {
                                a.collision = false;
                                b.collision = false;
                            }                           
                        }
                    }
                }

                checkCollisions();
            }

            function update() {
                balls.forEach(function(ball) {
                    ball.update();
                });

                handleCollisions();
            }

            function draw() {
                balls.forEach(function(ball) {
                    ball.draw();
                });
            }

            var FPS = 30;
            var loop = setInterval( function() {
                update();
                draw();
            }, 1000/FPS);

            canvas.onclick = function(e) {              
                var radiusAndMass = Math.floor(Math.random() * 50) + 1;
                var speed = Math.floor(Math.random() * 10) + 1;

                balls.push(new Ball({
                    point: {
                        //x: Math.floor(Math.random() * canvas.width),
                        //y: Math.floor(Math.random() * canvas.height),
                        x: e.offsetX,
                        y: e.offsetY
                    },
                    color: "#"+((1<<24)*Math.random()|0).toString(16),
                    radius: radiusAndMass,
                    mass: radiusAndMass,
                    speed: {
                        x: speed,
                        y: speed
                    }
                }));
            };


            document.getElementById('canvas-container').appendChild(canvas);
        }
    };
</script>   
</head>
<body>
<div id="wrap">
    <div id="canvas-container">

    </div>
</div>
</body>
</html>
EN

回答 3

Code Review用户

发布于 2014-06-12 04:05:17

修改这个对象是个好主意吗?例如,下面的模式有意义吗?函数点(I){ this.x = I.x;this.y = I.y;this.z = I.z;返回此;}

是也不是。在构造函数中修改this是正常的,但是您在这里所做的事情是不寻常的。像这样的事情会更常见:

代码语言:javascript
复制
function Point(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
}

对于这个参数列表,我们传递的内容非常清楚(3D坐标);对于I,除非在构造函数中读取代码,否则就不清楚了。这里没什么大不了的,但是有了Ball,我们就不知道I应该包含什么,除非我们读取了整个构造函数。

没有理由使用return this;您正在使用new调用它,该表达式的结果将是构造的对象。函数不需要返回任何内容。

为什么我不应该发送一个对象并从输入对象中提取变量呢?我喜欢创建的语法,但是这个模式有什么问题吗?

它没有什么问题,但是由于您的Point不做任何事情(即它没有函数属性,只有xyz),那么您最好忘记Point,如果您将对象文本传递给Point构造函数,那么只使用对象文本。换句话说,您正在创建一个对象文本,然后将它的所有属性复制到一个恰巧被称为"Point“的东西中,但除此之外,它还具有与传递给构造函数的对象完全相同的功能。为什么要多走一步?

使用Ball,您应该将函数属性附加到Ball.prototype,而不是在构造函数中分配它们。每个Ball对象没有理由拥有自己的一组单独的函数属性,这样做会带来额外的开销。换句话说,使用代码someBall.draw != anotherBall.draw,但是如果所有的球都引用相同的draw函数属性,那么效率就会高得多。

票数 7
EN

Code Review用户

发布于 2014-06-13 21:01:55

我不认为任何一种方法(对象与值)都有很强的理由,但我将列出这两种方法的所有优点/缺点,以便您选择适合您的方法:

传入您所描述的结构的一个对象(function Point(I))

+从旧实例或兼容对象( x、y和z)创建新实例非常简单:new Point(oldPoint)new Point(thingThatQuaksLikeAPoint)*

-从数字中创建新实例变得乏味和烦人(并且会产生垃圾(在某些情况下这可能是一个问题(对于数字处理/在缓慢硬件上的实时应用程序/糟糕的JS实现来说真的很糟糕))。new Point({ x: 1, y: 2, z: 3 }) vs new Point(1, 2, 3),或者,以下面的函数为例:

代码语言:javascript
复制
function add(P1, P2) {
    var newX = P1.x + P2.x;
    var newY = P1.y + P2.y;
    var newZ = P1.z + P2.z;

    return new Point({ x: newX, y: newY, z: newZ });
    
    // why do I have to wrap my numbers it in an object?!? :(
    // new Point(newX, newY, newZ); is so much more to the point!
}

- --构造函数可以访问比它需要的更多的东西--一般来说,在应用程序中设置围栏并保证某些边界不被跨越是很好的。如果您传入一个完整的对象,那么由于错误或诱惑,您可能会修改传递对象的状态(这会导致非常微妙的错误)。

传入单个组件(function Point(x, y, z))

+从数字中创建新实例是琐碎的、甜蜜的、简短的--就像new Point(1, 2, 3)一样容易

-如果调用其他点,那么每次new Point(otherPoint.x, otherPoint.y, otherPoint.z)new Point(otherPoint)时提取x、y和z都会变得单调乏味。

那么为什么不支持这两种方式呢?!

代码语言:javascript
复制
function Point(x, y, z) {
    if (arguments.length === 1) {
        // we got only one argument - it's a Point!
        var that = x; // just aliasing
        this.x = that.x;
        this.y = that.y;
        this.z = that.z;
    } else {
        // let's presume there can be only these 2 cases
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

这可能会慢一点,但这是值得的,海事组织--它使创造点变得轻而易举;)

我确实看到了一些代码,它们可以在您粘贴的示例中得到改进。如果您希望编写漂亮、干净的JS,那么请考虑查找原型是如何工作的。还要了解为什么requestAnimationFrame比setTimeout好得多(setTimeout比setInterval好)。此外,您可以使用rgb(1, 2, 3)作为有效的颜色,而不是#010203

*见鸭型

票数 5
EN

Code Review用户

发布于 2011-04-19 14:33:12

我认为这种模式很好。它具有“命名参数”的优点,如果使用不同的参数集调用某个函数/构造函数,它非常可读性强,并且可能非常有用。

我建议的唯一方法是为参数使用另一个名称,因为大写字母通常用于构造函数:

代码语言:javascript
复制
function Point(p) {
    this.x = p.x;
    this.y = p.y;
    this.z = p.z;
    return this;
}
票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/1940

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档