【沒人看系列】js 資料型別

阿否發表於2018-10-18

本文為作者JavaScript學習筆記,只是加強個人理解和認識。大部分概念與程式碼參考了阮一峰 js 教程


基本

1. 語句

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

語句和表示式的區別在於,前者主要為了進行某種操作,一般情況下不需要返回值;後者則是為了得到返回值,一定會返回一個值。

2. 變數

變數是對“值”的具名引用。變數就是為“值”起名,然後引用這個名字,就等同於引用這個值。變數的名字就是變數名。

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

變數提升

JavaScript 引擎的工作方式是,先解析程式碼,獲取所有被宣告的變數,然後再一行一行地執行。這造成的結果,就是所有的變數的宣告語句,都會被提升到程式碼的頭部,這就叫做變數提升(hoisting)。

8.判斷

switch語句後面的表示式,與case語句後面的表示式比較執行結果時,採用的是嚴格相等運算子(===),而不是相等運算子(==),這意味著比較時不會發生型別轉換。

7.do...while 迴圈

do...while迴圈與while迴圈類似,唯一的區別就是先執行一次迴圈體,然後判斷迴圈條件。

不管條件是否為真,do...while迴圈至少執行一次,這是這種結構最大的特點。另外,while語句後面的分號注意不要省略。

資料型別概述

1. 簡介

js 有6種資料型別, es6 又新增了第7種symbol

布林值

下列運算子會返回布林值:

  • 前置邏輯運算子: ! (Not)

  • 相等運算子:===!====!=

  • 比較運算子:>>=<<=

    如果 JavaScript 預期某個位置應該是布林值,會將該位置上現有的值自動轉為布林值。轉換規則是除了下面六個值被轉為false,其他值都視為true

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

注意,空陣列([])和空物件({})對應的布林值,都是true

數值

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

與數值相關的全域性方法

parseInt()

parseInt方法用於將字串轉為整數。

parseInt('123') // 123
複製程式碼

如果parseInt的引數不是字串,則會先轉為字串再轉換。

parseInt(1.23) // 1
// 等同於
parseInt('1.23') // 1
複製程式碼

字串轉為整數的時候,是一個個字元依次轉換,如果遇到不能轉為數字的字元,就不再進行下去,返回已經轉好的部分。

parseInt('8a') // 8
parseInt('12**') // 12
parseInt('12.34') // 12
parseInt('15e2') // 15
parseInt('15px') // 15
複製程式碼

parseFloat()

parseFloat方法用於將一個字串轉為浮點數。

isNaN()

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

isFinite()

isFinite方法返回一個布林值,表示某個值是否為正常的數值。除了Infinity-InfinityNaNundefined這幾個值會返回falseisFinite對於其他的數值都會返回true

字串

如果要在單引號字串的內部,使用單引號,就必須在內部的單引號前面加上反斜槓,用來轉義。雙引號字串內部使用雙引號,也是如此。

轉義

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

字串與陣列

符串可以被視為字元陣列,因此可以使用陣列的方括號運算子,無法改變字串之中的單個字元。

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

Base64 轉碼

JavaScript 原生提供兩個 Base64 相關的方法。

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

物件

簡單說,物件就是一組“鍵值對”(key-value)的集合,是一種無序的複合資料集合。

物件的每一個鍵名又稱為“屬性”(property),它的“鍵值”可以是任何資料型別。如果一個屬性的值為函式,通常把這個屬性稱為“方法”,它可以像函式那樣呼叫。

物件的引用

如果不同的變數名指向同一個物件,那麼它們都是這個物件的引用,也就是說指向同一個記憶體地址。修改其中一個變數,會影響到其他所有變數。

如果取消某一個變數對於原物件的引用,不會影響到另一個變數。

這種引用只侷限於物件,如果兩個變數指向同一個原始型別的值。那麼,變數這時都是值的拷貝。

屬性的讀取

讀取物件的屬性,有兩種方法,一種是使用點運算子,還有一種是使用方括號運算子。

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

obj.p // "Hello World"
obj['p'] // "Hello World"
複製程式碼

請注意,如果使用方括號運算子,鍵名必須放在引號裡面,否則會被當作變數處理。

var foo = 'bar';

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

obj.foo  // 1
obj[foo]  // 2
複製程式碼

上面程式碼中,引用物件objfoo屬性時,如果使用點運算子,foo就是字串;如果使用方括號運算子,但是不使用引號,那麼foo就是一個變數,指向字串bar

屬性的賦值

點運算子和方括號運算子,不僅可以用來讀取值,還可以用來賦值。

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 運算子

in運算子用於檢查物件是否包含某個屬性(注意,檢查的是鍵名,不是鍵值),如果包含就返回true,否則返回false。它的左邊是一個字串,表示屬性名,右邊是一個物件。

var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
複製程式碼

in運算子的一個問題是,它不能識別哪些屬性是物件自身的,哪些屬性是繼承的。就像上面程式碼中,物件obj本身並沒有toString屬性,但是in運算子會返回true,因為這個屬性是繼承的。

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

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}
複製程式碼

