四、运算符和变量

在第 12 章中,我们学习了可以在 JavaScript 中使用的变量和数学运算。在这一章中,我们将运用这些知识。

设置

让我们首先创建几个形状来处理一些东西。使用ellipserect函数,让我们创建一个大致类似于手推车的形状(列出 4-1 和图 4-1 )。

A462229_1_En_4_Fig1_HTML.jpg

图 4-1

Output of Listing 4-1

function setup() {
        createCanvas(800, 300);
}

function draw() {
        background(220);

        ellipse(100, 200, 50, 50); // left wheel
        ellipse(200, 200, 50, 50); // right wheel
        rect(50, 160, 200, 20) // cart
}

Listing 4-1Creating a cart using 
rect and ellipse

functions

看着我们在图 4-1 中的草图,我对它的位置并不完全满意。我现在希望我们把它画得更靠右一些。现在移动形状意味着我们需要增加每个形状函数的 x 位置参数的值。

让我们假设我们想给所有这些指定 x 位置的数字加上 150。我们可以试着在脑子里算算,然后把结果输入进去,但幸运的是,我们可以用 JavaScript 轻松地进行数学运算。不用输入加法的结果,我们只需输入所需的运算,JavaScript 就会为我们完成计算(清单 4-2 和图 4-2 )。

A462229_1_En_4_Fig2_HTML.jpg

图 4-2

Output of Listing 4-2

function setup() {
        createCanvas(800, 300);
}

function draw() {
        background(220);

        ellipse(100 + 150, 200, 50, 50);
        ellipse(200 + 150, 200, 50, 50);
        rect(50 + 150, 160, 200, 20)
}

Listing 4-2Using Math Operations

同样的事情也适用于其他运营商;我们可以用类似的方式做减法、乘法或除法。

对于操作符,我们需要记住的一点是操作的顺序。你可能已经从你的数学课上了解到了这一点,但是有些运算符比其他运算符更重要。例如,如果我们想给一个数加 2,然后乘以 4,我们可能会写这样的代码:10 + 2 * 4

但是在这个运算中,乘法将发生在加法之前。在加到 10 之前,2 将与 4 相乘,因此上面的操作将产生 18,而不是预期的值 48。

为了能够控制运算的顺序,我们可以使用括号。比如,我们可以这样写上面的方程:(10 + 2) * 4

括号内的任何内容都将在其他操作之前进行计算。在运算顺序上,先有括号,再有乘除法,再有加减法。

变量

能够对这样的表达式求值,将使我们的计算工作变得更容易。但我认为,在这个例子中,真正的问题是需要在这三个不同的地方输入相同的数字。这非常重复、费力,并且容易出错。这是一个使用变量会很有用的例子。

每当我们需要一个值,并且需要在多个地方使用该值时,我们会希望将该值存储在一个变量中。使用变量的好处是,如果我们需要更新变量的值,我们只需要在一个地方完成。让我们更新这个例子来使用一个变量。

记住如何创建变量。我们将从使用关键字var开始。使用这个关键字非常重要,原因将在后面讨论。

然后我们会为变量选择一个名字。选择一个有意义的名字也很重要。调用这个变量offsetx可能有意义,因为我们将使用它来偏移 x 轴上的形状。使用合理的名字将有助于他人甚至我们理解我们的代码。我们总是希望我们的程序尽可能具有可读性。

现在我们有了一个指向值的变量,我们可以在操作中使用这个变量,而不是值本身。这样做,我们只需要从一个点改变这个变量的值,就可以看到形状在移动(清单 4-3 )。

function setup() {
        createCanvas(800, 300);
}

function draw() {
        background(220);

        var offset = 150;

        ellipse(100 + offset, 200, 50, 50);
        ellipse(200 + offset, 200, 50, 50);
        rect(50 + offset, 160, 200, 20)
}

Listing 4-3Using variable 
offset

变量续

我想用一个不同的例子来说明变量的另一种行为。我们只在屏幕中间画一个圆形,中间画一个矩形(列出 4-4 和图 4-3 )。

A462229_1_En_4_Fig3_HTML.jpg

图 4-3

Output from Listing 4-4

