JavaScript 值

本篇文章主要介紹 JavaScript 中幾個常用的內(nèi)置值類型。

1. 數(shù)組

JavaScript 中,數(shù)組可以容納任意類型的值,可以是 string、number、object,也可以是其他數(shù)組(多維數(shù)組),聲明數(shù)組后加入值也不需要預(yù)先設(shè)定數(shù)組的長度大小。

1.1 稀疏數(shù)組

稀疏數(shù)組(sparse array)是指索引不連續(xù),數(shù)組長度大于元素個數(shù)的數(shù)組,通俗說就是含有空白或空缺單元的數(shù)組。

1.1.1 稀疏數(shù)組生成方式

// 構(gòu)造函數(shù)聲明一個沒有元素的數(shù)組
var a = new Array(5); // [empty × 5]

// 指定的索引值大于數(shù)組長度
var a = [];
a[5] = 4; // [empty × 5, 4]

// 指定大于元素個數(shù)的數(shù)組長度
var a = [];
a.length = 5; // [empty × 5]

// 數(shù)組直接量中省略值
var a = [0, , , ,]; // [0, empty × 3]

// 刪除數(shù)組元素
var a = [0, 1, 2, 3, 4];
delete a[4]; // [0, 1, 2, 3, empty]

1.1.2 emptyundefined

稀疏數(shù)組在控制臺中的表示如下:

var a = new Array(5);
console.log(a); // [empty × 5]

empty × 5 即表示數(shù)組有 5 個空缺單元。但是 empty 并非 JavaScript 的基本數(shù)據(jù)類型,當(dāng)嘗試訪問數(shù)組中的元素時,JavaScript 會返回一個 undefined,這是因為 JavaScript 引擎在發(fā)現(xiàn)元素缺失時會臨時賦值 undefined。

console.log(a[0]); // undefined

但是 emptyundefined 表示的并不是一個含義,empty 表示的是當(dāng)前數(shù)組元素沒有值,空缺的;而 undefined 則表示當(dāng)前元素是存在值的,并且值是 undefined。如下:

var a = new Array(5);
var b = [undefined, undefined, undefined];

a.forEach(i => { console.log(i) });    // 無 log 輸出,無元素不會執(zhí)行回調(diào)
b.forEach(i => { console.log(i) });    // undefined undefined undefined

1.1.3 稀疏數(shù)組的遍歷

  • forEach、filter、someevery 方法

這些方法在遍歷到稀疏數(shù)組的缺失元素時,回調(diào)函數(shù)不會執(zhí)行。

var a = [1,,,,];
a.forEach(i => { console.log(i) }); // 只會打印一次 1
  • for...in

for-in 語句只會遍歷對象的可枚舉屬性,不會遍歷稀疏數(shù)組中的缺失元素。

var a = [1,,,,5];
for (var i in a) { console.log(a[i]) };    // 1 5
  • for...of、for 循環(huán)、find、finedIndex 方法

for...of 和 for 循環(huán)都會將空缺元素的值替換為 undefined。find、finedIndex 是通過 for 循環(huán)實現(xiàn)的,所以同 for 循環(huán)。

var a = [1,,,,5];
for (var i of a) { console.log(i) };       // 1 undefined undefined undefined 5
  • includes 方法

includes 方法則比較特殊,可以理解為當(dāng)數(shù)組為空時,只會返回 false;而當(dāng)數(shù)組非空(指長度不為0的數(shù)組,其中包括全部元素都缺失的數(shù)組),且函數(shù)調(diào)用參數(shù)為空時會返回 true。

var a = [1,,,,];
var b = new Array(5);
var c = [];

a.includes();                         // true
b.includes();                         // true
c.includes();                         // false
  • map 方法

不會遍歷缺失元素,但返回的結(jié)果具有與源數(shù)組相同的長度和空隙。

var a = [1,,,,5];
a.map(i => i);       // [1, empty × 3, 5]
  • sort 方法

不會遍歷缺失元素,數(shù)組能正常排序,同時會返回與源數(shù)組相同的長度。

var a = [5,,,,1];
a.sort();    // [1, 5, empty × 3]
  • join 方法

缺失元素占的坑還是會被保留。

var a = new Array(5);
a.join();    // ",,,,"

1.1.4 稀疏數(shù)組轉(zhuǎn)密集數(shù)組

可以通過如下兩個方法實現(xiàn),轉(zhuǎn)換規(guī)則是將空缺元素使用 undefined 代替:

// 稀疏數(shù)組
var a = new Array(5);

Array.apply(null, a);    // ES5 [undefined, undefined, undefined, undefined, undefined]
Array.from(a);           // ES6 [undefined, undefined, undefined, undefined, undefined]

