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

10. 强制转换 #10

Closed
funfish opened this issue Nov 12, 2017 · 0 comments
Closed

10. 强制转换 #10

funfish opened this issue Nov 12, 2017 · 0 comments
Labels

Comments

@funfish
Copy link
Owner

funfish commented Nov 12, 2017

前言

类型与文法,在两个月以前其实已经看完了,但看完‘this与对象原型’和‘类型与文法’章节后,却自以为早已经掌握,没有什么可以谈的,于是便束之高阁。直到前一阵子,部门分享python基础的时候,提到python没有变量声明,拿来就用。着不是和JS很像?JS为什么没有变量声明呢?记得C语言都用变量声明,为何JS没有呢?变量声明有什么作用?好不好?

知乎问题为什么像 Java、C、C++ 这样的静态语言会比 Python、Ruby 这样的动态语言流行得多?

再结合另外一个知乎问题弱类型、强类型、动态类型、静态类型语言的区别是什么?里面介绍到:

Program Errors
• trapped errors。导致程序终止执行,如除0,Java中数组越界访问
• untrapped errors。 出错后继续执行,但可能出现任意行为。如C里的缓冲区溢出、Jump到错误地址

Forbidden Behaviours
语言设计时,可以定义一组forbidden behaviors. 它必须包括所有untrapped errors, 但可能包含trapped errors.
Well behaved、ill behaved
• well behaved: 如果程序执行不可能出现forbidden behaviors, 则为well behaved。
• ill behaved: 否则为ill behaved…

强、弱类型
• 强类型strongly typed: 如果一种语言的所有程序都是well behaved——即不可能出现forbidden behaviors,则该语言为strongly typed。
• 弱类型weakly typed: 否则为weakly typed。比如C语言的缓冲区溢出,属于trapped errors,即属于forbidden behaviors..故C是弱类型

动态、静态类型
• 静态类型 statically: 如果在编译时拒绝ill behaved程序,则是statically typed;
• 动态类型dynamiclly: 如果在运行时拒绝ill behaviors, 则是dynamiclly typed。

可以发现像JS这样的动态语言弱类型,没有如JAVA这样明显的编译过程,只是在浏览器或者Node运行时存在解析过程,并且在解析的时候检查有无forbidden behaviors。这样的动态语言有什么好处了,如部分答主所说的动如脱兔,如同草书般洒脱,不像其他语言一笔一划,讲求中正平稳。看看几年前JavaScript文件,程序如果比较大,几百行JavaScript都可以看得人头晕脑胀,也有没有IDE这样跳转工具,虽然现在有webStorm,对于大规模开发自然是不友好的,这种乱的感觉自然让很多人避开它。
到现在ECMAscript已经发展到ES7甚至ES8了,在Vue-router的开发里面,甚至都用上了Facebook的flow,来验证变量类型,提高代码质量,现在的JS已经是越来越旺盛了。
于是对变量声明的更进一步理解,于是又开始看‘类型与文法’这一章,没想到收获很多

开始

在JavaScript中,变量没有类型 -- 值才有类型。变量可以在任何时候,持有任何值

这句话深入我心,JS里面有var的变量声明,后面又有let和const。声明的变量可以是任何值,从基本变量到Object都是木有问题的,甚至在非严格模式下,不声明直接使用变量也不会报错,而且变量还可以随意更改,从number变到Object,完全没有问题,那为何要称变量为类型呢?明明说的就是变量背后的值是什么类型;

强制转换

falsy列表:

  1. undefined
  2. null
  3. false
  4. +0, 0, NaN
  5. ''
    这些在Boolean强制转换的时候都会变成false,值得注意的是String类型里面仅有唯一一个空字符串('')会被转换为false。
    同时,类似的如[], {}以及-1等等的都是true值,这些简单有用的true/false还是值得注意的。

明确的强制转换

一元操作符'+'能将String明确的转换为number,同样能将Date类型变为number如:

var d = new Date( "Mon, 18 Aug 2014 08:53:06 CDT" );
+d; // 1408369986000

但是通常都不会这么做,没有语义,容易误解,不如常用的'getTime()'好使

需要小心的是parseInt传入非string类型的时候产生的bug。

隐含的强制转换

下面的都是是这一章的重点了,明确的强制转换大多好懂,而且不容易犯错。相比之下,隐含的强制转换就深邃的多了。

var a = [1,2];
var b = [3,4];

a + b; // "1,23,4"

上面例子中当操作数不是number的时候,它们都被强制转换为String类型,我们知道'42' + '1' = '420'通过String来实现字符串拼接,但是Array为什么也会这么做?有深度的内容来了:

根据ES5语言规范的11.6.1部分,+的算法是(当一个操作数是object值时),如果两个操作数之一已经是一个string,或者下列步骤产生一个string表达形式,+将会进行连接。所以,当+的两个操作数之一收到一个object(包括array)时,它首先在这个值上调用ToPrimitive抽象操作(9.1部分),而它会带着number的上下文环境提示来调用[[DefaultValue]]算法(8.12.8部分)。

如果你仔细观察,你会发现这个操作现在和ToNumber抽象操作处理object的过程是一样的(参见早先的“ToNumber”一节)。在array上的valueOf()操作将会在产生一个简单基本类型时失败,于是它退回到一个toString()表现形式。两个array因此分别变成了"1,2"和"3,4"。现在,+就如你通常期望的那样连接这两个string:"1,23,4"。

