【沒人看系列】js 數(shù)據(jù)類型

本文為作者JavaScript學(xué)習(xí)筆記,只是加強(qiáng)個人理解和認(rèn)識。大部分概念與代碼參考了阮一峰 js 教程


基本

1. 語句

語句(statement)是為了完成某種任務(wù)而進(jìn)行的操作

語句和表達(dá)式的區(qū)別在于,前者主要為了進(jìn)行某種操作,一般情況下不需要返回值;后者則是為了得到返回值,一定會返回一個值。

2. 變量

變量是對“值”的具名引用。變量就是為“值”起名,然后引用這個名字,就等同于引用這個值。變量的名字就是變量名。

變量的聲明和賦值,是分開的兩個步驟
如果只是聲明變量而沒有賦值,則該變量的值是==undefined==。==undefined==是一個特殊的值,表示“無定義”。

變量提升

JavaScript 引擎的工作方式是,先解析代碼,獲取所有被聲明的變量,然后再一行一行地運(yùn)行。這造成的結(jié)果,就是所有的變量的聲明語句,都會被提升到代碼的頭部,這就叫做變量提升(hoisting)。

8.判斷

switch語句后面的表達(dá)式,與case語句后面的表示式比較運(yùn)行結(jié)果時,采用的是嚴(yán)格相等運(yùn)算符(===),而不是相等運(yùn)算符(==),這意味著比較時不會發(fā)生類型轉(zhuǎn)換。

7.do...while 循環(huán)

do...while循環(huán)與while循環(huán)類似,唯一的區(qū)別就是先運(yùn)行一次循環(huán)體,然后判斷循環(huán)條件。

不管條件是否為真,do...while循環(huán)至少運(yùn)行一次,這是這種結(jié)構(gòu)最大的特點(diǎn)。另外,while語句后面的分號注意不要省略。

數(shù)據(jù)類型概述

1. 簡介

js 有6種數(shù)據(jù)類型, es6 又新增了第7種symbol

布爾值

下列運(yùn)算符會返回布爾值:

  • 前置邏輯運(yùn)算符: ! (Not)

  • 相等運(yùn)算符:===!==,==!=

  • 比較運(yùn)算符:>,>=,<<=

    如果 JavaScript 預(yù)期某個位置應(yīng)該是布爾值,會將該位置上現(xiàn)有的值自動轉(zhuǎn)為布爾值。轉(zhuǎn)換規(guī)則是除了下面六個值被轉(zhuǎn)為false,其他值都視為true。

    • undefined
    • null
    • false
    • 0
    • NaN
    • ""''(空字符串)

注意,空數(shù)組([])和空對象({})對應(yīng)的布爾值,都是true

數(shù)值

NaN不等于任何值,包括它本身。

與數(shù)值相關(guān)的全局方法

parseInt()

parseInt方法用于將字符串轉(zhuǎn)為整數(shù)。

parseInt('123') // 123

如果parseInt的參數(shù)不是字符串,則會先轉(zhuǎn)為字符串再轉(zhuǎn)換。

parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1

字符串轉(zhuǎn)為整數(shù)的時候,是一個個字符依次轉(zhuǎn)換,如果遇到不能轉(zhuǎn)為數(shù)字的字符,就不再進(jìn)行下去,返回已經(jīng)轉(zhuǎn)好的部分。

parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15

parseFloat()

parseFloat方法用于將一個字符串轉(zhuǎn)為浮點(diǎn)數(shù)。

isNaN()

isNaN方法可以用來判斷一個值是否為NaN。isNaN只對數(shù)值有效,如果傳入其他值,會被先轉(zhuǎn)成數(shù)值.也就是說,isNaNtrue的值,有可能不是NaN,而是一個字符串。

isFinite()

isFinite方法返回一個布爾值,表示某個值是否為正常的數(shù)值。除了Infinity-InfinityNaNundefined這幾個值會返回false,isFinite對于其他的數(shù)值都會返回true

字符串

如果要在單引號字符串的內(nèi)部,使用單引號,就必須在內(nèi)部的單引號前面加上反斜杠,用來轉(zhuǎn)義。雙引號字符串內(nèi)部使用雙引號,也是如此。

轉(zhuǎn)義

\n表示換行,輸出的時候就分成了兩行。

字符串與數(shù)組