function setup() {
        createCanvas(800, 300);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(400, 150, 200, 200);

        // rectangle
        fill(255);
        rect(400, 150, 150, 30);
}

Listing 4-4
Circle and rectangle

你能想出一个我们可以对上述程序进行的优化吗?注意我们是如何重复形状的 x 和 y 位置值的。让我们用一个变量来代替(清单 4-5 )。

function setup() {
        createCanvas(800, 300);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // declaration of variables
        var x = 400;
        var y = 150;

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(x, y, 200, 200);

        // rectangle
        fill(255);
        rect(x, y, 150, 30);
}

Listing 4-5Using a variable to create a circle and rectangle

因为这些形状并不是相对于画布大小来定位的,所以如果我们要改变画布的大小,形状的相对位置也会改变。对于正方形画布,形状当前位于中心,但是对于更宽的画布,形状可能会开始向左侧倾斜。对于任何给定的画布大小,要使形状靠近中心,我们可以从使用变量设置画布的宽度和高度值开始。然后我们可以利用相同的变量来控制形状的位置。

setup函数中,我们将创建两个名为canvasWidthcanvasHeight的新变量,它们的值分别为 800 和 300。我们将把这些变量传递给createCanvas函数,而不是使用之前的硬编码值。计划是我们也可以在draw函数中使用这些相同的变量,这样即使我们要改变画布的大小,形状的相对位置也会保持不变。所以让我们在draw函数中使用这些变量(清单 4-6 )。我们将它们除以 2,这样我们就可以得到画布宽度和高度的半个点。

function setup() {
        var canvasWidth = 800;
        var canvasHeight = 300;

        createCanvas(canvasWidth, canvasHeight);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // declaration of variables
        var x = canvasWidth/2;
        var y = canvasHeight/2;

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(x, y, 200, 200);

        // rectangle
        fill(255);
        rect(x, y, 150, 30);
}

Listing 4-6Using variables in the 
draw

function

当执行代码时,您会注意到我们得到一个错误。如果我们要查看控制台内部的错误消息,它说明了没有定义变量名:

Uncaught ReferenceError: canvasHeight is not defined (sketch: line 14)
Uncaught ReferenceError: canvasWidth is not defined (sketch: line 14)

这可能令人惊讶,因为我们已经在setup函数中明确声明了这些变量。这个错误的原因与一个叫做scope的东西有关。变量的作用域决定了在哪里可以访问变量。JavaScript 变量在使用var关键字声明时有一个函数作用域。

您也可以使用'let''const'关键字来声明变量。使用这些关键字声明的变量有不同的相关作用域规则,但是出于本书的目的,我们不会深入研究这些关键字的用法。

函数作用域的工作原理是,在函数内部声明的任何变量在函数外部都是不可见的。它只对它所在的函数和可能嵌套在该函数中的其他函数可用。同样,如果我们在顶层有一个变量,这个变量将对该层和嵌套层中的所有内容可见,比如可能在那里定义的函数。我们现在面临的问题是在setup函数中定义的变量在draw函数中是不可见的。因此,如果要在draw函数中声明变量,它们在同一级别的其他函数中是不可见的。

这个问题的解决方案是这样的:我们不应该在setup函数中声明我们的变量,而是应该在这个顶层声明它们,这样它们就可以被在顶层声明的其他任何东西访问(清单 4-7 )。

// declaration of global variables
var canvasWidth = 800;
var canvasHeight = 300;

function setup() {
        createCanvas(canvasWidth, canvasHeight);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // declaration of variables
        var x = canvasWidth/2;
        var y = canvasHeight/2;

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(x, y, 200, 200);

        // rectangle
        fill(255);
        rect(x, y, 150, 30);
}

Listing 4-7Declaring a global variable

在顶层声明的变量称为全局变量。在这个顶层声明变量通常不是最好的主意,因为我们在浏览器中运行我们的代码,而在浏览器中运行的其他东西,如插件、附加组件等。,可能会因针对不同用途定义同名变量而导致冲突。每当两个变量声明共享相同的名称时,后来声明的那个变量会覆盖另一个变量,因为代码是从上到下执行的。这可能会导致程序不按预期运行。但作为初学者,这不是你必须担心的事情。其他更有经验的开发人员——也有同样的顾虑——会有适当的保护措施来确保他们的变量不会被覆盖。现在,我们可以把我们的变量放在顶部,并且能够在相同级别或更低级别定义的不同函数中共享它们。

