1. JS八种内置类型
- null
- undefined
- number
- boolean
- string
- object
- symbol
- bigint
对语言引擎和开发人员来说,类型是值的内部特征,它定义了值的行为,以使其区别于其他值。
案例一分析:
typeof null == 'object' // true
上面是一个由来已久的bug,需要使用复合类型来检测null的类型,如下:
var a = null;
if(!a && typeof a == 'object') {
// ...
}
null是“假值”(falsy或者false-like),也是唯一一个用typeof检测会返回"object"的基本类型值。
案例二分析:
typeof function a() {} == 'function'
这样看来,function(函数)也是JavaScript的一个内置类型。然而查阅规范就会知道,它实际上是object的一个“子类型”。具体来说,函数是“可调用对象”,它有一个内部属性[[Call]],该属性使其可以被调用。
2. undefined和undeclared
undefined表示变量声明了但未赋值undeclared表示未声明
看下面的例子:
var a;
typeof a == 'undefined' // true
typeof b == 'undefined' // true
虽然b是undeclared,但typeof仍然不会报错,这是因为typeof有个安全防范机制。如下:
if(DEBUG) {
// 会报错
}
if(typeof DEBUG == 'undefined') {
// 即使DEBUG未被定义,这样是安全的
}
// 这不仅对用户定义的变量(比如DEBUG)有用,对内建的API也有帮助:
if(typeof tool == 'undefined') {
tool = function() {
}
}
如果想要为某个缺失的功能写polyfill,会更友好。
3.数组
- 使用delete运算符可以将单元从数组中删除,单元删除后,数组的length属性并不会发生变化。
- 通过
Array#.slice或Array.from函数将类数组转为数组
4.字符串
字符串和数组都有length属性以及 indexOf和 concat 方法
字符串与数组的区别在于 无法通过下标修改指定位置的值,例如
var arr = [1,2,3]
arr[1] = '1' // [1, '1', 3]
var str = 'abc'
str[2] = '1' // 'abc'
// 字符串最好通过charAt方法获取,而非下标
str.charAt(2)
5.数字
Number#.toFixed:保留小数点后几位,如果指定的小数位数多于实际位数,就用0补齐。Number#.toPrecision: 精确到第几位。
<!--toFixed-->
var a = 42.59
a.toFixed(1) // 42.6
a.toFixed(4) // 42.5900
<!--toPrecision-->
a.toPrecision(1) // 4e+1
a.toPrecision(2) // 43
a.toPrecision(3) // 42.6
a.toPrecision(6) // 42.5900
5.1字面量的其他表示形式
0x打头:十六进制 0打头:八进制 0b打头:二进制
Oxf3 // 243的十六进制
0363 // 243的八进制
0b11110011 // 243的二进制(ES6支持)
5.2 面试题:0.1 + 0.2 == 0.3为何为false
js数字类型遵循IEEE 754规范,二进制浮点数中的0.1和0.2并非十分精确,它们相加的结果并非刚好等于0.3,而是一个比较接近的数字0.30000000000000004,所以结果为false。
对js中的数字来说,存在一个误差范围值,这个值通常是2^-52 (2.220446049250313e-16)。在ES6中,这个值定义在Number.EPSILON中,可以直接拿来用。也可以为ES6之前版本写polyfill:
if(!Number.EPSILON) {
Number.EPSILON = Math.pow(2, -52)
}
因而可以使用Number.EPSILON来比较两个数字是否相等。
function isEqual(a, b) {
return Math.abs(a - b) < Number.EPSILON
}
isEqual(0.1 + 0.2, 0.3) // true
isEqual(0.00001, 0.000011) // false
能够呈现的最大浮点数(1.798e+308)存放在Number.MAXVALUE.最小浮点数(5e-324)存放在Number.MINVALUE中,它不是负数,但无限趋近于0.
5.3 整数的安全范围
能被安全呈现的最大整数是2^53 - 1,在ES6中被定义为Number.MAX SAFE INTEGER.最小整数是-2^53 - 1,被定义为Number.MIN SAFE INTEGER
5.4 NaN:不是数字的数字
NaN仍是数字类型,用于指出数字类型中的错误情况,即“执行数学运算没有成功, 这是失败后返回的结果”
==NaN是一个特殊值, 他和自身不相等,NaN != NaN为true ==
如何判断NaN?
- 通过全局工具函数isNaN(),作用是检查参数是否是NaN,也不是数字。该API存在bug,如下:
let a = 2 / 'foo'
let b = 'foo'
window.isNaN(a) // true
window.isNaN(b) // true 'foo'不是数字也不是NaN,所以返回true
- ES6可以使用Number.isNaN()
- 还可以利用它不等于自身的特性。
return n !== n
尽量使用Number.isNaN()这样可靠的方法。
5.5 无穷数
- 在JS中下例的结果为Infinity(即Number.POSITIVE INfINITY),与之对应的负无穷是Number.NEGATIVE INfINITY
var a = 1 / 0
- 无穷/无穷是一个未定义的操作,所以返回NaN
Infinity / Infinity = NaN
- 有穷正数除以Infinity呢?很简单,结果是0。有穷负数除以Infinity呢?结果是-0。
5.6 零值
JS中有一个常规的0和一个-0 加法和减法运算不会得到负零
此外值得注意的是:将数字-0转为字符串后会变为'0',将字符串'-0'转为数字则是-0
JSON.stringify(-0)返回"0",而JSON.parse("-0")返回-0
如何区分0和-0?
- 0 === -0 会返回true,无法区分
可以自己写polyfill,如下:
function isNegZero(n) {
n = Number(n)
return (n == 0) && (1 / n == -Infinity)
}
5.7 Object.is()
ES6中用来判断两个值是否绝对相等的API。
var a = 1 / 'foo'
var b = 1 * -0
Object.is(a, NaN) // true
Object.is(b, -0) // true
Object.is(b, 0) // false
能使用
==和===时就尽量不要使用Object.is(..),因为前者效率更高、更为通用。Object.is(..)主要用来处理那些特殊的相等比较。
6. Null和Undefined
- null值曾经有过值,但目前没有值
- undefined指从未赋值
6.1 void运算符
通过void运算符可以返回undefined
表达式void ___ 没有返回值,因此返回结果undefined,void并不改变表达式的结果,只是让表达式不返回值。
let a = 10
console.log(void a, a) // undefined, 10
7. 值和引用
JavaScript中没有指针,引用的工作机制也不尽相同。在JavaScript中变量不可能成为指向另一个变量的引用。
JavaScript引用指向的是值。如果一个值有10个引用,这些引用指向的都是同一个值,它们相互之间没有引用/指向关系。
JavaScript对值和引用的赋值/传递在语法上没有区别,完全根据值的类型来决定。
简单值(即标量基本类型值,scalar primitive)总是通过值复制的方式来赋值/传递,包括null、undefined、字符串、数字、布尔和ES6中的symbol。
复合值(compound value)——对象(包括数组和封装对象,参见第3章)和函数,则总是通过引用复制的方式来赋值/传递。
function foo(x) {
x.push( 4 );
x; // [1,2,3,4]
// 然后
x = [4,5,6];
x.push( 7 );
x; // [4,5,6,7]
}
var a = [1,2,3];
foo( a );
a; // 是[1,2,3,4],不是[4,5,6,7]
我们向函数传递a的时候,实际是将引用a的一个复本赋值给x,而a仍然指向[1,2,3]。在函数中我们可以通过引用x来更改数组的值(push(4)之后变为[1,2,3,4])。但x =[4,5,6]并不影响a的指向,所以a仍然指向[1,2,3,4]。
我们不能通过引用x来更改引用a的指向,只能更改a和x共同指向的值。
请记住:我们无法自行决定使用值复制还是引用复制,一切由值的类型来决定。