📜  JS++ | JavaScript 中的类型

📅  最后修改于: 2022-05-13 01:55:29.996000             🧑  作者: Mango

JS++ | JavaScript 中的类型

在本章中,我们将探索 JavaScript 编程风格以及开发人员如何使用 JavaScript(而不是 JS++)中的类型。本章将帮助你理解后面详细解释 JS++ 类型系统的章节。

在本教程中,我们将使用 Google Chrome 网络浏览器。如果您还没有 Google Chrome,请单击此处下载。

为了执行 JavaScript 代码,我们将使用 Chrome 开发者工具控制台。打开 Chrome 并点击 Ctrl + Shift + J 组合键并选择“控制台”选项卡。

将以下代码复制并粘贴到控制台中,然后按 Enter 键执行它:

var message;
message = "This is a test.";
if (Math.random() > 0.5) {
    message = 123;
}
console.log(message);

点击“向上箭头”并点击“回车”以多次评估代码。尝试评估代码几次。

Chrome 控制台示例

请注意上述代码中的数据类型如何从字符串变为数字。但是,仅当随机生成的数字大于 0.5 时,它才会变为数字。因此,每次执行脚本时,变量“消息”的数据类型都可能不同。这是 JavaScript 中的一个主要问题。例如,以下 JavaScript 代码是不安全的:

function lowercaseCompare(a, b) {
    return a.toLowerCase() == b.toLowerCase();
}

原因是 toLowerCase() 是一种仅适用于 JavaScript字符串的方法。让我们在 Chrome 控制台中执行以下 JavaScript 代码:

function lowercaseCompare(a, b) {
    return a.toLowerCase() == b.toLowerCase();
}

console.log("First message.");
lowercaseCompare("10", 10); // Crashes with 'TypeError'
console.log("Second message."); // Never executes.

请注意脚本将如何因 TypeError 而崩溃。第二条消息永远不会被记录。关键的一点是代码崩溃了,因为 toLowerCase() 不是可用于数字的方法,但该函数是用字符串(“10”)和数字(10)调用的。 number 参数不是“lowercaseCompare”函数的有效参数。如果更改函数调用,您将观察到程序不再崩溃:

// Change this:
// lowercaseCompare("10", 10); // Crashes with 'TypeError'
// to:
lowercaseCompare("10", "10");

开发人员通过首先检查类型来解决 JavaScript 中的这些问题。这是在 JavaScript 中重写上述 'lowercaseCompare'函数的更安全的方法:

function lowercaseCompare(a, b) {
    if (typeof a != "string" || typeof b != "string") {
        return false;
    }

    return a.toLowerCase() == b.toLowerCase();
}

我们使用'typeof'检查类型,如果我们收到无效的参数类型,我们返回一个默认值。但是,对于较大的程序,这可能会导致大量额外代码,并且可能并不总是有适用的默认值。

JavaScript 中不可原谅的错误

在前面的示例中,我们探讨了 JavaScript 中一种不可原谅的错误:导致脚本执行结束的 TypeError。 JS++ 可以防止许多其他类型的错误,但是,现在,我们只看另一类错误:ReferenceErrors。下一段 JavaScript 代码有什么问题?

var message = "This is a test.";
console.log(message);

尝试在控制台中执行上述代码。再一次,没有任何记录。相反,您会得到一个 ReferenceError。这是因为上面的代码中有一个错字。如果我们修正错字,代码就会成功:

var message = "This is a test.";
console.log(message);

JavaScript 可能会因拼写错误而失败! TypeErrors 和 ReferenceErrors 不会在 JS++ 中发生。我们将 TypeErrors 和 ReferenceErrors 归类为“不可原谅”的错误,因为它们会导致 JavaScript 脚本执行停止。然而,JavaScript 中还有另一种类型的错误更危险,因为它们是“静默的”。

原谅 JavaScript 中的“静默”错误