屬性的遍歷:for...in 迴圈

for...in迴圈用來遍歷一個物件的全部屬性。

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

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

如果繼承的屬性是可遍歷的,那麼就會被for...in迴圈遍歷到。但是,一般情況下,都是隻想遍歷物件自身的屬性,所以使用for...in的時候,應該結合使用hasOwnProperty方法,在迴圈內部判斷一下,某個屬性是否為物件自身的屬性。

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區塊內部有變數的賦值操作,必須是當前物件已經存在的屬性,否則會創造一個當前作用域的全域性變數。 建議不使用with語句

函式

函式是一段可以反覆呼叫的程式碼塊。函式還能接受輸入的引數,不同的引數會返回不同的值。

1.概述

1.1 函式的宣告

JavaScript 有三種宣告函式的方法。

1)function 命令

function命令宣告的程式碼區塊,就是一個函式。function命令後面是函式名,函式名後面是一對圓括號,裡面是傳入函式的引數。函式體放在大括號裡面。

function print(s) {
  console.log(s);
}
複製程式碼

上面的程式碼命名了一個print函式,以後使用print()這種形式,就可以呼叫相應的程式碼。這叫做函式的宣告(Function Declaration)。

(2)函式表示式

除了用function命令宣告函式,還可以採用變數賦值的寫法。

var print = function(s) {
  console.log(s);
};
複製程式碼

這種寫法將一個匿名函式賦值給變數。這時,這個匿名函式又稱函式表示式(Function Expression),因為賦值語句的等號右側只能放表示式。

採用函式表示式宣告函式時,function命令後面不帶有函式名。如果加上函式名,該函式名只在函式體內部有效,在函式體外部無效。

(3)Function 建構函式

第三種宣告函式的方式是Function建構函式。

總的來說,這種宣告函式的方式非常不直觀,幾乎無人使用。

1.2 函式的重複宣告

如果同一個函式被多次宣告,後面的宣告就會覆蓋前面的宣告。

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

function f() {
  console.log(2);
}
f() // 2
複製程式碼

上面程式碼中,後一次的函式宣告覆蓋了前面一次。而且,由於函式名的提升(參見下文),前一次宣告在任何時候都是無效的,這一點要特別注意。

1.3 圓括號運算子,return 語句和遞迴

呼叫函式時,要使用圓括號運算子。圓括號之中,可以加入函式的引數。

函式體內部的return語句,表示返回。JavaScript 引擎遇到return語句,就直接返回return後面的那個表示式的值,後面即使還有語句,也不會得到執行。也就是說,return語句所帶的那個表示式,就是函式的返回值。return語句不是必需的,如果沒有的話,該函式就不返回任何值,或者說返回undefined

函式可以呼叫自身,這就是遞迴(recursion)。下面就是通過遞迴,計算斐波那契數列的程式碼。

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

fib(6) // 8
複製程式碼

上面程式碼中,fib函式內部又呼叫了fib,計算得到斐波那契數列的第6個元素是8。

1.4函式名的提升

JavaScript 引擎將函式名視同變數名,所以採用function命令宣告函式時,整個函式會像變數宣告一樣,被提升到程式碼頭部。

2.函式的屬性

name 屬性

函式的name屬性返回函式的名字。

如果是通過變數賦值定義的函式,那麼name屬性返回變數名。

如果變數的值是一個具名函式,那麼name屬性返回function關鍵字之後的那個函式名。

name屬性的一個用處,就是獲取引數函式的名字。

length 屬性

函式的length屬性返回函式預期傳入的引數個數,即函式定義之中的引數個數。

toString()

函式的toString方法返回一個字串,內容是函式的原始碼。

3. 函式的作用域

在 ES5 的規範中,Javascript 只有兩種作用域:一種是全域性作用域,變數在整個程式中一直存在,所有地方都可以讀取;另一種是函式作用域,變數只在函式內部存在。ES6 又新增了塊級作用域

與全域性作用域一樣,函式作用域內部也會產生“變數提升”現象。var命令宣告的變數,不管在什麼位置,變數宣告都會被提升到函式體的頭部。

函式本身的作用域

函式本身也是一個值,也有自己的作用域。它的作用域與變數一樣,就是其宣告時所在的作用域,與其執行時所在的作用域無關。

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

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

f() // 1
複製程式碼

上面程式碼中,函式x是在函式f的外部宣告的,所以它的作用域繫結外層,內部變數a不會到函式f體內取值,所以輸出1,而不是2

總之,函式執行時所在的作用域,是定義時的作用域,而不是呼叫時所在的作用域。

閉包就是建立區域性變數, 使得函式外部可以訪問到這個變數

4.引數

函式執行的時候,有時需要提供外部資料,不同的外部資料會得到不同的結果,這種外部資料就叫引數。

函式引數不是必需的,Javascript 允許省略引數。

但是,沒有辦法只省略靠前的引數,而保留靠後的引數。如果一定要省略靠前的引數,只有顯式傳入undefined