符串可以被視為字符數(shù)組,因此可以使用數(shù)組的方括號運(yùn)算符,無法改變字符串之中的單個字符。

length屬性返回字符串的長度,該屬性也是無法改變的。

Base64 轉(zhuǎn)碼

JavaScript 原生提供兩個 Base64 相關(guān)的方法。

  • btoa():任意值轉(zhuǎn)為 Base64 編碼
  • atob():Base64 編碼轉(zhuǎn)為原來的值

對象

簡單說,對象就是一組“鍵值對”(key-value)的集合,是一種無序的復(fù)合數(shù)據(jù)集合。

對象的每一個鍵名又稱為“屬性”(property),它的“鍵值”可以是任何數(shù)據(jù)類型。如果一個屬性的值為函數(shù),通常把這個屬性稱為“方法”,它可以像函數(shù)那樣調(diào)用。

對象的引用

如果不同的變量名指向同一個對象,那么它們都是這個對象的引用,也就是說指向同一個內(nèi)存地址。修改其中一個變量,會影響到其他所有變量。

如果取消某一個變量對于原對象的引用,不會影響到另一個變量。

這種引用只局限于對象,如果兩個變量指向同一個原始類型的值。那么,變量這時都是值的拷貝。

屬性的讀取

讀取對象的屬性,有兩種方法,一種是使用點(diǎn)運(yùn)算符,還有一種是使用方括號運(yùn)算符。

var obj = {
  p: 'Hello World'
};

obj.p // "Hello World"
obj['p'] // "Hello World"

請注意,如果使用方括號運(yùn)算符,鍵名必須放在引號里面,否則會被當(dāng)作變量處理。

var foo = 'bar';

var obj = {
  foo: 1,
  bar: 2
};

obj.foo  // 1
obj[foo]  // 2

上面代碼中,引用對象objfoo屬性時,如果使用點(diǎn)運(yùn)算符,foo就是字符串;如果使用方括號運(yùn)算符,但是不使用引號,那么foo就是一個變量,指向字符串bar。

屬性的賦值

點(diǎn)運(yùn)算符和方括號運(yùn)算符,不僅可以用來讀取值,還可以用來賦值。

JavaScript 允許屬性的“后綁定”,也就是說,你可以在任意時刻新增屬性,沒必要在定義對象的時候,就定義好屬性。

屬性的查看

查看一個對象本身的所有屬性,可以使用Object.keys方法。

var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);
// ['key1', 'key2']

屬性的刪除:delete 命令

delete命令用于刪除對象的屬性,刪除成功后返回true

注意,刪除一個不存在的屬性,delete不報錯,而且返回true

var obj = { p: 1 };
Object.keys(obj) // ["p"]

delete obj.p // true
obj.p // undefined
Object.keys(obj) // []

屬性是否存在:in 運(yùn)算符

in運(yùn)算符用于檢查對象是否包含某個屬性(注意,檢查的是鍵名,不是鍵值),如果包含就返回true,否則返回false。它的左邊是一個字符串,表示屬性名,右邊是一個對象。

var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true

in運(yùn)算符的一個問題是,它不能識別哪些屬性是對象自身的,哪些屬性是繼承的。就像上面代碼中,對象obj本身并沒有toString屬性,但是in運(yùn)算符會返回true,因?yàn)檫@個屬性是繼承的。

這時,可以使用對象的hasOwnProperty方法判斷一下,是否為對象自身的屬性。

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}

屬性的遍歷:for...in 循環(huán)

for...in循環(huán)用來遍歷一個對象的全部屬性。

var obj = {a: 1, b: 2, c: 3};

for (var i in obj) {
  console.log('鍵名:', i);
  console.log('鍵值:', obj[i]);
}

如果繼承的屬性是可遍歷的,那么就會被for...in循環(huán)遍歷到。但是,一般情況下,都是只想遍歷對象自身的屬性,所以使用for...in的時候,應(yīng)該結(jié)合使用hasOwnProperty方法,在循環(huán)內(nèi)部判斷一下,某個屬性是否為對象自身的屬性。

var person = { name: '老張' };

for (var key in person) {
  if (person.hasOwnProperty(key)) {
    console.log(key);
  }
}

with 語句

with語句的格式如下:

with (對象) {
  語句;
}

