本书“ {您不知道JS}类型和语法构造”

图片 无论您有JavaScript编程经验如何,很可能您都不完全了解该语言。 这份简明的指南比所有现有书籍更深入地探讨了类型:您将学习类型如何工作,关于类型转换的问题以及如何使用新功能。

就像“您不知道JS”系列中的其他书籍一样,它显示了JavaScript程序员宁愿远离(或假设它们不存在)的语言的重要方面。 掌握了这些知识,您将可以真正掌握JavaScript。

摘录。 平等是严格和非严格的。


非严格相等由==运算符检查,严格相等由===运算符检查。 两种运算符都用于比较“相等”的两个值,但是形式(严格/非严格)的选择会导致行为上非常重要的差异,尤其是在如何决定相等性方面。

对于这两个运算符存在一个普遍的误解:“ ==检查值的相等性,而===检查值和类型的相等性。” 听起来很合理
但不准确。 无数知名的JavaScript书和博客都这么说,但不幸的是,它们全都错了。

正确的描述是:“ ==在检查相等性时允许类型转换,而===禁止类型转换。”

平等验证绩效


停下来思考第一个(不准确的)解释与第二个(精确的)解释有何不同。
在第一种解释中,显然===运算符比==要做更多的工作,因为它还需要检查类型。

在第二种解释中,==运算符的工作量更大,因为对于不同的类型,它必须进行类型转换。

不要陷入许多人陷入的陷阱。 不要以为这会以某种方式影响程序的速度,并且==会大大降低===的速度。 尽管转换需要一些时间,但大约需要几微秒(是的,百万分之一秒)。

如果要比较两个相同类型的值,则==和===使用相同的算法,因此,如果您不考虑引擎实施中的细微差异,则它们必须执行一个
和同样的工作。

如果要比较两个不同类型的值,则性能不是重要因素。 您还必须问自己一些问题:如果我要比较两个值,是否要进行类型转换?

如果需要转换,请使用非严格等于==,如果不希望进行转换,请使用严格等于===。

==和===这两个运算符都检查其操作数的类型。 不同之处在于它们如何响应类型不匹配。

抽象相等性检查


==运算符的行为在ES5规范的第11.9.3节(“抽象平等检查器算法”)中定义。 这是一种详细但简单的算法,其中明确列出了所有可能的类型组合以及应在每种组合中应用的类型转换方法(如有必要)。

当某人谴责(隐式)类型转换太复杂并且包含太多有用的实际使用缺陷时,他谴责“抽象相等性检查”的规则。 通常,人们认为这种机制太复杂且不自然,不适合实际研究和使用,它会在JS程序中产生错误,而不是简化代码读取。

我认为这是一个错误的假设-您的读者是有能力的开发人员,他们整日编写算法,即编写代码(并同时阅读和理解它)。 因此,我将尝试用简单的词来解释“抽象相等性测试”。 但是,我也建议阅读ES5规范的11.9.3节。 我认为这会让您感到惊讶,那里的一切逻辑如何。

实际上,第一部分(11.9.3.1)指出,如果两个比较值属于同一类型,则将以简单自然的方式比较它们。 例如,42仅是42,而字符串“ abc”仅是“ abc”。

请记住一些小例外:

  • NaN的值永远不会等于其自身(请参阅第2章)。
  • +0和-0彼此相等(请参阅第2章)。

11.9.3.1节的最后一节专门讨论了==与对象(包括函数和数组)是否相等的严格测试。 当两个完全相同时,两个这样的值才相等。 不执行类型转换。

严格相等检查===的定义与11.9.3.1相同,包括提供两个对象值。 这个事实鲜为人知,但是==和===在比较两个对象时表现完全相同!

11.9.3中算法的其余部分表明,松散相等==可用于比较两种不同类型的值,其中一种或两种都需要
隐式转换。 转换的结果是将名称转换为一种类型,然后可以通过简单的标识直接将它们进行相等性比较。
价值观。

=完全按照人们的预期确定; 实际上,运算==已完全实现,然后进行计算
否认结果。 严格检查不等式的操作也是如此!==。

比较:字符串和数字


