function Person(name) { this.name = name; } let ferdinand = Person("Ferdinand"); // oops console.log(name); // → Ferdinand
虽然我们错误调用了Person,代码也可以执行成功,但会返回一个未定义值,并创建名为name的全局绑定。而在严格模式中,结果就不同了。
"use strict"; function Person(name) { this.name = name; } let ferdinand = Person("Ferdinand"); // → TypeError: Cannot set property 'name' of undefined
JavaScript 会立即告知我们代码中包含错误。这种特性十分有用。
幸运的是,使用class符号创建的构造器,如果在不使用new来调用,则始终会报错,即使在非严格模式下也不会产生问题。
严格模式做了更多的事情。 它不允许使用同一名称给函数赋多个参数,并且完全删除某些有问题的语言特性(例如with语句,这是错误的,本书不会进一步讨论)。
简而言之,在程序顶部放置"use strict"很少会有问题,并且可能会帮助你发现问题。
类型
有些语言甚至在运行程序之前想要知道,所有绑定和表达式的类型。 当类型以不一致的方式使用时,他们会马上告诉你。 JavaScript 只在实际运行程序时考虑类型,即使经常尝试将值隐式转换为它预期的类型,所以它没有多大帮助。
尽管如此,类型为讨论程序提供了一个有用的框架。 许多错误来自于值的类型的困惑,它们进入或来自一个函数。 如果你把这些信息写下来,你不太可能会感到困惑。
你可以在上一章的goalOrientedRobot函数上面,添加一个像这样的注释来描述它的类型。
// (WorldState, Array) → {direction: string, memory: Array} function goalOrientedRobot(state, memory) { // ... }
有许多不同的约定,用于标注 JavaScript 程序的类型。
关于类型的一点是,他们需要引入自己的复杂性,以便能够描述足够有用的代码。 你认为从数组中返回一个随机元素的randomPick函数的类型是什么? 你需要引入一个绑定类型T,它可以代表任何类型,这样你就可以给予randomPick一个像([T])->T的类型(从T到T的数组的函数)。
当程序的类型已知时,计算机可以为你检查它们,在程序运行之前指出错误。 有几种 JavaScript 语言为语言添加类型并检查它们。 最流行的称为 TypeScript。 如果你有兴趣为你的程序添加更多的严谨性,我建议你尝试一下。
在本书中,我们将继续使用原始的,危险的,非类型化的 JavaScript 代码。
测试
如果语言不会帮助我们发现错误,我们将不得不努力找到它们:通过运行程序并查看它是否正确执行。