它的作用是操作同一個對象的多個屬性時,提供一些書寫的方便。

// 例一
var obj = {
  p1: 1,
  p2: 2,
};
with (obj) {
  p1 = 4;
  p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;

// 例二
with (document.links[0]){
  console.log(href);
  console.log(title);
  console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);

注意,如果with區(qū)塊內(nèi)部有變量的賦值操作,必須是當(dāng)前對象已經(jīng)存在的屬性,否則會創(chuàng)造一個當(dāng)前作用域的全局變量。 建議不使用with語句

函數(shù)

函數(shù)是一段可以反復(fù)調(diào)用的代碼塊。函數(shù)還能接受輸入的參數(shù),不同的參數(shù)會返回不同的值。

1.概述

1.1 函數(shù)的聲明

JavaScript 有三種聲明函數(shù)的方法。

1)function 命令

function命令聲明的代碼區(qū)塊,就是一個函數(shù)。function命令后面是函數(shù)名,函數(shù)名后面是一對圓括號,里面是傳入函數(shù)的參數(shù)。函數(shù)體放在大括號里面。

function print(s) {
  console.log(s);
}

上面的代碼命名了一個print函數(shù),以后使用print()這種形式,就可以調(diào)用相應(yīng)的代碼。這叫做函數(shù)的聲明(Function Declaration)。

(2)函數(shù)表達(dá)式

除了用function命令聲明函數(shù),還可以采用變量賦值的寫法。

var print = function(s) {
  console.log(s);
};

這種寫法將一個匿名函數(shù)賦值給變量。這時,這個匿名函數(shù)又稱函數(shù)表達(dá)式(Function Expression),因?yàn)橘x值語句的等號右側(cè)只能放表達(dá)式。

采用函數(shù)表達(dá)式聲明函數(shù)時,function命令后面不帶有函數(shù)名。如果加上函數(shù)名,該函數(shù)名只在函數(shù)體內(nèi)部有效,在函數(shù)體外部無效。

(3)Function 構(gòu)造函數(shù)

第三種聲明函數(shù)的方式是Function構(gòu)造函數(shù)。

總的來說,這種聲明函數(shù)的方式非常不直觀,幾乎無人使用。

1.2 函數(shù)的重復(fù)聲明

如果同一個函數(shù)被多次聲明,后面的聲明就會覆蓋前面的聲明。

function f() {
  console.log(1);
}
f() // 2

function f() {
  console.log(2);
}
f() // 2

上面代碼中,后一次的函數(shù)聲明覆蓋了前面一次。而且,由于函數(shù)名的提升(參見下文),前一次聲明在任何時候都是無效的,這一點(diǎn)要特別注意。

1.3 圓括號運(yùn)算符,return 語句和遞歸

調(diào)用函數(shù)時,要使用圓括號運(yùn)算符。圓括號之中,可以加入函數(shù)的參數(shù)。

函數(shù)體內(nèi)部的return語句,表示返回。JavaScript 引擎遇到return語句,就直接返回return后面的那個表達(dá)式的值,后面即使還有語句,也不會得到執(zhí)行。也就是說,return語句所帶的那個表達(dá)式,就是函數(shù)的返回值。return語句不是必需的,如果沒有的話,該函數(shù)就不返回任何值,或者說返回undefined。

函數(shù)可以調(diào)用自身,這就是遞歸(recursion)。下面就是通過遞歸,計算斐波那契數(shù)列的代碼。

function fib(num) {
  if (num === 0) return 0;
  if (num === 1) return 1;
  return fib(num - 2) + fib(num - 1);
}

fib(6) // 8

上面代碼中,fib函數(shù)內(nèi)部又調(diào)用了fib,計算得到斐波那契數(shù)列的第6個元素是8。

1.4函數(shù)名的提升

JavaScript 引擎將函數(shù)名視同變量名,所以采用function命令聲明函數(shù)時,整個函數(shù)會像變量聲明一樣,被提升到代碼頭部。

2.函數(shù)的屬性

name 屬性

函數(shù)的name屬性返回函數(shù)的名字。

如果是通過變量賦值定義的函數(shù),那么name屬性返回變量名。

如果變量的值是一個具名函數(shù),那么name屬性返回function關(guān)鍵字之后的那個函數(shù)名。