这上面两段话以为着什么?说的是当你用object做加法操作的时候,object会调用其valueOf方法并返回基本类型,如果valueOf返回的基本类型失败(如没有valueOf方法或则返回的是object),那么就退回去用toString()方法,如果object没有toString()方法,那就返回错误;当然对象自然都是有toString方法的。
其他的String到number的常见的转换就是如'42' + 2 = 44通过字符串和数字直接相加。

上面提到的都是'+'加法计算,那减法呢?
字符串相加可以理解为拼接的过程,但是相减就完全不一样了,如下:

[3] + [1] // '31'
[3] - [1] // 2

恐怖吧?减法直接将其强制转为String,再由String转换为Number类型。

||和&&操作符

||和&&操作符其实没有特别的,只是当你去研究它的时候,又会发现它很特别
在判断的时候如if语言或则是三目判断,常会用到||和&&操作符。但是:

引用ES5语言规范的11.11部分:
一个&&或||操作符产生的值不见得是Boolean类型。这个产生的值将总是两个操作数表达式其中之一的值。

||和&&操作符只是意味着选择,而不是其他语言那样的逻辑判断!
想想确实也用过||和&&来做选择,比如以前很常用的:

e = event || window.event

用来做兼容处理。这里用的就是选择,而常见的cb&&cb()也是做选择。那我们所谓的逻辑判断呢?这个时候用选择的思维考虑一下,马上豁然开朗,逻辑的意思也就是返回最后被选择的数,如果这个数是true那就是真咯。

宽松等价与严格等价

提到== 和 ===操作,大多数开发者对前者经常是避而不及,省心的选择就是用后者,做严格的判断,毕竟前者太过花哨了。。。。。。还是先用心了解一下吧:
这里不得不提几条规范:

4.如果Type(x)是Number而Type(y)是String, 返回比较x == ToNumber(y)的结果。
5.如果Type(x)是String而Type(y)是Number, 返回比较ToNumber(x) == y的结果。
6.如果Type(x)是Boolean, 返回比较 ToNumber(x) == y 的结果。
7.如果Type(y)是Boolean, 返回比较 x == ToNumber(y) 的结果。

当String类型和Number比较的时候,String类型会被ToNumber为数字进行对比。同样的Boolean类型会被ToNumber,转换为0或者1。于是就有了如下:

'42' == false // false
'42' == true // false

可以看出'42'既不是0也不1,但是上面表达式却显得42不true也不false。

2.如果x是null而y是undefined,返回true。
3.如果x是undefined而y是null,返回true。
8.如果Type(x)是一个String或者Number而Type(y)是一个Object, 返回比较 x == ToPrimitive(y) 的结果。
9.如果Type(x)是一个Object而Type(y)是String或者Number, 返回比较 ToPrimitive(x) == y 的结果。

对于null和undefined自然是自身不true也不false,就等于null或则undefined,这个还是很容易理解的;那Object呢?
前面在'+'加法操作的时候提到过Object的转换问题,能通过valueOf返回基本类型就返回,不能就通过toString()方法,这里也是适用的。
值得一提的是形如{}这样的对象,其toString()之后是[Object Object]而不是'0',和平时用的Object.prototype.toString.call还是很接近的。
来看看下面例子

Number.prototype.valueOf = function() {
    return 3;
};

new Number( 2 ) == 3;   // true

这种就是业界毒瘤了,希望不要有傻逼这么写。。。。。哈哈哈哈

抽象关系比较

前面提到了加法和双等于判断,接下来怎么可以不提到大于小于判断呢?有了前面的基础,对于强制转换还是很容易理解的。看一下例子

var a = { b: 42 };
var b = { b: 43 };

a < b;  // false
a > b;  // false
a == b;  // false

a <= b; // true
a >= b; // true

这里Object对象自然都是被强制转换为[object Object],那肯定是不是大小于关系的,那==呢?[object Object]难道不等于[object Object]?这个时候就不要陷入思维误区了,就像'' == '0'为false一样,明明转换后都是数字0为何不成立?因为他俩都是String,不用强制转换直接对比。同样的Object和Object用==判断自然也不用强制转换。值得一提的是Object只有和自己做==判断的时候才为true。
那后面的小于等于和大于等于呢?妈呀,看着都要乱了,这个要根据语言规范了,谁叫他是老大呢?

因为语言规范说,对于a <= b,它实际上首先对b < a求值,然后反转那个结果。因为b < a也是false,所以a <= b的结果为true。

原来是反转。。。。。。JS的逻辑还真会玩

文法

语句是有完成值的,比如你打开浏览器的控制台,输入var a = 1回车,这个时候,显示的下一行是undefined,而不是1,但你敲入a会车的时候,才显示1,这里的undefined和1就是语句的完成值。
关于强制转换,一个经常被引用的坑是[] + {}{} + [],这两个表达式的结果分别是[object Object]0,因为后面的{}表示的是缺省';'的语句于是变成{}; + []自然是0了。
另外文中还提到结构解析,操作优先级,自动分号(ASI)和错误的问题,这些都是基础部分,这里就不介绍了。

@funfish funfish added the Book label Nov 27, 2017
@funfish funfish changed the title 强制转换 10. 强制转换 Mar 11, 2018
@funfish funfish closed this as completed Feb 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant