这一册回答这样两个问题:面试
类型和值真正的工做方式 强制转换的角落和缝隙、强制转换值得花时间学习&合理的地方正则表达式
首先要说明的是,变量没有类型,变量持有有类型的值。 其中内建类型有:null
、undefined
、string
、number
、boolean
、object
、symbol
(ES6新加); 使用typeof
能够准确测出除null
外的6个,还有function
:tyepof demo;若是要测试null
:算法
//惟一一个typeof是object可是是fasly的值
var a = null;
(!a && typeof a === "object"); // true
复制代码
类Array指长得很像Array可是没有连接到Array.prototype的数据结构,好比DOM查询操做返回的DOM元素列表(连接到NodeList.prototype)、函数内参数值暴露的arguments
对象(连接到Object.prototype,在ES6被废弃)。 如何转换有两种方式:数组
Array.prototype.slice.call(***)
Array.from(***)
肤浅的类似性体如今它们都有一个length
属性,一个indexOf
方法和一个concat
方法:demo浏览器
说是肤浅说明二者更大的是区别。String是不可变的,String.prototype上挂载的方法操做字符串,不是原地修改内容,而是建立返回一个新的字符串;Array是至关可变的,尽管Array.prototype上挂载的方法有建立返回一个新数组的非变化方法,也有原地修改内容的变化方法。安全
对于String操做,有些Array方法是有用的,咱们能够对string
"借用"非变化的array
方法:demo;也有著名的反转字符串的例子是作了String=>Array=>String的转换:demo。固然若是频繁借用Array方法,那为何最开始不直接设为Array类型。性能优化
0.1+0.2===0.3
为何是false
?安全整数是什么?第一个问题,0.1和0.2在js中的二进制形式表达是不精确的,相加结果不是0.3,而是接近0.3的一个值,因此是false
。为了靠谱地作数值的比较,引入一个容差/机械极小值,在ES6中这个值是Number.EPSILON
,只要差值的绝对值小于容差,认为是数值相等。bash
第二个问题,JavaScript能表示的最大最小值是Number.MAX_VALUE
和Number.MIN_VALUE
,可是对于整数而言有一个安全范围,在这个安全范围内,整数是无误表示,这个范围是Number.MIN_SAFE_INTEGER
到Number.MAX_SAFE_INTEGER
。数据结构
NaN
是什么?如何断定一个值是NaN
?使用不一样为number
的值作操做等到的就是一个NaN
,它是一个哨兵值,能够理解为不合法的数字/失败的数字,试着进行数学操做可是失败了,这就是失败的number
结果。函数
NaN
值的断定不能直接和NaN
比较,由于NaN
是惟一一个本身不等于本身的。ES6给了一个Number.isNaN(..)
,如要要填补的话,类型是数字+本身不等于本身两个条件。
ES6提供了Object.is(..)
,用来测试两个值的绝对等价性,没有任何例外:demo。Object.is(..)
的定位是这样的:
Object.is(..)
可能不该当用于那些==
或===
已知 安全 的状况(见第四章“强制转换”),由于这些操做符可能高效得多,而且更惯用/常见。Object.is(..)
很大程度上是为这些特殊的等价状况准备的。
[[Class]]
是什么?typeof
结果是object
的值被额外地打上一个内部标签属性,就是[[Class]]
。这个属性不能直接访问,但能够间件经过Object.prototype.toString.call(..)
访问。
好比:demo ,注意简单基本类型string
、number
、boolean
发生封箱行为;好比结果[Object Array]
后面的Array
反应的是原生类型构造器/内建函数这个信息;
特殊的是null
和undefined
也能工做,结果是[Object Null] [Object Undefined]
,虽然没有Null
和Undefined
;
Object.prototype.toString.call(..)
也被用来测值类型,比typeof
更完善。
封箱与开箱是两个相反的过程,封箱将基本类型值包装进对象,开箱从对象中取出基本类型值。封箱有手动封箱(好比new String("aaa")
)和自动封箱两种,在基本类型值上访问对象的属性和方法就会发生自动封箱,好比var a = "zzz";a.length
。自动封箱,即让封箱在须要的地方隐含发生,这是推荐的,一个是方便另外一个是浏览器对于.length
等常见的状况有性能优化。开箱也分手动开箱(valueOf
)和自动开箱两种,当以一种查询基本类型值的方式使用对象包装时,自动开箱发生,即开箱隐含发生,给个demo。
Array
、Object
、Function
、RegExp
这四种的话使用字面形式建立值几乎是更好的选择。
(使用Array
构造器的一个用途:快速建立undefined * n
的数组)
(使用Object
构造器,得一个一个去添加属性,麻烦!)
(Function
构造器用于动态定义函数,这篇文章里有一个demo,可是通常也挺少用的)
使用RegExp
有一个用途是字面形式没有的——用于建立动态的正则表达式
Date
、Error
这两种的话没有字面形式,只能使用原生类型构造器。
使用Date
构造器给一个demo,获取当前时间时间戳比较经常使用
Error
手动使用比较少,通常是自动被抛出,可是有些像message
、type
、stack
的属性可能会有用
Symbol
使用Symbol(...)
构造器生成的数据是简单的基本标量(注意不是object
),Symbol
主要为特殊的ES6结构的内建行为设计。能够自定义,也有预约义的Symbol
值。(这里简单带过了,之后实际遇到了再研究)
ToString
、ToNumber
、ToBoolean
分别是?和强制转换的关系?强制转换最后都是转为boolean
、string
、number
这样的基本标量,而抽象操做ToString
、ToNumber
和ToBoolean
规定了这样的转换。
首先是ToString
,转换为string
,给个demo,注意ToString
能够被明确地调用,也能够在一个须要String
的上下文环境中自动地调用;另外想补充一下JSON.stringify(..)
字符串化(自己和ToString
的抽象操做没有关系,只要是对简单类型的字符串化行为比较像),JSON字符串化分安全值和非法值,非法值主要是由于不能移植到其余消费JSON值的语言,非法值包括undefined
、function
、symbol
和带有循环引用的object
。object
能够定义一个toJSON
方法,该方法在JSON.stringify(..)
调用时首先被调用,主要用途是将JSON非法值转换为安全值,另外JSON.stringify(..)
也提供第二个参数是替换器——过滤object
属性、第三个参数填充符。
而后说说ToNumber
,转换为number
,给个demo,注意object/array
首先是ToPrimitive
抽象操做。
最后是ToBoolean
,转换为boolean
,这个比较简单,falsy列表中的转换为false
:null
、undefined
、""
、+0
、-0
、NaN
、false
。
String
<-->Number
最明确的固然是不带new
的String(..)
和Number(..)
;另外x.toString()
也被认为是明确转为字符串,尽管背后隐含地发生了封箱;一元操做符+
也被认为是明确转为数字,可是若是状况是多个+/-
连着,是增长困惑的。
和String-->Number
彷佛有点关联的是解析数字字符串的行为(好比parseInt
、parseFloat
),解析数字字符串和类型转换的一个最大区别是,解析数字字符串是容忍错误的,好比parseInt("42px")
结果是"42"
,而类型转换是零容忍的。关于parseInt
有个网红面试题。
Boolean
明确地转为布尔值类型有两种方式:不带new
的Boolean(a)
和两个否认符号!!a
。Boolean(a)
很是明确,可是不常见,!!a
更加明确。
Numbers --> Strings
两个操做数的+
操做符是这样工做的:若是两个操做数之一是一个string
(或者object/array
经过ToPrimitive抽象操做成为一个string
),作string
链接;其余状况作数字加法。使用在隐含地Numbers转换为Strings上面是,number + ""
。
Strings --> Numbers
这里使用-
操做符,-
操做符仅为数字操做定义,因此两个操做数都会被转为number,即发生ToNumber抽象操做。使用在隐含地Strings转换为Numbers上面是,string - 0
。
两个操做数的+
操做符在没有string
的状况是作数字加法,也适用这里,好比true+true===2
,这里就隐含地发生了Booleans转换为Numbers。这个点有个有意思的用途,好比有3个条件,在仅一个为true
状况作后续操做:
const cond1 = ....;
const cond2 = ....;
const cond3 = ....;
if((cond1&&!cond2&&!cond3)||(!cond1&&cond2&&!cond3)||(!cond1&&!cond2&&cond3)){
//后续操做...
}
复制代码
这里的boolean逻辑就很复杂,但可以使用前面的方法用数字加法作简化:
if((!!cond1+!!cond2+!!cond3)===1){//后续操做...}
复制代码
有些表达式操做会作一个隐含地强制转换为boolean
值:
if(..)
for(;;)
第二个子句while(..)
、do{..}while(..)
?:
三元表达式的第一个子句||
、&&
其中||
和&&
被称为逻辑或和逻辑与,但也能够认为是操做数选择器操做符,由于:
a || b; //大体至关于 a?a:b
a && b; //大体至关于 a?b:a
复制代码
另外JS压缩器会把if(a){foo();}
转换为a&&foo()
,由于代码更短,可是不多有人这么作(我有这么作过...)
比较同类型的值时,它们算法是相同的,作的工做也基本是一致的,因此这里是随意的;在比较不一样类型的值,宽松等价容许作强制转换,若是想要强制转换用宽松等价,不然用严格等价。 宽松不等价与严格不等价是在前面两个的基础上取反。
同类型的值是简单天然地比较;
不一样类型的值:
string
与number
若是Type(x)是Number而Type(y)是String, 返回比较x == ToNumber(y)的结果。
若是Type(x)是String而Type(y)是Number, 返回比较ToNumber(x) == y的结果。
boolean
若是Type(x)是Boolean, 返回比较 ToNumber(x) == y 的结果。
若是Type(y)是Boolean, 返回比较 x == ToNumber(y) 的结果。
这里挂个demo,提醒千万不要==true
或者==false
null
与undefined
若是x是null而y是undefined,返回true。
若是x是undefined而y是null,返回true。
null
与undefined
是互相等价的,而不等价于其余值(惟一宽松等价)。这里给一个伪代码:
var a = doSomething();
if (a == null) {
// ..
}
复制代码
不管a
是null
仍是undefined
,检查都是经过的,而不须要再去作a===undefined||a===null
,更加简洁。
object
与非object
- 若是Type(x)是一个String或者Number而Type(y)是一个Object, 返回比较 x == ToPrimitive(y) 的结果。
- 若是Type(x)是一个Object而Type(y)是String或者Number, 返回比较 ToPrimitive(x) == y 的结果
宽松等价的坏列表指的是比较难理解的比较状况,发生的也是隐含强制转换,demo。
为了不坏列表,给出了能够遵循的规则:
true
或者false
值,那么就永远,永远不要使用==
。[]
,""
,或0
这些值,那么认真地考虑不使用==
。在这些状况,使用===
比==
好
a<b
时发生了什么?说说抽象关系比较算法大体流程:
a
和b
的值有一个object
类型,首先是ToPrimitive抽象操做处理,使得比较双方都是基础类型值string
类型,按照简单的字典顺序比较(数字在字母前面)在这个例子中a<b
、a==b
、a>b
结果都是false
,奇怪的是a>=b
、a<=b
都是true
。由于大于等于、小于等于更贴切的翻译是不小于、不大于,对比较操做取反。
比较没有像等价那样的“严格的关系型比较”,因此若是不容许隐含强制转换发生,在比较以前作好类型的强制转换
undefined
和undeclared概念给个代码片断就知道这二者的区别:在这里a就是undefined
,b就是undeclared。
var a;
a; // undefined
b; // ReferenceError: b is not defined
复制代码
typeof
在对待 undeclared的变量时不会报错,这是一种安全防卫行为。它有什么用呢?
debug.js
文件中有一个DEBUG
的全局变量,只有在开发环境下导入全局,生产环境不会,在其余的程序代码中可能会去检查这个DEBUG
变量:demo第二个实际case没有遇到过,另外也在连接下面挂出了依赖注入的解决方式。
除了以前提到的使用Date
构造器的两种方法(new Date().getTime()
和Date.now()
)之外,一元操做符+
也能够:+new Date
/+new Date()
,可是使用一元操做符+
转换的方式不够明确,因此不是很推荐。
~
发生了什么?有什么用途?位操做符|
会发生ToInt32
(转换为32位整数),像0|x
通常被用于转换为32位整数。位操做符~
首先第一步也是ToInt32
的转换,其次是按位取反,给个demo。用途有两个:
arr#indexOf
、arr#findIndex
、str#indexOf
时若是找不到结果返回-1
,-1
是一个错误状况的哨兵值,使用>=-1
/==-1
这样的判断,是一种“抽象泄露”,而若是使用~x //0当x=-1
这样的判断更加优雅~~
截断数字的小数部分,固然是ToInt32的结果例如分析下面表达式的执行结果:
a && b || c ? c || b ? a : c && b : a
复制代码
工做分两步,首先是肯定更紧密的绑定,由于&&
优先级比||
高,而||
优先级比? :
高。因此更紧密的绑定使得上述表达式等价于:
((a && b) || c) ? (c || b) ? a : (c && b) : a
复制代码
第二步是在同等优先级的操做符状况下,是从左发生分组(左结合)仍是从右发生分组(右结合),好比?:
是右结合的,因此a?b:c?d:e
是等价于a?b:(c?d:e)
,而不是(a?b:c)?d:e
,因此上述表达式等价于:
((a && b) || c) ? ((c || b) ? a : (c && b)) : a
复制代码
掘金上有看到问true || true && fn()
表达式中,fn
会不会执行。 按照以前更紧密的绑定分析,等价于表达式true||(true&&fn())
,可是执行顺序依然是从左往右,由于||
的短接,因此在第一个选择数为true
的状况,不会去执行第二个选择数,也就不会执行fn
函数。
let
和const
声明的变量是在TDZ
中的,因此必须得在声明以后使用,而不会发生提高;var b = 3;
function foo( a = 42, b = a + b + 5 ) {
// ..
}
复制代码
在赋值中的b引用将在参数b的TDZ中发生(不会被拉到外面的b引用),因此它会抛出一个错误。然而,赋值中的a是没有问题的,由于那时参数a的TDZ已通过去了。
switch
每一个case是严格等价仍是宽松等价?答案是严格等价,可是宽松等价是能够模拟的:
var a = "42";
switch (true) {
case a == 10:
console.log( "10 or '10'" );
break;
case a == 42:
console.log( "42 or '42'" );
break;
default:
// 永远不会运行到这里
}
复制代码