name屬性的一個用處,就是獲取參數(shù)函數(shù)的名字。

length 屬性

函數(shù)的length屬性返回函數(shù)預(yù)期傳入的參數(shù)個數(shù),即函數(shù)定義之中的參數(shù)個數(shù)。

toString()

函數(shù)的toString方法返回一個字符串,內(nèi)容是函數(shù)的源碼。

3. 函數(shù)的作用域

在 ES5 的規(guī)范中,Javascript 只有兩種作用域:一種是全局作用域,變量在整個程序中一直存在,所有地方都可以讀??;另一種是函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。ES6 又新增了塊級作用域

與全局作用域一樣,函數(shù)作用域內(nèi)部也會產(chǎn)生“變量提升”現(xiàn)象。var命令聲明的變量,不管在什么位置,變量聲明都會被提升到函數(shù)體的頭部。

函數(shù)本身的作用域

函數(shù)本身也是一個值,也有自己的作用域。它的作用域與變量一樣,就是其聲明時所在的作用域,與其運(yùn)行時所在的作用域無關(guān)。

var a = 1;
var x = function () {
  console.log(a);
};

function f() {
  var a = 2;
  x();
}

f() // 1

上面代碼中,函數(shù)x是在函數(shù)f的外部聲明的,所以它的作用域綁定外層,內(nèi)部變量a不會到函數(shù)f體內(nèi)取值,所以輸出1,而不是2

總之,函數(shù)執(zhí)行時所在的作用域,是定義時的作用域,而不是調(diào)用時所在的作用域。

閉包就是創(chuàng)建局部變量, 使得函數(shù)外部可以訪問到這個變量

4.參數(shù)

函數(shù)運(yùn)行的時候,有時需要提供外部數(shù)據(jù),不同的外部數(shù)據(jù)會得到不同的結(jié)果,這種外部數(shù)據(jù)就叫參數(shù)。

函數(shù)參數(shù)不是必需的,Javascript 允許省略參數(shù)。

但是,沒有辦法只省略靠前的參數(shù),而保留靠后的參數(shù)。如果一定要省略靠前的參數(shù),只有顯式傳入undefined。

傳遞方式

函數(shù)參數(shù)如果是原始類型的值(數(shù)值、字符串、布爾值),傳遞方式是傳值傳遞(passes by value)。這意味著,在函數(shù)體內(nèi)修改參數(shù)值,不會影響到函數(shù)外部。

如果函數(shù)參數(shù)是復(fù)合類型的值(數(shù)組、對象、其他函數(shù)),傳遞方式是傳址傳遞(pass by reference)。也就是說,傳入函數(shù)的原始值的地址,因此在函數(shù)內(nèi)部修改參數(shù),將會影響到原始值。

注意,如果函數(shù)內(nèi)部修改的,不是參數(shù)對象的某個屬性,而是替換掉整個參數(shù),這時不會影響到原始值。

arguments 對象

由于 JavaScript 允許函數(shù)有不定數(shù)目的參數(shù),所以需要一種機(jī)制,可以在函數(shù)體內(nèi)部讀取所有參數(shù)。這就是arguments對象的由來。

嚴(yán)格模式下,arguments對象是一個只讀對象,修改它是無效的,但不會報錯。

與數(shù)組的關(guān)系

需要注意的是,雖然arguments很像數(shù)組,但它是一個對象。數(shù)組專有的方法(比如sliceforEach),不能在arguments對象上直接使用。

如果要讓arguments對象使用數(shù)組方法,真正的解決方法是將arguments轉(zhuǎn)為真正的數(shù)組。下面是兩種常用的轉(zhuǎn)換方法:slice方法和逐一填入新數(shù)組。

var args = Array.prototype.slice.call(arguments);

// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}

5.函數(shù)的其他知識點(diǎn)

閉包

理解閉包,首先必須理解變量作用域。前面提到,JavaScript 有兩種作用域:全局作用域和函數(shù)作用域。函數(shù)內(nèi)部可以直接讀取全局變量。

由于在 JavaScript 語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量,因此可以把閉包簡單理解成“定義在一個函數(shù)內(nèi)部的函數(shù)”。閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境. 在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。

閉包的最大用處有兩個,一個是可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量始終保持在內(nèi)存中,即閉包可以使得它誕生環(huán)境一直存在。

