本篇文章主要介紹 JavaScript 中幾個常用的內(nèi)置值類型旁舰。
1. 數(shù)組
JavaScript 中,數(shù)組可以容納任意類型的值嗡官,可以是 string
箭窜、number
、object
衍腥,也可以是其他數(shù)組(多維數(shù)組)磺樱,聲明數(shù)組后加入值也不需要預先設(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 empty
和 undefined
稀疏數(shù)組在控制臺中的表示如下:
var a = new Array(5);
console.log(a); // [empty × 5]
empty × 5
即表示數(shù)組有 5 個空缺單元。但是 empty
并非 JavaScript 的基本數(shù)據(jù)類型块差,當嘗試訪問數(shù)組中的元素時侵续,JavaScript 會返回一個 undefined
,這是因為 JavaScript 引擎在發(fā)現(xiàn)元素缺失時會臨時賦值 undefined
憨闰。
console.log(a[0]); // undefined
但是 empty
和 undefined
表示的并不是一個含義状蜗,empty
表示的是當前數(shù)組元素沒有值,空缺的鹉动;而 undefined
則表示當前元素是存在值的轧坎,并且值是 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
、some
边琉、every
方法
這些方法在遍歷到稀疏數(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
方法則比較特殊爷辱,可以理解為當數(shù)組為空時,只會返回 false;而當數(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ù)組元素浪谴,所以速度會非晨快。
如下是一個 jsperf 測試:
// Sparse Array
var a = [];
a[10000] = 1;
a.forEach(function(){});
// Dense Array
var b = Array.from(a);
b.forEach(function(){});
可見密集數(shù)組的訪問性能明顯比稀疏數(shù)組的高苟耻,因此建議日常編碼中能避免稀疏數(shù)組的盡量避免篇恒。
1.2 類數(shù)組
類數(shù)組的兩個條件:
具有:指向?qū)ο笤氐臄?shù)字索引下標以及
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ù)都是在其原始值上進行操作抹恳。
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ù)的十進制數(shù)健霹。
JavaScript 的數(shù)字類型是基于 IEEE 754 標準實現(xiàn)旺上,并且使用的是雙精度格式,即 64 位二進制糖埋。
雙精度浮點格式(64 位):1 位符號位宣吱、11 位有效數(shù)字位和** 52 位指數(shù)位**。
由于 JavaScript 的數(shù)字值可以使用 Number
對象進行封裝瞳别,因此可以直接調(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ōu)先識別為數(shù)字常量的一部分,然后才是對象屬性訪問運算符馆铁。
42.toFixed( 3 ); // SyntaxError
但是下面的語法是有效的(注意其中的空格):
(42).toFixed(3); // "42.000"
十六進制:0xf3
or 0Xf3
八進制:0o363
or 0O363
二進制:0b11110011
or 0B11110011
以上進制的前綴盡量使用小寫字母跑揉。
4.1 較小的數(shù)字
從數(shù)學角度下面的計算應(yīng)該為 true,但是結(jié)果為 false埠巨。這是因為數(shù)值的運算都會轉(zhuǎn)換為二進制历谍,而小數(shù)部分的二進制有些數(shù)字無法精準表示出來,在有限的位數(shù)下辣垒,就會進行誤差取舍望侈,所以導致最終結(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
召娜、Number
、Boolean
惊楼、Array
玖瘸、Object
秸讹、Fuction
、RegExp
雅倒、Date
璃诀、Error
、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)和正則表達式持寄,我們通常喜歡以常 量的形式來創(chuàng)建它們源梭。實際上,使用常量和使用構(gòu)造函數(shù)的效果是一樣的(創(chuàng)建的值都是通過封裝對象來包裝)稍味。
5.4.1 Array(...)
構(gòu)造函數(shù) Array()
不要求帶 new 關(guān)鍵字废麻,不帶的時候會自動補上。因此 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ù)的話則使用當前的日期和時間。
構(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迈喉。
這是因為不同的對象在底層都表示為二進制绍刮,在 JavaScript 中二進制前三位都為 0 的話會被判 斷為 object
類型,
null 的二進制表示全部都是 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ù)組進行使用 in
操作符時置逻,檢查的不是數(shù)組的值窍帝,而是數(shù)組的下標。
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)中眠冈。原因是“可枚舉”就相當于“可以出現(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
}