1. undefined和undeclared
访问未被声明的变量,会报ReferenceError
,
var a;
a; // undefined
b; // Uncaught ReferenceError: b is not defined
但是typeof
运算符并不会报错,
对于值为undefined
的变量和未被声明的变量,都会返回一个字符串'undefined'
。
var a;
typeof a; // 'undefined'
typeof b; // 'undefined'
在最外层作用域中,检测一个变量是否被定义,除了使用typeof b;
之外,
也可以使用window.hasOwnProperty('b');
,
但是typeof
更具通用性,它还可以用来检测当前词法环境中是否声明过某个变量。
function f(){
var a;
function g(){
typeof b; // 'undefined'
// 这里就无法使用window.hasOwnProperty来检测了
// ...
}
g();
}
f();
2. 数组的空白单元
以下数组a
的第0
个元素是空白单元(empty slot)。
a = [];
a[1] = undefined;
a; // [empty, undefined]
a[0]; // undefined
a[1]; // undefined
a.hasOwnProperty(0); // false
a.hasOwnProperty(1); // true
0 in a; // false
1 in a; // true
2.1 创建空白单元的其他办法
(1)使用Array
构造器来创建的数组,其元素也是空白单元,
a = Array(3);
a; // [empty × 3]
(2)还有一个办法是使用[,,,]
来创建空白单元,
a = [,,,]; // 由于最后一个逗号会被省略
a; // [empty × 3]
(3)修改数组的length
属性也可以创建空白单元,
a = [];
a.length = 3;
a; // [empty × 3]
2.2 空白单元的性质
(1)空白单元的值为undefined
(2)数组对该下标的hasOwnProperty
为false
(3)使用in
运算符检测数组的对象属性,返回false
(4)forEach
,filter
,every
,some
,map
,迭代时,会忽略空白单元
(5)for...of
会遍历空白单元
(6)Array.from
,扩展运算符...
,keys
,values
,entries
,会将空白单元转为undefined
(7)join
,toString
会将空白视为空字符串
3. 数组的非数值索引
a = [0,1];
a['x'] = 2;
a; // [0, 1, x: 2]
a.length; // 2
为数组添加非数值索引,并不会自动调整数组的长度。
该索引,会变成数组的对象属性。
值得注意的是,如果非数值索引,可以被转为非NaN
的正数的话,
就相当于给数组添加一个转换后的数值索引。
a = [0,1];
a['2'] = 2; // Number('2') -> 2
a; // [0, 1, 2]
a.length; // 3
a['3y'] = 3; // Number('3y') -> NaN
a; // [0, 1, 2, 3y: 3]
a.length; // 3
a['-4'] = 4;
a; // [0, 1, 2, 3y: 3, -4: 4]
a.length; // 3
注:
(1)在写法上,Number(x)
相当于+x
,
(2)NaN
是唯一一个与自己都不===
的值,即,
NaN !== NaN // true
而window.isNaN
是有问题的,
window.isNaN(NaN); // true
window.isNaN('x'); // true,这里也为true
4. 数值后面的点
(1)数值前面的0
可以省略,
a = 0.42; // 0.42
a = .42; // 0.42
(2)数值后面的点可以省略
a = 42.0; // 42
a = 42.; // 42
注意,42.0
和42.
结果都是整数42
。
此外,数字后面如果写了.
,那么后面的字符会优先考虑为它的小数部分,
42.toFixed(3); // Uncaught SyntaxError: Invalid or unexpected token
42..toFiexed(3); // '42.000'
42 .toFixed(3); // '42.000'
对于42.toFixed(3);
,由于.
后面的t
不是一个合法的数字,所以就报语法错了。
而42..toFiexed(3);
相当于(42.).toFiexed(3);
,是符合语法的表达式。
此外42 .toFixed(3);
也是符合语法的,注意42
后面有一个空格。
5. Number.EPSILON
Number.EPSILON
是ES6引入表示的机器精度(machine epsilon)的数值,
它的大小为,Math.pow(2,-52)
,即,2.220446049250313e-16
。
使用它可以在判断两个浮点数,在机器精度的误差范围内是否相等,
a = 0.1;
b = 0.2;
a + b === 0.3; // false
a + b; // 0.30000000000000004
a + b - 0.3 < Number.EPSILON; // true
6. 整数的安全范围
Number.MAX_SAFE_INTEGER; // 9007199254740991,即,Math.pow(2,53)-1
Number.MIN_SAFE_INTEGER; // -9007199254740991
Number.MAX_SAFE_INTEGER + 1; // 9007199254740992
Number.MAX_SAFE_INTEGER + 2; // 9007199254740992
Number.MAX_SAFE_INTEGER + 3; // 9007199254740994
Number.MAX_SAFE_INTEGER + 4; // 9007199254740996
Number.isSafeInteger(Number.MAX_SAFE_INTEGER); // true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1); // false
Number.MIN_SAFE_INTEGER
和Number.MAX_SAFE_INTEGER
界定了安全整数的范围,
而Number.isSafeInteger
是ES6添加的,用于判断一个整数是否安全的方法。
7. 正负无穷,正负零
1/0; // Infinity
-1/0; // -Infinity
Number.POSITIVE_INFINITY === Infinity; // true
Number.NEGATIVE_INFINITY === -Infinity; // true
Infinity === Infinity; // true
Infinity === -Infinity; // false
0/1; // 0
0/-1; // -0
0 === -0; // true
1/0; // Infinity
1/-0; // -Infinity
我们看到正零和负零是相等的,0 === -0
,因此只能通过Infinity
来进行区分,
1/0===Infinity
而1/-0===-Infinity
,
Infinity
与-Infinity
是不相等的。
注:
ES6新增了Object.is,
来判断两个值是否为相同(the same value)。
Object.is(0, -0); // false
Object.is(-0, -0); // true
Object.is(NaN, NaN); // true
8. 值的复制和引用
原始值,包含除了Object
类型之外的所有其他值。
即,Null
,Undefined
,Boolean
,Number
,String
,Symbol
类型的值。
原始值的传递方式是复制,例如,
a = 1;
b = a;
b++;
a; // 1
b; // 2
Object
类型的值传递方式是引用,例如,
a = [];
b = a;
b.push(1);
a; // [1]
b; // [1]
8.1 构造函数的返回值
我们知道,如果一个函数不返回值,则相当于返回undefined
,
而如果一个构造函数返回了一个原始值,则会丢弃该原始值,返回this
。
相反,如果一个构造函数返回了一个Object
类型的值,则会丢弃this
。
function F(){
return 1;
}
obj = new F; // F {},即,F的实例
function F(){
return [1];
}
obj = new F; // [1],丢弃this,obj不是F的实例
8.2 对原始值调用Object(...)
,可以获取对应的包装对象
下面拿Symbol
类型的值(原始值),进行介绍。
首先需要了解的是,Symbol('x')
和Symbol.for('x')
,创建了两种不同的symbol原始值,
Symbol('x') === Symbol('x'); // false
Symbol.for('x') === Symbol.for('x'); // true
Symbol.for('x')
会创建全局唯一的符号,而Symbol('x')
每次都会创建一个新符号,
虽然他们的字符串表示都是'Symbol(x)'
。
其次,Symbol
类型值的包装对象,不能通过new Symbol
来获得,
new Symbol('x'); // Uncaught TypeError: Symbol is not a constructor
只能通过Object(symbol)
来获得,
s = Symbol.for('x');
obj = Object(s); // Symbol {Symbol(x)},obj是Symbol构造函数的实例
obj instanceof Symbol; // true
最后,在非严格模式下,this
指向的原始值会自动转换为它的包装对象,
function f(){
return this;
}
a = 1;
obj = f.call(sa; // Number {1},obj是Number的实例
obj instanceof Number; // true
即,这里隐式调用了Object()
方法。
原始值 | Object(...) | valueOf(...) |
---|---|---|
null | new Object | new Object |
undefined | new Object | new Object |
1 | new Number(1) | 1 |
'x' | new String('x') | 'x' |
true | new Boolean(true) | true |
Symbol.for('x') | Object(Symbol.for('x')) | Symbol(x) |
如上表,除了null
和undefined
之外,
Object(...)
可以将原始值转换为对应的包装对象,
而valueOf
可以将包装对象转换为对应的原始值。
8.3 toPrimitive,valueOf和toString
Symbol.toPrimitive
是一个语言内置的符号,
当一个对象需要转换为原始值的时候,首先会调用它自己的Symbol.toPrimitive
方法。
例如,我们可以为对象定义一个Symbol.toPrimitive
方法,
来影响它被转换为原始值的方式。
obj1 = {};
+obj1; // NaN
`${obj1}`; // '[object Object]'
obj1+''; // '[object Object]'
obj1[Symbol.toPrimitive]; // undefined
obj1[Symbol.toPrimitive] = function(hint){
switch(hint){
case 'number':
return 1;
case 'string':
return 'x';
default:
return hint;
}
};
+obj1; // 1,hint === 'number'
`${obj1}`; // 'x',hint === 'string'
obj1 + ''; // 'default',hint === 'default'
obj1 + 0; // 'default',hint === 'default'
如果Symbol.toPrimitive
方法没有被定义,就会调用toString
或valueOf
来获取原始值,
当hint
是string
时,会依次调用toString
,以及valueOf
,直到返回一个非Object
的值为止。
当hint
是default
或number
时,会依次调用valueOf
,以及toString
,直到返回一个非Object
的值为止。
obj1 = {};
obj1.toString = function(){
return true;
};
obj1.valueOf = function(){
return false;
};
+obj1; // 0,hint === 'number',调用valueOf返回false,再+false为0
`${obj1}`; // 'true',hint === 'string',调用toString返回true,再`${true}`为'true'
obj1 + ''; // 'false',hint === 'default',调用valueOf返回false,再false+''为'false'
obj1 + 0; // 'false',hint === 'default',调用valueOf返回false,再false+0为0
参考
你不知道的JavaScript(中卷)
MDN: Symbol
MDN: Symbol.toPrimitive