1.1.5 稀疏數(shù)組特性

稀疏數(shù)組跟密集數(shù)組相比具有以下特性:

  • 訪問速度慢
  • 內(nèi)存利用率高

這與 V8 引擎構(gòu)建 JS 對象的方式有關(guān)。V8 訪問對象有兩種模式:字典模式快速模式。

稀疏數(shù)組使用的是字典模式,也稱為散列表模式,該模式下 V8 使用散列表來存儲對象屬性。由于每次訪問時都需要計算哈希值(實際上只需要計算一次,哈希值會被緩存)和尋址,所以訪問速度非常慢。另一方面,對比起使用一段連續(xù)的內(nèi)存空間來存儲稀疏數(shù)組,散列表的方式會大幅度地節(jié)省內(nèi)存空間。

而密集數(shù)組在內(nèi)存空間中是被存儲在一個連續(xù)的類數(shù)組里,引擎可以直接通過數(shù)組索引訪問到數(shù)組元素,所以速度會非??臁?/p>

如下是一個 jsperf 測試:

// Sparse Array
var a = [];
a[10000] = 1;
a.forEach(function(){});

// Dense Array
var b = Array.from(a);
b.forEach(function(){});
稀疏數(shù)組.png

可見密集數(shù)組的訪問性能明顯比稀疏數(shù)組的高,因此建議日常編碼中能避免稀疏數(shù)組的盡量避免。

1.2 類數(shù)組

類數(shù)組的兩個條件

  • 具有:指向?qū)ο笤氐臄?shù)字索引下標(biāo)以及 length 屬性告訴我們對象的元素個數(shù)

  • 不具有:諸如 push、forEach 以及 indexOf 等數(shù)組對象具有的方法

1.2.1 類數(shù)組:NodeList

如下,通過 querySelectorAll 獲取到的 NodeList,有 length,可以通過下表訪問到具體的元素,不能調(diào)用數(shù)組的方法。所以它就是一個類數(shù)組。

const arrayLike = document.querySelectorAll("div");

console.log(Object.prototype.toString.call(arrayLike)); // [object NodeList]

console.log(arrayLike.length); // 127

console.log(arrayLike[0]);
// <div id="js-pjax-loader-bar" class="pjax-loader-bar"></div>

console.log(Array.isArray(arrayLike)); // false

arrayLike.push("push");
// Uncaught TypeError: arrayLike.push is not a function(…)

1.2.2 類數(shù)組對象

如下就是通過一個對象創(chuàng)建出來的類數(shù)組。

const arrayLikeObj = {
  length: 2,
  0: "This is Array Like Object",
  1: true,
};

1.2.3 類數(shù)組函數(shù)

const arrayLikeFunc1 = function () {};
console.log(arrayLikeFunc1.length); // 0
const arrFunc1 = Array.prototype.slice.call(arrayLikeFunc1, 0);
console.log(arrFunc1, arrFunc1.length); // [], 0

1.2.4 類數(shù)組轉(zhuǎn)化為數(shù)組

// 數(shù)組slice方法
Array.prototype.slice.call(arrLike);

// Array.from
Array.from(arrLike);

2. 字符串

字符串和數(shù)組的確很相似,它們都是類數(shù)組,都有 length 屬性以及 indexOf(..)concat(..) 方法

var a = "foo";
var b = ["f", "o", "o"];

a.length; // 3
b.length; // 3

a.indexOf("o"); // 1
b.indexOf("o"); // 1

a.concat("bar"); // foobar
b.concat(["b", "a", "r"]); // ["f","o","o","b","a","r"]

JavaScript 中字符串是不可變的,而數(shù)組是可變的。

var a = "foo";
var b = ["f", "o", "o"];

a[1] = "O";
b[1] = "O";

a; // "foo",不可改變
b; // ["f","O","o"],可變

字符串不可變是指字符串的成員函數(shù)不會改變其原始值,而是創(chuàng)建并返回一個新的字符串。而數(shù)組的成員函數(shù)都是在其原始值上進(jìn)行操作。

var a = "foo";

c = a.toUpperCase();
a === c; // false
a; // "foo"
c; // "FOO"

有些數(shù)組非變更的函數(shù)(不會改變原數(shù)組),可以用來處理字符串,如下:

var a = "foo";

a.join; // undefined
a.map; // undefined

var c = Array.prototype.join.call(a, "-");
var d = Array.prototype.map
  .call(a, function (v) {
    return v.toUpperCase() + ".";
  })
  .join("");

c; // "f-o-o"
d; // "F.O.O."

3. 數(shù)字

