Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
husky-dot opened this issue Oct 17, 2019 · 1 comment
Open

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

husky-dot opened this issue Oct 17, 2019 · 1 comment

Comments

@husky-dot
Copy link
Owner

作者:valentinogagliardi

译者:前端小智

来源:github


阿里云最近在做活动,低至2折,有兴趣可以看看:
https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r


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

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

原文:https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter7.md

交流

阿里云最近在做活动,低至2折,有兴趣可以看看:https://promotion.aliyun.com/ntms/yunparter/invite.html?userCode=pxuujn3r

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

https://github.com/qq449245884/xiaozhi

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

clipboard.png

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

@1442916418
Copy link

44 - "alex" 的输出结果是?
结果:NaN

44 + "alex" 的输出结果是?为啥?
结果:“44alex”
原因:隐式转换
加法运算符(+)
如果 x 是字符串或者 y 是字符串那么返回 ToString(x) 后面跟 ToString(y)

"alex" + { name: "Alex" } 的输出结果是 ?
结果:“alex[object object]”

["alex"] == "alex" 输出结果是啥?为什么呢?
结果:true
原因:隐式转换

undefined == null 输出结果是啥?为什么呢?
结果:true
原因:抽象等式比较算法
比较 x == y 是这样执行的:如果 x 是 undefined ,y 是对象,返回比较的结果Object(x) == y

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants