If you're seeing this message, it means we're having trouble loading external resources on our website.

如果你被网页过滤器挡住,请确保域名*.kastatic.org*.kasandbox.org 没有被阻止.

主要内容

按钮对象类型

使代码可被重复使用而且功能强大的最佳方法就是用面向对象编程,特别是用来做用户界面控制,比如按钮。在面向对象编程中,我们以具有某种行为的抽象对象类型来考虑编程,并用具体参数来创作这些对象类型的实例。如果你忘了如何在 Javascript 中做到,在此复习
为了用面向对象编程来制作按钮,我们必须定义一个 Button(按钮)对象类型并为它定义方法,例如如何绘制,如何处理鼠标点击。我们要写这样的代码:
var btn1 = new Button(...);
btn1.draw();

mouseClicked = function() {
  if (btn1.isMouseInside()) {
     println("Whoah, you clicked me!");
  }
}
让我们将这个与上一篇文章对比:
var btn1 = {...};
drawButton(btn1);

mouseClicked = function() {
  if (isMouseInside(btn1)) {
     println("Whoah, you clicked me!");
  }
}
很相似,对吧?但是这里有一个很大的差别 -- 函数都是在 Button 对象类型中定义的,它们确实是属于这些按钮的。在此,行为与属性有更精密的关系,而这通常会形成更严谨和适合重用的代码。
为了定义 Button 对象类型,我们要从构造函数开始:它就是接受设置参数并设定此对象实例开始的属性的特殊函数。
作为初次尝试,以下是一个接受 x、y、宽度与高度的构造函数:
var Button = function(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
};

var btn1 = new Button(100, 100, 150, 150);
这的确是有效的,但是我有另外一种方法要推荐。构造函数不是接受单独的参数,而是接受一个设置对象。
var Button = function(config) {
    this.x = config.x;
    this.y = config.y;
    this.width = config.width;
    this.height = config.height;
    this.label = config.label;
};
用 config(设置)对象的优势是可以不停的加更多参数让构造函数来处理(例如 label,而且在我们创造按钮时理解每个参数是干什么仍然很容易:
var btn1 = new Button({
    x: 100, y: 100,
    width: 150, height: 50,
    label: "Please click!"});
但是我们可以再进一步。如果大部分的按钮都有一样的宽度或高度会怎样?我们不应该必须为每一个按钮标明宽度和高度的参数,我们只用在需要的时候表明。我们可以让构造函数检查这些属性在设置对象中是否已定义,如果没有就设定为默认的值。例如:
var Button = function(config) {
    this.x = config.x || 0;
    this.y = config.y || 0;
    this.width = config.width || 150;
    this.height = config.height || 50;
    this.label = config.label || "Click";
};
现在我们只要用属性的一部分来调用,因为其它属性会被设定为默认的值:
var btn1 = new Button({x: 100, y: 100, label: "Please click!"});
为了个构造函数费这儿大劲儿?然而,这是值得的,我发誓。
现在我们已把构造函数搞定了,让我们来定义一些行为:draw(绘制)函数。它的代码会跟 drawButton 函数的一样,但是它会从 this 获取属性,因为它是在对象原型中定义的:
Button.prototype.draw = function() {
    fill(0, 234, 255);
    rect(this.x, this.y, this.width, this.height, 5);
    fill(0, 0, 0);
    textSize(19);
    textAlign(LEFT, TOP);
    text(this.label, this.x+10, this.y+this.height/4);
};
一旦这被定义,我们可以如此调用它:
btn1.draw();
以下是个用这个 Button 对象创作两个按钮的程序 - 注意创作并绘制多个按钮有多么容易:
但是,我们跳过了最难的部分:处理点击。我们可以从在 Button 原型中定义一个函数开始,如果用户在某个按钮的框界范围内点击就返回 true(成立)。再一次,这与我们以前的函数一样,但是它不是从代入的函数中而是从 this 中获取属性:
Button.prototype.isMouseInside = function() {
    return mouseX > this.x &&
           mouseX < (this.x + this.width) &&
           mouseY > this.y &&
           mouseY < (this.y + this.height);
};
现在我们可以在一个 mouseClicked 函数中调用:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
试一试点击以下每个按钮:
但是,我们设置这些点击处理的方法有一点让我不舒服。面向对象编程的目的就是把跟一个对象有关的行为都整合在对象中,并用属性来自定义行为。可是,我们在对象的外面遗留了一些行为,mouseClicked 中的 println
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
这些打印语句应该以某种方式绑定到每个按钮上,就像是可以代入构造函数的东西。看这现在的情况我们可以决定将一段信息代入构造的 config,并定义一个 handleMouseClick 函数来打印它:
var Button = function(config) {
    ...
    this.message = config.message || "Clicked!";
};

Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
         println(this.message);
    }
};

var btn1 = new Button({
    x: 100,
    y: 100,
    label: "Please click!",
    message: "You made the right choice!"
});

mouseClicked = function() {
   btn1.handleMouseClick();
};
这就好多了,因为现在所有跟一个按钮有关的东西都包含在构造函数中。但是过于简单。如果我们要做打印一段信息以外的事情,例如画几个形状或者切换场景,要用几行代码的,会怎么样?在这种情况,我们要为构造函数提供不止一个字符串 -- 我们真的要提供一些代码。我们怎么才能代入这些代码?
... ... 用一个函数!在 JavaScript 中(不是所有编程语言都如此),我们可以将函数作为参数代入函数。这在很多情况中都有用,但在定义像按钮的用户界面控制的行为时尤其有用。我们可以告诉按钮:"喂,这是一个函数,是我要你在用户点击这个按钮时调用的一些代码。" 我们叫这些函数 "callback" (回调)函数因为它们不是立刻调用,而是在之后适当的时间将被 "回调"。
我们可以先将一个函数的 onClick 作为参数代入:
var btn1 = new Button({
    x: 100,
    y: 100,
    label: "Please click!",
    onClick: function() {
       text("You made the right choice!", 100, 300);
    }
});
我们接着要保证构造函数根据我们代入的内容设定一个 onClick 属性。如果没有 onClick 代入的话,作为默认,我们就产生一个 "no-op" 函数,完全不进行操作的函数。它的存在只是为了能被调用以避免有错误:
var Button = function(config) {
    // ...
    this.onClick = config.onClick || function() {};
};
最后,我们需要在用户单击按钮后实际回调回调函数。 这实际上非常简单-我们可以通过写入保存的属性名称并用空括号括起来的方式来调用它:
Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
        this.onClick();
    }
};
我们先在就完成了 - 我们有一个让我们可以很容易创建新按钮的 Button 对象,它可以让每个按钮看起来不同,而且被点击后有不同的反应。四处点击以下的例子,看看改变按钮参数会发生什么:
现在你有了这个按钮模版,你也可以自定义你的按钮,例如不同的颜色,或者让它们对其它事件进行反应,例如 mouseover(鼠标放上去但是没有点击)。在你的程序中试一试!

想加入讨论吗?

尚无帖子。
你会英语吗?单击此处查看更多可汗学院英文版的讨论.