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












网友评论