为了演示==的转换,首先创建字符串和数字的示例,这在本章的前面已经完成:

var a = 42; var b = "42"; a === b; // false a == b; // true 

正如预期的那样,检查a === b失败,因为不允许进行转换,并且值42和“ 42”不同。

但是,在第二个比较中,a == b,使用了非严格等式; 这意味着如果类型不同,则比较算法将执行一个隐式转换
或两者兼而有之。

但是,这里正在执行哪种转换? 值a(即42)是否将成为字符串,或者值b“ 42”将成为数字? 第11.9.3.4–5节中的ES5规范说:

  1. 如果类型(x)的类型为Number,类型(y)的类型为String,则返回比较结果x == ToNumber(y)。
  2. 如果类型(x)的类型为字符串,类型(y)的类型为Number,则返回比较结果ToNumber(x)== y。

在规范中,使用Number和String类型的形式名称,而在原始类型的书中,通常使用符号数字和字符串。 不要将规范中数字符号的大小写与内置的Number()函数混淆。 就我们的目的而言,类型名称中的字符大小写不起作用-它们含义相同。

规范指出,将值“ 42”转换为数字以进行比较。 关于转换的执行方式,前面已经介绍过,特别是在描述抽象操作ToNumber时。 在这种情况下,很明显
得出的两个42值相等。

比较:布尔值的任何东西


尝试直接比较值为true或false时,会遇到类型==的隐式转换中最危险的陷阱之一。

一个例子:

 var a = "42"; var b = true; a == b; // false 

等等,这是怎么回事? 我们知道“ 42”是真正的含义(请参阅本章前面的内容)。 如何证明将它与true和严格相等的语句进行比较==
不给真的吗?

原因很简单,并且在同一时间欺骗性地狡猾。 这很容易造成误解,许多JS开发人员都没有努力去完全理解它。

我们再次引用规范11.9.3.6–7:

  1. 如果类型(x)为布尔类型,则返回比较结果ToNumber(x)== y。
  2. 如果类型(y)为布尔类型,则返回比较结果x == ToNumber(y)。

让我们看看这里是什么。 第一步:

 var x = true; var y = "42"; x == y; // false 

类型(x)实际上属于布尔类型,因此执行ToNumber(x)操作,将true转换为1。现在计算条件1 ==“ 42”。 类型仍然不同,因此(几乎是递归地)算法会重复。 与前面的情况一样,“ 42”被转换为42,条件1 == 42显然为假。

如果交换操作数,结果将保持不变:

 var x = "42"; var y = false; x == y; // false 

这次,类型(y)为布尔类型,因此ToNumber(y)给出0。条件“ 42” == 0递归变为42 == 0,这当然是错误的。

换句话说,值“ 42”既不是== true,也不是== false。 乍一看,这种说法似乎是完全不可想象的。 含义怎么可能既不正确也不错误?

但这是问题! 您在问错问题。 尽管实际上这不是您的错,但欺骗您的是大脑。

值“ 42”确实为true,但是无论您的大脑说什么,构造“ 42” == true都不执行布尔/转换测试。 “ 42”不转换为布尔值(true); 相反,将true转换为1,然后将“ 42”转换为42。

无论您是否喜欢,这里都根本不使用ToBoolean,因此对于==操作,“ 42”的真假完全不重要! 了解==比较算法在所有不同类型组合中的行为方式非常重要。 如果布尔值在一侧,则始终首先将其转换为数字。

如果您觉得这很奇怪,那么您并不孤单。 就个人而言,我建议在任何情况下都不要使用== true或== false。 没关系

但是请记住,我在这里仅谈论==。 构造=== true和=== false不允许类型转换,因此可以防止隐藏的ToNumber转换。

一个例子:

 var a = "42"; //  (  !): if (a == true) { // .. } //   (  !): if (a === true) { // .. } //   ( ): if (a) { // .. } //  ( ): if (!!a) { // .. } //   ( ): if (Boolean( a )) { // .. } 

如果您在代码中避免== true或== false(使用布尔值的松散相等性),则无需担心此真假陷阱。

比较:null与未定义