JavaScript 只有一種數(shù)值類型:number,它可以表示整數(shù)和帶小數(shù)的十進(jìn)制數(shù)。

JavaScript 的數(shù)字類型是基于 IEEE 754 標(biāo)準(zhǔn)實現(xiàn),并且使用的是雙精度格式,即 64 位二進(jìn)制。

雙精度浮點(diǎn)格式(64 位):1 位符號位11 位有效數(shù)字位和** 52 位指數(shù)位**。

由于 JavaScript 的數(shù)字值可以使用 Number 對象進(jìn)行封裝,因此可以直接調(diào)用 Number.prototype 中的方法。

(42).toFixed(3); // "42.000"
(0.42).toFixed(3); // "0.420"
(42).toFixed(3); // "42.000"

如下是無效語法,因為 . 被看作為 42. 的一部分,所以沒有 . 屬性訪問符來調(diào)用 toFixed 方法。. 運(yùn)算符會被優(yōu)先識別為數(shù)字常量的一部分,然后才是對象屬性訪問運(yùn)算符。

42.toFixed( 3 ); // SyntaxError

但是下面的語法是有效的(注意其中的空格):

(42).toFixed(3); // "42.000"

十六進(jìn)制0xf3 or 0Xf3

八進(jìn)制0o363 or 0O363

二進(jìn)制0b11110011 or 0B11110011

以上進(jìn)制的前綴盡量使用小寫字母。

4.1 較小的數(shù)字

從數(shù)學(xué)角度下面的計算應(yīng)該為 true,但是結(jié)果為 false。這是因為數(shù)值的運(yùn)算都會轉(zhuǎn)換為二進(jìn)制,而小數(shù)部分的二進(jìn)制有些數(shù)字無法精準(zhǔn)表示出來,在有限的位數(shù)下,就會進(jìn)行誤差取舍,所以導(dǎo)致最終結(jié)果的不精確。

0.1 + 0.2 === 0.3; // false

解決此問題最常見的做法就是設(shè)置一個誤差范圍值,通常是 2^-52,這個值在 JavaScript 中被定義在Number.EPSILON 中,或者使用 Math.pow(2,-52)

function numbersCloseEnoughToEqual(n1, n2) {
  return Math.abs(n1 - n2) < Number.EPSILON;
}

numbersCloseEnoughToEqual(0.1 + 0.2, 0.3); // true
numbersCloseEnoughToEqual(0.0000001, 0.0000002); // false

4.2 整數(shù)的安全范圍

能夠被“安全”呈現(xiàn)的最大整數(shù)是 2^53 - 1,即 9007199254740991,在 ES6 中被定義為 Number.MAX_SAFE_INTEGER。
最小整數(shù)是 -9007199254740991, 在 ES6 中 被 定 義 為 Number.MIN_SAFE_INTEGER。

超過此范圍的值,應(yīng)該轉(zhuǎn)換為字符串展示,或者借助相關(guān)的工具庫。

對于數(shù)位操作,最大支持 32 位的數(shù)字,超過 32 位的將會被忽略

5. 原生函數(shù)

JavaScript 的內(nèi)建函數(shù)(built-in function),也叫原生函數(shù)(native function),常用的原生函數(shù)如下:

String、NumberBoolean、Array、Object、FuctionRegExp、DateError、Symbol。

5.1 內(nèi)部屬性 [[Class]]

所有 typeof 返回值為 "object" 的對象(如數(shù)組)都包含一個內(nèi)部屬性 [[Class]],可以看作為一個內(nèi)部的分類。這個屬性一般無法直接訪問,一般通過 Object.prototype.toString 來查看,如下:

Object.prototype.toString.call([1, 2, 3]); // "[object Array]"

Object.prototype.toString.call(/regex-literal/i); // "[object RegExp]"

5.2 封裝對象

由于基本類型沒有屬性和方法,需要通過封裝對象才能訪問,此時 JavaScript 會自動為基本類型值包裝一個封裝對象:

var a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"

如果需要經(jīng)常用到這些字符串屬性和方法,比如在 for 循環(huán)中使用 i < a.length,那么從 一開始就創(chuàng)建一個封裝對象也許更為方便,這樣 JavaScript 引擎就不用每次都自動創(chuàng)建了。

但實際證明這并不是一個好辦法,因為瀏覽器已經(jīng)為 .length 這樣的常見情況做了性能優(yōu)化,直接使用封裝對象來“提前優(yōu)化”代碼反而會降低執(zhí)行效率。

一般情況下,我們不需要直接使用封裝對象。最好的辦法是讓 JavaScript 引擎自己決定什 么時候應(yīng)該使用封裝對象。換句話說,就是應(yīng)該優(yōu)先考慮使用 "abc" 和 42 這樣的基本類型 值,而非 new String("abc")new Number(42)。