JavaScript 中有一类“静默”错误,可以静默地继续在您的程序中传播。我们称这些“宽容”错误是因为它们不会停止脚本执行,但是,尽管名称无害,但我们可以认为它们比不可原谅的错误更危险,因为它们会继续传播。

考虑以下 JavaScript函数:

function subtract(a, b) { return a - b; }

这个函数表面上看起来很简单,但是——随着脚本变得越来越复杂——当变量发生变化并依赖于跨越数千行代码的其他值时,你最终可能会意外地从变量中减去一个最终成为数字的变量最终成为一个字符串。如果您尝试这样的调用,您将得到 NaN(非数字)。

在控制台中评估以下代码:

function subtract(a, b) { return a - b; }
subtract("a", 1);

观察生成的 NaN(非数字)值。它不会使您的应用程序崩溃,因此我们称其为宽容错误,但错误值将在您的程序的其余部分传播,因此您的程序会继续以静默方式运行并出现错误。例如,后续计算可能取决于“减法”函数返回的值。让我们尝试额外的算术运算来观察:

function subtract(a, b) { return a - b; }
var result = subtract("a", 1); // NaN
console.log(result);
result += 10; // Add 10 to NaN
console.log(result);

没有崩溃,也没有错误报告。它只是默默地继续以错误值运行。

您将无法运行以下代码,但这里说明了此类错误值如何在潜在的真实场景(购物车后端)中通过您的应用程序传播:

var total = 0;
total += totalCartItems();
while ((removedPrice = removedFromCart()) != null) {
    total = subtract(total, removedPrice);
}
total += tax();
total += shipping();

在上面的示例中,我们的购物车可能会以 NaN(非数字)值结束 - 导致业务损失,由于没有明确的错误,因此很难检测到。

JavaScript 直觉

JS++ 的设计基于广泛的 JavaScript 开发经验——不仅适用于大型、复杂的应用程序,还适用于任何可以使用 JavaScript 的地方——用于 Windows Script Host 的脚本和宏到基于 ActiveX 的遗留程序等,这些程序在某些企业环境中仍然很流行。简而言之,JS++ 可以在任何期望使用 JavaScript 的地方工作——从基础到复杂再到神秘。

与 JS++ 相关的一个重要观察结果是大多数 JavaScript 程序已经是良好类型的(但不是“完美”类型)。回想一下 JavaScript 'lowercaseCompare'函数的“不安全”和“安全”版本:

// Unsafe:
function lowercaseCompare(a, b) {
    return a.toLowerCase() == b.toLowerCase();
}
// Safe: 
function lowercaseCompare(a, b) {
    if (typeof a != "string" || typeof b != "string") {
        return false;
    }

    return a.toLowerCase() == b.toLowerCase();
}

安全版本要繁琐得多,而且——实际上——大多数 JavaScript 开发人员会以不安全的方式编写大部分函数。原因是,通过查看函数体,我们知道预期的参数类型是字符串,因为这两个参数都使用了仅适用于字符串的 'toLowerCase' 方法。换句话说,在 JavaScript 中,我们仅通过查看代码就能对类型有一种直觉

考虑以下变量并猜测它们的类型:

var employeeAge;
var employeeName;
var isEmployed;

employeeAge 作为数字有意义,employeeName 作为字符串有意义,isEmployed 作为布尔值有意义。

现在尝试猜测以下函数的预期参数类型:

function multiply(a, b) { return a * b; }
function log(message) { console.log("MESSAGE: " + message); }

如果您为 'a' 和 'b' 参数提供数字参数,则函数'multiply' 最有意义。此外,'log'函数对于字符串是最正确的。

JavaScript 强制转换(“类型强制”)

有时,JavaScript 程序员不会使用“typeof”检查类型,而是强制将参数转换为他们需要的数据类型(尤其是在直觉可能失败的情况下)。这种技术是类型强制的一个实例,并导致代码更具容错性,因为如果提供的参数的数据类型不正确,它不会以异常退出。

再一次,让我们看看如何使用这个想法来改变我们的 'lowercaseCompare' 示例:

