[JS] typeof empty-slot 0 -0 toPrimitive

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)数组对该下标的hasOwnPropertyfalse
(3)使用in运算符检测数组的对象属性,返回false

(4)forEachfiltereverysomemap,迭代时,会忽略空白单元
(5)for...of会遍历空白单元

(6)Array.from,扩展运算符...keysvaluesentries,会将空白单元转为undefined
(7)jointoString会将空白视为空字符串


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.042.结果都是整数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_INTEGERNumber.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===Infinity1/-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类型之外的所有其他值。
即,NullUndefinedBooleanNumberStringSymbol类型的值。

原始值的传递方式是复制,例如,

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)

如上表,除了nullundefined之外,
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方法没有被定义,就会调用toStringvalueOf来获取原始值,
hintstring时,会依次调用toString,以及valueOf,直到返回一个非Object的值为止。
hintdefaultnumber时,会依次调用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

你可能感兴趣的:([JS] typeof empty-slot 0 -0 toPrimitive)