傳遞方式

函式引數如果是原始型別的值(數值、字串、布林值),傳遞方式是傳值傳遞(passes by value)。這意味著,在函式體內修改引數值,不會影響到函式外部。

如果函式引數是複合型別的值(陣列、物件、其他函式),傳遞方式是傳址傳遞(pass by reference)。也就是說,傳入函式的原始值的地址,因此在函式內部修改引數,將會影響到原始值。

注意,如果函式內部修改的,不是引數物件的某個屬性,而是替換掉整個引數,這時不會影響到原始值。

arguments 物件

由於 JavaScript 允許函式有不定數目的引數,所以需要一種機制,可以在函式體內部讀取所有引數。這就是arguments物件的由來。

嚴格模式下,arguments物件是一個只讀物件,修改它是無效的,但不會報錯。

與陣列的關係

需要注意的是,雖然arguments很像陣列,但它是一個物件。陣列專有的方法(比如sliceforEach),不能在arguments物件上直接使用。

如果要讓arguments物件使用陣列方法,真正的解決方法是將arguments轉為真正的陣列。下面是兩種常用的轉換方法:slice方法和逐一填入新陣列。

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

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

5.函式的其他知識點

閉包

理解閉包,首先必須理解變數作用域。前面提到,JavaScript 有兩種作用域:全域性作用域和函式作用域。函式內部可以直接讀取全域性變數。

由於在 JavaScript 語言中,只有函式內部的子函式才能讀取內部變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”。閉包最大的特點,就是它可以“記住”誕生的環境. 在本質上,閉包就是將函式內部和函式外部連線起來的一座橋樑。

閉包的最大用處有兩個,一個是可以讀取函式內部的變數,另一個就是讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在。

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

立即呼叫的函式表示式

解決方法就是不要讓function出現在行首,讓引擎將其理解成一個表示式。最簡單的處理,就是將其放在一個圓括號裡面。

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
複製程式碼

通常情況下,只對匿名函式使用這種“立即執行的函式表示式”。它的目的有兩個:一是不必為函式命名,避免了汙染全域性變數;二是 IIFE 內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數。

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

// 寫法二
(function () {
  var tmp = newData;
  processData(tmp);
  storeData(tmp);
}());
複製程式碼

上面程式碼中,寫法二比寫法一更好,因為完全避免了汙染全域性變數。

陣列

1.定義

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

除了在定義時賦值,陣列也可以先定義後賦值。

任何型別的資料,都可以放入陣列。

2.陣列的本質

本質上,陣列屬於一種特殊的物件。typeof運算子會返回陣列的型別是object

3.length 屬性

陣列的length屬性,返回陣列的成員數量。

只要是陣列,就一定有length屬性。該屬性是一個動態的值,等於鍵名中的最大整數加上1

length屬性是可寫的。如果人為設定一個小於當前成員個數的值,該陣列的成員會自動減少到length設定的值。

清空陣列的一個有效方法,就是將length屬性設為0。

如果人為設定length大於當前元素個數,則陣列的成員數量會增加到這個值,新增的位置都是空位。

4. in 運算子

檢查某個鍵名是否存在的運算子in,適用於物件,也適用於陣列。

5.for...in 迴圈和陣列的遍歷

for...in迴圈不僅可以遍歷物件,也可以遍歷陣列,畢竟陣列只是一種特殊物件。但是,for...in不僅會遍歷陣列所有的數字鍵,還會遍歷非數字鍵。所以,不推薦使用for...in遍歷陣列。

陣列的遍歷可以考慮使用for迴圈或while迴圈。

陣列的forEach方法,也可以用來遍歷陣列,詳見《標準庫》的 Array 物件一章。

陣列的空位

當陣列的某個位置是空元素,即兩個逗號之間沒有任何值,我們稱該陣列存在空位(hole)。需要注意的是,如果最後一個元素後面有逗號,並不會產生空位。也就是說,有沒有這個逗號,結果都是一樣的。

陣列的空位是可以讀取的,返回undefined

使用delete命令刪除一個陣列成員,會形成空位,並且不會影響length屬性。

類似陣列的物件

如果一個物件的所有鍵名都是正整數或零,並且有length屬性,那麼這個物件就很像陣列,語法上稱為“類似陣列的物件”(array-like object)。

典型的“類似陣列的物件”是函式的arguments物件,以及大多數 DOM 元素集,還有字串。

陣列的slice方法可以將“類似陣列的物件”變成真正的陣列。

var arr = Array.prototype.slice.call(arrayLike);
複製程式碼

除了轉為真正的陣列,“類似陣列的物件”還有一個辦法可以使用陣列的方法,就是通過call()把陣列的方法放到物件上面。

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

Array.prototype.forEach.call(arrayLike, print);
複製程式碼

注意,這種方法比直接使用陣列原生的forEach要慢,所以最好還是先將“類似陣列的物件”轉為真正的陣列,然後再直接呼叫陣列的forEach方法。

相關文章