[JS] typeof empty-slot 0 -0 toPrimitive

1. undefined和undeclared

訪問未被聲明的變量,會(huì)報(bào)ReferenceError

var a;

a;  // undefined
b;  // Uncaught ReferenceError: b is not defined

但是typeof運(yùn)算符并不會(huì)報(bào)錯(cuò),
對(duì)于值為undefined的變量和未被聲明的變量,都會(huì)返回一個(gè)字符串'undefined'

var a;

typeof a;  // 'undefined'
typeof b;  // 'undefined'

在最外層作用域中,檢測(cè)一個(gè)變量是否被定義,除了使用typeof b;之外,
也可以使用window.hasOwnProperty('b');,
但是typeof更具通用性,它還可以用來檢測(cè)當(dāng)前詞法環(huán)境中是否聲明過某個(gè)變量。

function f(){
    var a;

    function g(){
        typeof b;  // 'undefined'
        // 這里就無法使用window.hasOwnProperty來檢測(cè)了

        // ...
    }

    g();
}

f();

2. 數(shù)組的空白單元

以下數(shù)組a的第0個(gè)元素是空白單元(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 創(chuàng)建空白單元的其他辦法

(1)使用Array構(gòu)造器來創(chuàng)建的數(shù)組,其元素也是空白單元,

a = Array(3);

a;  // [empty × 3]

(2)還有一個(gè)辦法是使用[,,,]來創(chuàng)建空白單元,

a = [,,,];  // 由于最后一個(gè)逗號(hào)會(huì)被省略

a;  // [empty × 3]

(3)修改數(shù)組的length屬性也可以創(chuàng)建空白單元,

a = [];
a.length = 3;

a;  // [empty × 3]

2.2 空白單元的性質(zhì)

(1)空白單元的值為undefined
(2)數(shù)組對(duì)該下標(biāo)的hasOwnPropertyfalse
(3)使用in運(yùn)算符檢測(cè)數(shù)組的對(duì)象屬性,返回false

(4)forEach,filterevery,somemap,迭代時(shí),會(huì)忽略空白單元
(5)for...of會(huì)遍歷空白單元

(6)Array.from,擴(kuò)展運(yùn)算符...,keysvalues,entries,會(huì)將空白單元轉(zhuǎn)為undefined
(7)jointoString會(huì)將空白視為空字符串


3. 數(shù)組的非數(shù)值索引

a = [0,1];
a['x'] = 2;

a;  // [0, 1, x: 2]
a.length;  // 2

為數(shù)組添加非數(shù)值索引,并不會(huì)自動(dòng)調(diào)整數(shù)組的長度。
該索引,會(huì)變成數(shù)組的對(duì)象屬性。

值得注意的是,如果非數(shù)值索引,可以被轉(zhuǎn)為非NaN正數(shù)的話,
就相當(dāng)于給數(shù)組添加一個(gè)轉(zhuǎn)換后的數(shù)值索引。

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)相當(dāng)于+x,
(2)NaN是唯一一個(gè)與自己都不===的值,即,

NaN !== NaN  // true

window.isNaN是有問題的,

window.isNaN(NaN);  // true
window.isNaN('x');  // true,這里也為true

4. 數(shù)值后面的點(diǎn)

(1)數(shù)值前面的0可以省略,

a = 0.42;  // 0.42
a = .42;  // 0.42

(2)數(shù)值后面的點(diǎn)可以省略

a = 42.0;  // 42
a = 42.;  // 42

注意,42.042.結(jié)果都是整數(shù)42。

此外,數(shù)字后面如果寫了.,那么后面的字符會(huì)優(yōu)先考慮為它的小數(shù)部分,

42.toFixed(3);  // Uncaught SyntaxError: Invalid or unexpected token
42..toFiexed(3);  // '42.000'
42 .toFixed(3);  // '42.000'

對(duì)于42.toFixed(3);,由于.后面的t不是一個(gè)合法的數(shù)字,所以就報(bào)語法錯(cuò)了。
42..toFiexed(3);相當(dāng)于(42.).toFiexed(3);,是符合語法的表達(dá)式。

此外42 .toFixed(3);也是符合語法的,注意42后面有一個(gè)空格。


5. Number.EPSILON

Number.EPSILON是ES6引入表示的機(jī)器精度(machine epsilon)的數(shù)值,
它的大小為,Math.pow(2,-52),即,2.220446049250313e-16。

使用它可以在判斷兩個(gè)浮點(diǎn)數(shù),在機(jī)器精度的誤差范圍內(nèi)是否相等,

a = 0.1;
b = 0.2;

a + b === 0.3;  // false
a + b;  // 0.30000000000000004

a + b - 0.3 < Number.EPSILON;  // true