// Unsafe:
function lowercaseCompare(a, b) {
    return a.toLowerCase() == b.toLowerCase();
}
// Safer: 
function lowercaseCompare(a, b) {
    a = a.toString();
    b = b.toString();

    return a.toLowerCase() == b.toLowerCase();
}

在“lowercaseCompare”函数的重写版本中,我们“强制”将“a”和“b”参数转换为字符串。这使我们能够安全地调用 'toLowerCase' 方法而不会崩溃。现在,如果调用 'lowercaseCompare'函数,我们会得到以下结果:

lowercaseCompare("abc", "abc") // true
lowercaseCompare("abc", 10) // false
lowercaseCompare("10", "10") // true
lowercaseCompare("10", 10) // true

但是,精明的观察者会注意到新版本的“lowercaseCompare”被标记为“更安全”而不是“安全”。

为什么?

toString 不是强制转换为字符串的最正确方法。 (由于运行时方法查找,它也不是最快的,但想象一下在编写一行代码时必须考虑所有这些细节?这就是在 JS++ 之前的 Web 编程方式。)

一个例子是,如果我们尝试使用忘记初始化的变量调用“lowercaseCompare”,如果我们使用“toString”,它将再次崩溃。让我们尝试一下:

function lowercaseCompare(a, b) {
    a = a.toString();
    b = b.toString();

    return a.toLowerCase() == b.toLowerCase();
}
var a, b; // uninitialized variables
var result = lowercaseCompare(a, b);
console.log(result); // Never executes

不,相反,对字符串执行类型强制的最正确方法是这样的:

// Finally safe: 
function lowercaseCompare(a, b) {
    a += ""; // correct type coercion
    b += ""; // correct type coercion

    return a.toLowerCase() == b.toLowerCase();
}
var a, b;
var result = lowercaseCompare(a, b);
console.log(result);

正确的代码只剩下一个问题:它变得不可读。如果您必须在任何地方插入 += “” 来表达您想要字符串数据的意图,您的代码会是什么样子?

JS++ 中的“小写比较”

现在有很多东西要消化!用 JavaScript 编写好的代码很难。想象一下,在用 JavaScript 编写一小段代码时必须考虑所有这些因素:安全性、性能、代码可读性、不可原谅的错误、静默错误、正确性等等。这实际上只是触及了 JavaScript 极端情况的表面,但它为我们提供了足够的信息来开始理解 JS++ 中的类型。

但是,如果我们用 JS++ 编写代码,JS++ 实际上会为我们处理所有这些考虑。这意味着您可以编写可读的代码,但 JS++ 编译器将处理生成快速、安全和正确的代码。

在我们进入下一章之前——详细解释 JS++ 类型系统——让我们尝试用 JS++ 重写“lowercaseCompare”代码。我们将从故意不正确的代码开始,向您展示 JS++ 如何及早捕获此类错误并展示如何修复它们。创建一个“test.jspp”文件并输入以下代码:

import System;

function lowercaseCompare(string a, string b) {
    return a.toLowerCase() == b.toLowerCase();
}

Console.log("First message.");
lowercaseCompare("10", 10);
Console.log("Second message.");

尝试编译文件。它行不通。 JS++ 很早就发现了错误:

[  ERROR  ] JSPPE5024: No overload for `lowercaseCompare' 
matching signature `lowercaseCompare(string, int)' at line 8 char 0 at test.jspp

它会准确告诉您错误发生的位置,以便您在用户、访问者或客户有机会遇到它之前修复它。让我们修复有问题的行,JS++ 告诉我们它在第 8 行:

// lowercaseCompare("10", 10);
// becomes:
lowercaseCompare("10", "10");

修复有问题的行后运行代码。在 Windows 中,右键单击该文件并选择“使用 JS++ 执行”。在 Mac 或 Linux 中,在终端中运行以下命令:

js++ --execute test.jspp

您将看到两条消息都已成功记录。

在下一章中,我们将通过示例探索 JS++ 类型系统和“类型保证”。