当您在空值和未定义值之间使用lax ==相等时,会发生隐式转换的另一个示例。 同样,我将引用ES5规范,
第11.9.3.2–3节:

  1. 如果x包含null,y包含undefined,则返回true。
  2. 如果x包含未定义,y包含null,则返回true。

与非严格运算符==相比,null和undefined彼此相等(即它们彼此转换),并且在整个语言中没有其他值。

对我们来说,这意味着为比较起见,可以将null和undefined视为无法区分的,如果您使用非严格相等性测试运算符==,则允许它们相互隐式转换:

 var a = null; var b; a == b; // true a == null; // true b == null; // true a == false; // false b == false; // false a == ""; // false b == ""; // false a == 0; // false b == 0; // false 

null和undefined之间的转换是安全且可预测的,并且其他任何值都不能为此类检查带来误报。 我建议使用此转换,以便程序中的null和undefined不会有所不同,并被解释为单个值。

一个例子:

 var a = doSomething(); if (a == null) { // .. } 

仅当doSomething()返回null或未定义并且对任何其他值(包括0,false和“”)失败时,a == null检查才通过。

此检查的显式形式(禁止任何类型的转换)(在我看来)看起来更难看,并且工作效率可能更低!

 var a = doSomething(); if (a === undefined || a === null) { // .. } 

我认为a == null的形式是这种情况的另一个示例,在这种情况下,隐式转换使读取代码更容易,但确实可靠且安全。

比较:对象和非对象


如果将对象/函数/数组与简单的标量原语(字符串,数字或布尔值)进行比较,则ES5规范将说明以下内容(第11.9.3.8–9节):

  1. 如果类型(x)的类型为字符串或数字,类型(y)的类型为对象,则返回比较结果x == ToPrimitive(y)。
  2. 如果类型(x)的类型为对象,类型(y)的类型为字符串或数字,则返回比较结果ToPrimitive(x)== y。

您可能已经注意到,在规范的这些部分中仅提到了字符串和数字,但没有提及布尔值。 事实是,如上所述,第11.9.3.6–7节确保首先将任何布尔操作数表示为Number。

一个例子:

 var a = 42; var b = [ 42 ]; a == b; // true 

对于值[42],将调用抽象操作ToPrimitive(请参见“抽象操作”),其结果为“ 42”。 从这一刻起,简单条件“ 42” == 42仍然存在,正如我们已经发现的,它变为42 == 42,因此a和b等于类型转换。

如您所料,本章前面讨论的抽象ToPrimitive操作的所有功能((toString(),valueOf()))在这种情况下也适用。如果您具有复杂的数据结构并且想要为此定义一个专门的方法valueOf(),为了检查是否相等,必须提供一个简单的值。

第3章检查了对象包装器在原始值周围的“拆包”(例如,在新的String(“ abc”)中),从而导致了底层原始值的返回
值(“ abc”)。 此行为与==算法中的ToPrimitive转换有关:

 var a = "abc"; var b = Object( a ); //  ,  `new String( a )` a === b; // false a == b; // true 

a == b表示true,因为b通过ToPrimitive操作转换(或“解包”)为基本简单标量基本值“ abc”,该基本值与a中的值匹配。

由于==算法中的其他优先规则,有些值并非如此。 一个例子:

 var a = null; var b = Object( a ); //  ,  `Object()` a == b; // false var c = undefined; var d = Object( c ); //  ,  `Object()` c == d; // false var e = NaN; var f = Object( e ); //  ,  `new Number( e )` e == f; // false 

null和undefined值不能打包(它们没有等效的对象包装器),因此Object(null)与Object()基本上没有区别:这两个调用都创建了通常的
ny对象。

NaN可以打包为等效的Number对象包装,但是当==导致解包时,NaN == NaN比较失败,因为NaN值永远不会等于其自身(请参见第2章)。

»这本书的更多信息可以在出版商的网站上找到
» 目录
» 摘录

小贩优惠券可享受25%的折扣-JavaScript

支付纸质版本的书后,就会通过电子邮件发送电子书。

Source: https://habr.com/ru/post/zh-CN456730/


All Articles