在这种情况下,我们在setup函数之外初始化必要的变量,以便这些变量可以从setupdraw函数中访问。现在,我们可以尝试将canvasWidthcanvasHeight变量设置为不同的值,并注意形状如何始终保持在中心,因为它的位置是使用与画布相同的变量导出的。

p5.js 中预定义的变量

p5.js 是一个非常有用的库,它有几个预定义的变量,我们可以用它们来获得某些值。我们可以使用的两个这样的变量名是widthheight。通过在setupdraw函数中使用这些变量名,我们可以得到当前的画布大小。这使得我们可以通过定义自己的变量名来做同样的事情。p5.js 开发人员一定已经意识到这是很多开发人员会尝试自己做的事情,因此提供了一个更简单的问题解决方案。

有了这些知识,清单 4-7 中的代码可以写成清单 4-8 中所示的形式。

function setup() {
        createCanvas(800, 300);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // declaration of variables
        var x = width / 2;
        var y = height / 2;

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(x, y, 200, 200);

        // rectangle
        fill(255);
        rect(x, y, 150, 30);
}

Listing 4-8Working with predefined variables

你应该注意到widthheight是 p5.js 变量,这意味着它们在setupdraw函数之外不可用。

现在我们知道如何使用变量,我们可以动画我们的形状!p5.js 中动画的诀窍是记住 p5.js 不断地为我们执行draw函数。每次再次执行draw函数时,我们放在该函数中的任何内容实际上都被重新绘制。

这个draw功能被执行的次数(可以认为是渲染到屏幕上)被称为帧率。默认情况下,p5.js 的帧速率为 60。这意味着它每秒钟尝试重新绘制(或呈现)60 次draw函数的内容。如果我们有办法改变这些draw调用之间使用的变量的值,那么我们就能够创建动画。

这应该会让您想起动画书动画。每次调用一个draw函数都会产生一个静态图像,但是由于它每秒发生 60 次,每个图像都略有不同,所以你会感觉它是动态的。

为了能够创建一个动画,我们将在名为countdraw函数之外初始化一个变量。在draw函数中,我们将使用这个简单的表达式,每次调用draw函数,变量count就会增加 1。

count = count + 1;

现在,如果我们要在 position 参数中使用这个变量,我们可以移动形状(清单 4-9 )。这是我们 p5.js 冒险的一个惊人的进步。

var count = 0; // initialize a counter variable

function setup() {
        createCanvas(800, 300);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // declaration of variables
        var x = width / 2 + count;
        var y = height / 2;

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(x, y, 200, 200);

        // rectangle
        fill(255);
        rect(x, y, 150, 30);

        count = count + 1; // increment the counter variable
}

Listing 4-9.Animating a shape

如果我们不想让形状移动,而是想让它变大呢?放轻松!我们将首先创建一个size变量,并在我们的形状中使用它,而不是硬编码的值,以便能够更容易地更新大小(清单 4-10 )。

var count = 0; // initialize a counter variable

function setup() {
        createCanvas(800, 300);
        rectMode(CENTER);
}

function draw() {
        background(1, 186, 240);

        // declaration of variables
        var x = width / 2;
        var y = height / 2;
        var size = 200 + count; // control the size of the shapes

        // circle
        fill(237, 34, 93);
        noStroke();
        ellipse(x, y, size, size);

        // rectangle

        fill(255);
        rect(x, y, size*0.75, size*0.15);

        count = count + 1; // increment the counter variable
}

Listing 4-10Using a size variable

摘要

在这一章中,我们重温了以前见过的操作符,并谈了一点关于操作符优先的内容。然后我们又看了看变量,了解了更多关于它们的行为,尤其是关于它们的范围。我们还了解了 p5.js 自带的一些内置变量,比如只在setupdraw函数中可用的widthheight

最后我们创作了我们的第一部动画!

实践

创建一个动画,其中最初在屏幕外的五个矩形被动画化,从左侧进入屏幕,从右侧退出。它们也应该以不同的速度移动。