5.3 拆封對象

如果想要得到封裝對象中的基本類型值,可以使用 valueOf() 函數(shù):

var a = new String("abc");
var b = new Number(42);
var c = new Boolean(true);

a.valueOf(); // "abc"
b.valueOf(); // 42
c.valueOf(); // true

在需要用到封裝對象中的基本類型值的地方會發(fā)生隱式拆封,如下:

var a = new String("abc");
var b = a + ""; // b的值為"abc"

typeof a; // "object"
typeof b; // "string"

5.4 構(gòu)造函數(shù)

關(guān)于數(shù)組(array)、對象(object)、函數(shù)(function)和正則表達(dá)式,我們通常喜歡以常 量的形式來創(chuàng)建它們。實際上,使用常量和使用構(gòu)造函數(shù)的效果是一樣的(創(chuàng)建的值都是通過封裝對象來包裝)。

5.4.1 Array(...)

構(gòu)造函數(shù) Array() 不要求帶 new 關(guān)鍵字,不帶的時候會自動補(bǔ)上。因此 Array(1, 2, 3)new Array(1, 2, 3)
效果是一樣的。

需要注意的是,Array 只有一個數(shù)字參數(shù)的時候,創(chuàng)建出來的是該數(shù)字長度的空數(shù)組,多個數(shù)字的時候,創(chuàng)建的則是擁有這些數(shù)字的數(shù)組。

5.4.2 Object(...)、Function(...)、RegExp(...)

除非萬不得已,否則盡量不要使用 Object(..)/Function(..)/RegExp(..)

5.4.3 Date(...) 和 Error(...)

創(chuàng)建日期對象必須使用 new Date()。Date(...)可以帶參數(shù),用來指定日期和時間,而不帶 參數(shù)的話則使用當(dāng)前的日期和時間。

構(gòu)造函數(shù) Error(...)(與前面的 Array() 類似)帶不帶 new 關(guān)鍵字都可。

5.4.4 Symbol(...)

Symbol 比較特殊,不能帶 new 關(guān)鍵字,否則會出錯。

6. 對象

6.1 語言 BUG null

通過 typeof null 的結(jié)果是 object,但是 null 本身是一個基本類型,這是 JavaScript 語言中的一個 BUG。

這是因為不同的對象在底層都表示為二進(jìn)制,在 JavaScript 中二進(jìn)制前三位都為 0 的話會被判 斷為 object 類型,
null 的二進(jìn)制表示全部都是 0,自然前三位也是 0,所以執(zhí)行 typeof 時會返回“object”。

6.2 對象屬性的存在性

6.2.1 屬性訪問

通過屬性訪問返回值是否是 undefined,可以判斷屬性是否存在,但是這個屬性也有可能存儲的就是 undefined,此時就沒法區(qū)分了。

6.2.2 in 操作符

in 操作符會檢查屬性是否在對象及其 [[Prototype]] 原型鏈中。

var myObject = { a: 2 };

"a" in myObject; // true
"b" in myObject; // false

這里需要注意的是,如果對數(shù)組進(jìn)行使用 in 操作符時,檢查的不是數(shù)組的值,而是數(shù)組的下標(biāo)。

6.2.3 hasOwnProperty 方法

hasOwnProperty(..) 只會檢查屬性是否在對象中,不會檢查 [[Prototype]] 鏈。

var myObject = { a: 2 };

myObject.hasOwnProperty("a"); // true
myObject.hasOwnProperty("b"); // false

6.2.4 propertyIsEnumerable 方法

propertyIsEnumerable(..) 會檢查給定的屬性名是否直接存在于對象中(而不是在原型鏈上)并且滿足 enumerable: true。

6.2.5 Object.keys(..)Object.getOwnPropertyNames(..)

這兩個方法都只會查找對象直接包含的屬性。

6.2.6 可枚舉性

從下面代碼可以看出來,myObject.b 確實存在并且有訪問值,但是卻不會出現(xiàn)在 for..in 循環(huán)中。原因是“可枚舉”就相當(dāng)于“可以出現(xiàn)在對象屬性的遍歷中”。

var myObject = {};
Object.defineProperty(
  myObject,
  "a"
  // 讓 a 像普通屬性一樣可以枚舉 { enumerable: true, value: 2 }
);
Object.defineProperty(
  myObject,
  "b",
  // 讓b不可枚舉
  { enumerable: false, value: 3 }
);
myObject.b; // 3
"b" in myObject; // true
myObject.hasOwnProperty("b"); // true
// .......
for (var k in myObject) {
  console.log(k, myObject[k]); // "a" 2
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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