阅读 1240

【JS 口袋书】第 7 章:JS 中的类型转换与比较

作者:valentinogagliardi

译者:前端小智

来源:github


阿里云最近在做活动,低至2折,有兴趣可以看看: promotion.aliyun.com/ntms/yunpar…


为了保证的可读性,本文采用意译而非直译。

JS 中的基本类型

JS 有 7 种基本类型,分别如下:

  • String
  • Number
  • Boolean
  • Null
  • Undefined
  • Object
  • Symbol (ES6)

布尔值表示的值可以是 true ,也可以是 false。另一方面,null 是故意缺少一个值。null 通常被赋值给一个变量,用来表示变量过后会被赋予值。

var maybe = null;
复制代码

然后是 undefined,表示是一个变量没有任何附加项:

var name;
console.log(name)
undefined
复制代码

nullundefined 看起来很相似,但它们是两个截然不同的类型,以至于开发人员仍不确定要使用哪个类型。

可以使用 typeof 操作符来查看变量的类型:

typeof "alex"
"string"
复制代码

number 类型:

typeof 9
"number"
复制代码

boolean 类型:

typeof false
"boolean"
复制代码

undefined 类型:

typeof undefined
"undefined"
复制代码

null 类型

typeof null
"object"
复制代码

这个结果有点奇怪。null 看起来像一个对象,但实际上它是JS的一个历史错误,自该语言诞生以来就一直存在。由于这些原因,JS 一直名声不佳。null 只是其中的一例。另外,一种类型和另一种类型之间的转换有一些奇怪的规则。

先给大家介绍一下背景。各位先用 Python 做一个例子。Python 中的以下指令

'hello' + 89
复制代码

这样会得到一个明确的错误:

TypeError: can only concatenate str (not "int") to str
复制代码

在 JS 中完全没有问题:

'hello' + 89
复制代码

结果:

"hello89"
复制代码

更加奇怪是直接加一个数组:

'hello' + []
复制代码

结果:

'hello'
复制代码

再来:

'hello' + [89]
复制代码

结果:

"hello89"
复制代码

看起来这种转换背后有某种逻辑,甚至还可以有更加复杂的数组结构:

'hello' + [89, 150.156, 'mike']
复制代码

结果:

"hello89,150.156,mike"
复制代码

这两行 JS 足以让 Java 开发人员望而却步。但是 JS 中的这种行为是100%故意的。因此,有必要研究一下 JS 中隐式转换(也称为类型强制转换)。

当数字变成字符串

一些编程语言有一个称为类型转换的概念,这意味着:如果咱们想将一个类型转换成另一种类型,那么必须使转换明确。在 JS 中也有提供这种方法。考虑以下示例

var greet = "Hello";
var year = 89;
复制代码

如果想进行显式转换,可以在代码中用 toString() 方法:

var greet = "Hello";
var year = 89;

var yearString = year.toString()
复制代码

或者使用 String

var greet = "Hello";
var year = 89;

var yearString = String(year)
复制代码

String 是JS 内置对象的一部分,它反映了一些基本类型:StringNumberBooleanObject。这些内置组件可用于类型之间的转换。转换后,咱们可以拼接两个变量

greet + yearString;
复制代码

但是除了这种显式转换之外,在 JS 中还有一种微妙的机制,称为隐式转换,由 JS 引擎提供。

'hello' + 89
复制代码

结果:

"hello89"
复制代码

但是这种转换背后的逻辑是什么? 你可能会惊讶地发现,如果 JS 中的加法运算符+中的一个是字符串,则会自动将两个操作数中的任何一个转换为字符串!

更令人惊讶的是,这个规则在ECMAScript规范中已经固定下来了。第11.6.1节定义了加法运算符的行为,在这里总结一下:

加法运算符(+) 如果 x 是字符串或者 y 是字符串那么返回 ToString(x) 后面跟 ToString(y)

这种把戏只对数字有效吗? 不是,数组和对象一样的,跑不掉:

'hello' + [89, 150.156, 'mike']
复制代码

结果:

"hello89,150.156,mike"
复制代码

对象怎样:

'hello' + { name: "Jacopo" }
复制代码

结果:

"hello[object Object]"
复制代码

为了弄清,咋肥事,可以通过将对象转换为字符串来进行快速测试:

String({ name: "Jacopo" })
复制代码

结果:

"[object, Object]"
复制代码

但还有另一个问题:乘法、除法和减法的情况又是肿么样的?

我不是一个数字!

咱们看到加法运算符在至少一个操作数是字符串时,是如何将操作数转换成字符串。但是其他的算术运算符呢?

操作符 描述
+
++ 自增
*
** 指数 (es6)
-
-- 自减
/
% 取余

如果对不是数字的类型使用其中一个操作符(+除外),那么就得到了一种特殊类型的 : NaN

89 ** "alex"
复制代码

结果:

NaN
复制代码

NaN 表示不是数字,任何失败的算术运算,如下面的代码所示:

var obj = { name: "Jacopo" } % 508897
复制代码

结果:

console.log(obj)
NaN
复制代码

注意与NaN结合的 typeof。这个代码看起来没问题:

typeof 9 / "alex"
NaN
复制代码

那下面呢?

var strange = 9 / "alex"
复制代码

再使用 typeof:

typeof strange
"number"
复制代码

NaN 被分配给一个变量时,它就变成了number,这就引出了一个新问题。我如何检查一些变量是否是 NaN? ES6 中有一个名为 isNaN() 的新方法:

var strange = 9 / "alex"
isNaN(strange)
true
复制代码

接着来看看 JS 中的比较运算符,它们和算术运算符一样奇怪。

相等还是不等