閉包的另一個用處,是封裝對象的私有屬性和私有方法。

立即調(diào)用的函數(shù)表達(dá)式

解決方法就是不要讓function出現(xiàn)在行首,讓引擎將其理解成一個表達(dá)式。最簡單的處理,就是將其放在一個圓括號里面。

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

通常情況下,只對匿名函數(shù)使用這種“立即執(zhí)行的函數(shù)表達(dá)式”。它的目的有兩個:一是不必為函數(shù)命名,避免了污染全局變量;二是 IIFE 內(nèi)部形成了一個單獨(dú)的作用域,可以封裝一些外部無法讀取的私有變量。

// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);

// 寫法二
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());

上面代碼中,寫法二比寫法一更好,因?yàn)橥耆苊饬宋廴救肿兞俊?/p>

數(shù)組

1.定義

數(shù)組(array)是按次序排列的一組值。每個值的位置都有編號(從0開始),整個數(shù)組用方括號表示。

除了在定義時賦值,數(shù)組也可以先定義后賦值。

任何類型的數(shù)據(jù),都可以放入數(shù)組。

2.數(shù)組的本質(zhì)

本質(zhì)上,數(shù)組屬于一種特殊的對象。typeof運(yùn)算符會返回數(shù)組的類型是object。

3.length 屬性

數(shù)組的length屬性,返回數(shù)組的成員數(shù)量。

只要是數(shù)組,就一定有length屬性。該屬性是一個動態(tài)的值,等于鍵名中的最大整數(shù)加上1

length屬性是可寫的。如果人為設(shè)置一個小于當(dāng)前成員個數(shù)的值,該數(shù)組的成員會自動減少到length設(shè)置的值。

清空數(shù)組的一個有效方法,就是將length屬性設(shè)為0。

如果人為設(shè)置length大于當(dāng)前元素個數(shù),則數(shù)組的成員數(shù)量會增加到這個值,新增的位置都是空位。

4. in 運(yùn)算符

檢查某個鍵名是否存在的運(yùn)算符in,適用于對象,也適用于數(shù)組。

5.for...in 循環(huán)和數(shù)組的遍歷

for...in循環(huán)不僅可以遍歷對象,也可以遍歷數(shù)組,畢竟數(shù)組只是一種特殊對象。但是,for...in不僅會遍歷數(shù)組所有的數(shù)字鍵,還會遍歷非數(shù)字鍵。所以,不推薦使用for...in遍歷數(shù)組。

數(shù)組的遍歷可以考慮使用for循環(huán)或while循環(huán)。

數(shù)組的forEach方法,也可以用來遍歷數(shù)組,詳見《標(biāo)準(zhǔn)庫》的 Array 對象一章。

數(shù)組的空位

當(dāng)數(shù)組的某個位置是空元素,即兩個逗號之間沒有任何值,我們稱該數(shù)組存在空位(hole)。需要注意的是,如果最后一個元素后面有逗號,并不會產(chǎn)生空位。也就是說,有沒有這個逗號,結(jié)果都是一樣的。

數(shù)組的空位是可以讀取的,返回undefined

使用delete命令刪除一個數(shù)組成員,會形成空位,并且不會影響length屬性。

類似數(shù)組的對象

如果一個對象的所有鍵名都是正整數(shù)或零,并且有length屬性,那么這個對象就很像數(shù)組,語法上稱為“類似數(shù)組的對象”(array-like object)。

典型的“類似數(shù)組的對象”是函數(shù)的arguments對象,以及大多數(shù) DOM 元素集,還有字符串。

數(shù)組的slice方法可以將“類似數(shù)組的對象”變成真正的數(shù)組。

var arr = Array.prototype.slice.call(arrayLike);

除了轉(zhuǎn)為真正的數(shù)組,“類似數(shù)組的對象”還有一個辦法可以使用數(shù)組的方法,就是通過call()把數(shù)組的方法放到對象上面。

function print(value, index) {
  console.log(index + ' : ' + value);
}

Array.prototype.forEach.call(arrayLike, print);

注意,這種方法比直接使用數(shù)組原生的forEach要慢,所以最好還是先將“類似數(shù)組的對象”轉(zhuǎn)為真正的數(shù)組,然后再直接調(diào)用數(shù)組的forEach方法。

?著作權(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)容