6. 整數(shù)的安全范圍

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界定了安全整數(shù)的范圍,
Number.isSafeInteger是ES6添加的,用于判斷一個(gè)整數(shù)是否安全的方法。

7. 正負(fù)無窮,正負(fù)零

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

我們看到正零和負(fù)零是相等的,0 === -0,因此只能通過Infinity來進(jìn)行區(qū)分,
1/0===Infinity1/-0===-Infinity,
Infinity-Infinity是不相等的。

注:
ES6新增了Object.is,
來判斷兩個(gè)值是否為相同(the same value)。

Object.is(0, -0);  // false
Object.is(-0, -0);  // true
Object.is(NaN, NaN);  // true

8. 值的復(fù)制和引用

原始值,包含除了Object類型之外的所有其他值。
即,Null,Undefined,Boolean,Number,String,Symbol類型的值。

原始值的傳遞方式是復(fù)制,例如,

a = 1;
b = a;
b++;

a;  // 1
b;  // 2

Object類型的值傳遞方式是引用,例如,

a = [];
b = a;
b.push(1);

a;  // [1]
b;  // [1]

8.1 構(gòu)造函數(shù)的返回值

我們知道,如果一個(gè)函數(shù)不返回值,則相當(dāng)于返回undefined,
而如果一個(gè)構(gòu)造函數(shù)返回了一個(gè)原始值,則會(huì)丟棄該原始值,返回this

相反,如果一個(gè)構(gòu)造函數(shù)返回了一個(gè)Object類型的值,則會(huì)丟棄this。

function F(){
    return 1;
}

obj = new F;  // F {},即,F(xiàn)的實(shí)例
function F(){
    return [1];
}

obj = new F;  // [1],丟棄this,obj不是F的實(shí)例

8.2 對(duì)原始值調(diào)用Object(...),可以獲取對(duì)應(yīng)的包裝對(duì)象

下面拿Symbol類型的值(原始值),進(jìn)行介紹。
首先需要了解的是,Symbol('x')Symbol.for('x'),創(chuàng)建了兩種不同的symbol原始值,

Symbol('x') === Symbol('x');  // false
Symbol.for('x') === Symbol.for('x');  // true

Symbol.for('x')會(huì)創(chuàng)建全局唯一的符號(hào),而Symbol('x')每次都會(huì)創(chuàng)建一個(gè)新符號(hào),
雖然他們的字符串表示都是'Symbol(x)'

其次,Symbol類型值的包裝對(duì)象,不能通過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構(gòu)造函數(shù)的實(shí)例

obj instanceof Symbol;  // true

最后,在非嚴(yán)格模式下,this指向的原始值會(huì)自動(dòng)轉(zhuǎn)換為它的包裝對(duì)象,

function f(){
    return this;
}

a = 1;
obj = f.call(sa;  // Number {1},obj是Number的實(shí)例

obj instanceof Number;  // true

即,這里隱式調(diào)用了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(...)可以將原始值轉(zhuǎn)換為對(duì)應(yīng)的包裝對(duì)象,
valueOf可以將包裝對(duì)象轉(zhuǎn)換為對(duì)應(yīng)的原始值。

8.3 toPrimitive,valueOf和toString

Symbol.toPrimitive是一個(gè)語言內(nèi)置的符號(hào),
當(dāng)一個(gè)對(duì)象需要轉(zhuǎn)換為原始值的時(shí)候,首先會(huì)調(diào)用它自己的Symbol.toPrimitive方法。

例如,我們可以為對(duì)象定義一個(gè)Symbol.toPrimitive方法,
來影響它被轉(zhuǎn)換為原始值的方式。

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方法沒有被定義,就會(huì)調(diào)用toStringvalueOf來獲取原始值,
當(dāng)hintstring時(shí),會(huì)依次調(diào)用toString,以及valueOf,直到返回一個(gè)非Object的值為止。
當(dāng)hintdefaultnumber時(shí),會(huì)依次調(diào)用valueOf,以及toString,直到返回一個(gè)非Object的值為止。

obj1 = {};
obj1.toString = function(){
  return true;
};
obj1.valueOf = function(){
  return false;
};

+obj1;  // 0,hint === 'number',調(diào)用valueOf返回false,再+false為0
`${obj1}`;  // 'true',hint === 'string',調(diào)用toString返回true,再`${true}`為'true'
obj1 + '';  // 'false',hint === 'default',調(diào)用valueOf返回false,再false+''為'false'
obj1 + 0;  // 'false',hint === 'default',調(diào)用valueOf返回false,再false+0為0

參考

你不知道的JavaScript(中卷)
MDN: Symbol
MDN: Symbol.toPrimitive

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容