JS 中有两大类比较运算符。首先是所说的**“弱比较”**。它是一个抽象的比较运算符(双等号):==。然后还有一个“强比较”:===,又名 严格比较运算符。他俩兄弟的行为方式不一样,来看看一些例子。

首先,如果咱们用两个操作符比较两个字符串,俩兄弟得到一致的结果:

"hello" == "hello"
true

"hello" === "hello"
true
复制代码

看起来很 nice,现在来比较两种不同的类型,数字和字符串。

首先是“强比较”

"1" === 1
false
复制代码

很明显,字符串 1 等于数字 1。弱比较又是怎么样?

"1" == 1
true
复制代码

true表示这两个值相等。这种行为与咱们前面看到的隐式转换有关。原来,抽象比较操作符会在比较类型之前自动转换类型。这是一个摘要:

抽象等式比较算法 比较 x == y 是这样执行的:如果 x 是字符串,y 是数字,返回比较的结果ToNumber(x) == y

说白了就是:如果第一个操作数是字符串,第二个操作数是数字,那么将第一个操作数转换为数字。

有趣的是,JS 规范中充满了这些疯狂的规则,我强烈建议对此进行更深入的研究。当然现在都建议使用强比较。

严格相等比较的规范指出,在将值与===进行比较之前,不会进行自动转换。在代码中使用严格的相等比较可以避免愚蠢的错误。

基本数据类型与对象

咱们已经看到了 JS 的构建块:StringNumberBoolean、Null、UndefinedObjectSymbol。它们都是大写的,这种风格甚至出现在ECMAScript规范中。但是除了这些基本类型之外,还有一些镜像原语的双胞胎:内置对象。例如,String 类型有一个等效的 String ,它以两种方式使用。如果像函数那样调用(通过传递参数),它会将任何值转换成字符串:

var someValue = 555;

String(someValue);

"555"
复制代码

如果使用 String 作为 new 的构造函数,那么结果就是String类型的对象

var someValue = 555;

var newString = new String(someValue);
复制代码

这种方式值得在控制台中查看对象,看看它与“普通”字符串有何不同

可以使用 typeof 来确认它确实是一个对象:

typeof newString
"object"
复制代码

基本类型的 Number 也有一个内置对象 Number,它可以(几乎)将任何值转换为数字:

var someValue = "555";

Number(someValue);

555;
复制代码

我说的几乎是因为在试图转换无效的“数字”时得到 NaN

var notValidNumber = "aa555";

Number(notValidNumber);

NaN;
复制代码

在构造函数形式Number中使用时,将返回number类型的新对象:

var someValue = "555";

new Number(someValue);

Number {555}
复制代码

内置对象 Boolean 以将任何值转换成布尔值:

var convertMe = 'alex';

Boolean(convertMe)

true
复制代码

用构造函数的方式会返回一个对象:

var convertMe = 'alex';

typeof new Boolean(convertMe)

"object"
复制代码

内置的 Object 行为也一样:

Object('hello'); // String {"hello"}

Object(1); // Number{1}
复制代码

如果在没有参数的情况下调用,它将返回一个空对象

Object()

{}
复制代码

如果作为构造函数调用,则返回一个新对象

new Object({
  name: "Alex",
  age: 33
});

{name: "Alex", age: 33}
复制代码

此时,你可能会问自己:什么时候可以使用内置对象,什么时候应该使用基本类型初始化值? 根据经验,当你只需要一个简单的类型时,应该避免构造函数调用:

// 不要这么用
var bool = new Boolean("alex");
var str = new String('hi');
var num = new Number(33);
var strObj = new Object('hello')
复制代码

除了不实用之外,这种形式还会带来性能损失,因为这种方式每次都会创建一个新对象。 最后但同样重要的是,当咱们想要一个简单的字符串或数字时,创建一个对象是没有意义的。所以下面的形式是首选的

// ok 的
var bool = true
var str = 'hi';
var num = 33;
var obj = { name: "Alex", age: 33 };
复制代码

当需要转换某些内容时,可以像使用函数一样使用 BooleanStringNumber,。

总结

JS 中有七个构建块,即 StringNumberBooleanNullUndefinedObjectSymbol,这些也称为基本类型。

JS 开发人员可以使用算术和比较操作符操作这些类型。但是咱们需要特别注意加法运算符+和抽象比较运算符 ==,它们本质上倾向于在类型之间进行转换。

这种 JS 中的隐式转换称为类型强制转换,并在ECMAScript规范中定义。建议在代码中始终使用严格的比较操作符===代替 ==

作为一种最佳实践,当你打算在一种类型和另一种类型之间进行转换时,一定要弄清楚彼此之间的类型。为此,JS 有一堆内建对象,它们基本类型的一种映射:StringNumberBoolean。这些内置函数可用于显式地在不同类型之间进行转换。

思考题:

  1. 44 - "alex" 的输出结果是?

  2. 44 + "alex" 的输出结果是?为啥?

  3. "alex" + { name: "Alex" } 的输出结果是 ?

  4. ["alex"] == "alex" 输出结果是啥?为什么呢?

  5. undefined == null 输出结果是啥?为什么呢?

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:github.com/valentinoga…

交流(欢迎加入群,群工作日都会发红包,互动讨论技术)

阿里云最近在做活动,低至2折,有兴趣可以看看:promotion.aliyun.com/ntms/yunpar…

干货系列文章汇总如下,觉得不错点个Star,欢迎 加群 互相学习。

github.com/qq449245884…

因为篇幅的限制,今天的分享只到这里。如果大家想了解更多的内容的话,可以去扫一扫每篇文章最下面的二维码,然后关注咱们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

每次整理文章,一般都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励

关注下面的标签,发现更多相似文章
评论