1. 程式人生 > 實用技巧 >JavaScript基礎鞏固系列——入門篇+資料型別

JavaScript基礎鞏固系列——入門篇+資料型別

全手打原創,轉載請標明出處:https://www.cnblogs.com/dreamsqin/p/13686263.html, 多謝,=。=~(如果對你有幫助的話請幫我點個贊啦)

重新學習JavaScript是因為當年轉前端有點兒趕鴨子上架的意味,我一直在反思我的知識點總是很零散,不能在腦海中形成一個完整的體系,所以這次想通過再次學習將知識點都串聯起來,結合日常開發的專案,達到溫故而知新的效果。與此同時,總結一下我認為很重要但又被我遺漏的知識點~

背景知識

JavaScript既是一種輕量級的指令碼語言,又是一種嵌入式語言。

  • 指令碼語言(script language):不具備開發作業系統的能力,只用來編寫控制大型應用程式(例如瀏覽器)的指令碼。
  • 嵌入式語言(embedded):通過嵌入大型應用程式呼叫宿主環境提供的底層API(例如瀏覽器為JavaScript提供瀏覽器控制類、DOM類、Web類API;Node為JavaScript提供檔案操作、網路通訊等API;)實現本身核心語法不支援的複雜功能。

JavaScript歷史

  • 為什麼誕生:Navigator 瀏覽器需要一種可以嵌入網頁的指令碼語言,用來控制瀏覽器行為,因為當時網速很慢而且上網費很貴,有些操作不宜在伺服器端完成。
  • 需求:不需要太強的功能,語法較簡單,容易學習和部署。

JavaScript的程式設計風格是函數語言程式設計和麵向物件程式設計的一種混合體。

  • 語法來源
    • 基本語法:借鑑 C 語言和 Java 語言。
    • 資料結構:借鑑 Java 語言,包括將值分成原始值和物件兩大類。
    • 函式的用法:借鑑 Scheme 語言和 Awk 語言,將函式當作第一等公民,並引入閉包。
    • 原型繼承模型:借鑑 Self 語言(Smalltalk 的一種變種)。
    • 正則表示式:借鑑 Perl 語言。
    • 字串和陣列處理:借鑑 Python 語言。

ECMAScript歷史

ECMAScript 和 JavaScript 的關係是,前者是後者的規範,後者是前者的一種實現。

  • 為什麼誕生:微軟開發了JScript並內置於IE3.0瀏覽器中,Netscape 面臨喪失瀏覽器指令碼語言的主導權局面,最終決定將 JavaScript 提交給國際標準化組織 ECMA(European Computer Manufacturers Association),希望 JavaScript 能夠成為國際標準,以此抵抗微軟。
  • 需求:規定瀏覽器指令碼語言的標準。

基本語法

  • 變數->變數提升:JavaScript引擎的工作方式是,先解析程式碼,獲取所有被宣告的變數,然後再一行一行地執行。這造成的結果,就是所有變數的宣告語句,都會被提升到程式碼的頭部,對應的也有函式名提升(前提是使用function命令宣告,如果採用賦值語句定義就會報錯)。
// 原始碼(如果沒有變數提升會報錯:a is not defined)
console.log(a);
var a = 1;

// 實際執行程式碼
var a;
console.log(a);
a = 1;

// 執行不會報錯
f();
function f() {}
  • 識別符號:用來識別各種值(變數、函式)的合法名稱,只能以任意 Unicode 字母、美元符號$或下劃線_開頭,只能包含 Unicode 字母、美元符號、下劃線及數字。
    • 中文是合法的識別符號,可以用作變數名。
    • JavaScript保留字:arguments、break、case、catch、class、const、continue、debugger、default、delete、do、else、enum、eval、export、extends、false、finally、for、function、if、implements、import、in、instanceof、interface、let、new、null、package、private、protected、public、return、static、super、switch、this、throw、true、try、typeof、var、void、while、with、yield。
  • 註釋:JavaScript可以相容HTML程式碼註釋,所以<!---->也被視為合法的單行註釋,但-->只有在行首才會被當作單行註釋,否則為正常運算,例如:
function countdown(n) {
  while (n --> 0) console.log(n);    // 被作為n-- > 0執行
}
countdown(3)
// 2
// 1
// 0
  • 區塊:使用大括號,將多個相關的語句組合在一起。
    • 區塊對於var命令來說不構成單獨的作用域,與不使用區塊的情況沒有任何區別。例如:

{
var a = 1;
}
a // 1,在區塊外部變數依然有效

- **條件語句**:
	- 終於明白條件判斷大家為什麼要這麼寫了`if (2 = x)`,因為常量寫在運算子的左邊,一旦不小心將相等運算子寫成賦值運算子,就會報錯,因為常量不能被賦值。
    - 在沒有標明區塊(大括號)時,`else`程式碼塊總是與離自己最近的那個`if`語句配對。
    - `switch`語句後面的表示式與`case`語句後面的表示式比較時,採用的是嚴格相等運算子(`===`),即不會發生型別轉換。
- **迴圈語句**:
	- `break`語句用於跳出程式碼塊或迴圈。
    - `continue`語句用於立即終止本輪迴圈,返回迴圈結構的頭部,開始下一輪迴圈。
    - 標籤:JavaScript 語言允許語句的前面有標籤(label),相當於定位符,用於跳轉到程式的任意位置,例如:
    ```javascript
top:
  for (var i = 0; i < 3; i++){
    for (var j = 0; j < 3; j++){
      if (i === 1 && j === 1) break top;
      console.log('i=' + i + ', j=' + j);
    }
  }
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0

資料型別

  • 整數和浮點數:
    • JavaScript內部,所有數字都是以64位浮點數形式儲存。

1 === 1.0 // true - 由於浮點數不是精確的值,所以涉及小數的比較和運算要特別小心。
0.1 + 0.2 === 0.3 // false
0.3 / 0.1 // 2.9999999999999996
(0.3 - 0.2) === (0.2 - 0.1) // false```

  • 數值精度:精度最多隻能到53個二進位制位,絕對值小於2的53次方的整數,即負的2的53次方到 2的53次方,都可以精確表示。
Math.pow(2, 53) // 9007199254740992(簡單的法則就是JavaScript對15位的十進位制數都可以精確處理)
  • 數值範圍:JavaScript 提供Number物件的MAX_VALUEMIN_VALUE屬性,返回可以表示的最大值和最小值,負向溢位時返回0,正向溢位時返回Infinity。
Number.MAX_VALUE // 1.7976931348623157e+308
Number.MIN_VALUE // 5e-324
  • 數值表示法:0b11(二進位制)、0o377(八進位制)、35(十進位制)、0xFF(十六進位制)、123e3(科學計數法)
    • 小數點前的數字多於21位、小數點後的零多於5個時JavaScript自動將數值轉為科學計數法表示。
    • 預設情況下,JavaScript 內部會自動將八進位制、十六進位制、二進位制轉為十進位制。
  • 特殊數值:
    • 幾乎所有場合,正零負零都會被當作正常的0,是等價的,唯一有區別的場合是+0或-0當作分母,返回的值是不相等的。

(1 / +0) === (1 / -0) // false(除以正零得到+Infinity,除以負零得到-Infinity) - `NaN`表示“非數字”(Not a Number),主要出現在將字串解析成數字出錯的場合、還有一些數學函式的運算結果,資料型別屬於Number,不等於任何值(包括它本身),布林運算時被當作false,和任何數運算得到的都是NaN。
typeof NaN // 'number'
NaN === NaN // false
Boolean(NaN) // false
NaN + 32 // NaN - `Infinity`表示“無窮”,用來表示兩種場景,一種是一個正的數值太大,或一個負的數值太小,無法表示;另一種是非0數值除以0,Infinity有正負之分,大於一切數值(除了NaN),四則運算符合無窮的數學計算規則,與null計算時null會轉成0,與undefined計算返回的都是NaN。
Infinity === -Infinity // false
Infinity > 1000 // true
Infinity > NaN // false
0 * Infinity // NaN(特殊)
Infinity - Infinity // NaN(特殊)
Infinity / Infinity // NaN(特殊)
0 / Infinity // 0
Infinity / 0 // Infinity```

  • 全域性方法:
    • parseInt():用於將字串轉為整數,頭部有空格會自動去除,轉換時是一個個字元依次轉換,如果遇到不能轉為數字的字元,就不再進行下去,返回已經轉好的部分,如果字串的第一個字元不能轉化為數字(後面跟著數字的正負號除外)返回NaN,可以接受第二個引數(2到36之間,超出返回NaN,為0、undefined、null則忽略)表示被解析的值的進位制並返回該值對應的十進位制數。

parseInt('15e2') // 15
parseInt('.3') // NaN
parseInt('+1') // 1
parseInt('') // NaN
parseInt('1000', 2) // 8
parseInt('10', null) // 10 - `parseFloat()`:用於將一個字串轉為浮點數,需要與`Number()`函式區分。
parseFloat('') // NaN
Number('') // 0
parseFloat('123.45#') // 123.45
Number('123.45#') // NaN - `isNaN()`:用來判斷一個值是否為`NaN`,只對數值有效,如果傳入其他值,會被先轉成數值(所以重點關注裡面傳入的值是否可以被Number轉為數值)。
isNaN('Hello') // true
// 相當於
isNaN(Number('Hello')) // true
//判斷NaN更可靠的方法是,利用NaN是唯一一個不等於自身的這個特點
function myIsNaN(value) {
return value !== value;
}```
- isFinite():返回一個布林值,表示某個值是否為正常的數值,除了Infinity、-Infinity、NaN和undefined這幾個值會返回false,isFinite對於其他的數值都會返回true。

  • 字串:
    • 字串可以被視為字元陣列,因此可以使用陣列的方括號運算子,用來返回某個位置的字元(位置編號從0開始),但無法改變字串之中的單個字元。

'hello'[1] // "e"

var s = 'hello';
delete s[0];
s // "hello"
s[1] = 'a';
s // "hello" - 對於碼點在U+10000到U+10FFFF之間的字元,JavaScript 總是認為它們是兩個字元(length屬性為2),所以JavaScript 返回的字串長度可能是不正確的。 - `Base64`轉碼:就是一種編碼方法,可以將任意值轉成 0~9、A~Z、a-z、+和/這64個字元組成的可列印字元,應用場景一個是將不可列印符號轉成可列印字元(例如ASCII的0~31),或者以文字格式傳遞二進位制資料,有兩個原生方法:`btoa()`任意值轉為 Base64 編碼、`atob()`Base64 編碼轉為原來的值,但不適用於非ASCII的字元(需要先通過`encodeURIComponent`進行轉碼)。
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"

function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"```

  • 物件:
    • 對於不符合標識名條件的鍵名必須加上引號,例如第一個字元為數字,或者含有空格或運算子。

var obj = {
'1p': 'Hello World',
'h w': 'Hello World',
'p+q': 'Hello World'
};```
- JavaScript 引擎如果遇到無法確定是物件還是程式碼塊的情況,一律解釋為程式碼塊,如果要解釋為物件,最好在大括號前加上圓括號。

// eval語句(作用是對字串求值)
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}```
	- 數字鍵在方括號讀取運算子中可以不加引號,但不能使用點運算子讀取。
    ```
var obj = {
  123: 'hello world'
};

obj.123 // 報錯
obj[123] // "hello world"```
	- `Object.keys()`:返回一個物件本身的所有屬性。
    ```
var obj = {
  key1: 1,
  key2: 2
};

Object.keys(obj);
// ['key1', 'key2']```
	- `delete`:用於刪除物件的屬性,刪除成功後返回true,但刪除一個不存在的屬性,delete不報錯,而且也返回true,只有某屬性不可刪除時(`configurable:false`)才返回false,只能刪除物件本身的屬性,無法刪除繼承的屬性。
    - `in`:用於檢查物件是否包含某個屬性,如果包含就返回true,否則返回false,但無法識別哪些屬性是物件自身的,哪些屬性是繼承的(可以用`hasOwnProperty`判斷)。
    ```
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true

var obj = {};
if ('toString' in obj) {
  console.log(obj.hasOwnProperty('toString')) // false
}```
	- `for...in`:用來遍歷一個物件的全部屬性,僅遍歷可遍歷(`enumerable:true`)屬性跳過不可遍歷屬性,不僅遍歷物件自身的屬性,還遍歷繼承的屬性(前提是繼承的屬性是可遍歷的),如果想僅遍歷自身屬性可用`hasOwnProperty`在迴圈內部判斷。
    ```
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
  console.log('鍵名:', i);
  console.log('鍵值:', obj[i]);
}
// 鍵名: a
// 鍵值: 1
// 鍵名: b
// 鍵值: 2
// 鍵名: c
// 鍵值: 3```
	- `with`:操作同一個物件的多個屬性時,提供一些書寫的方便(在內部可以不使用點運算子就能直接讀取屬性),如果with區塊內部有變數的賦值操作,必須是當前物件已經存在的屬性,否則會創造一個當前作用域的全域性變數,**不建議使用with語句,無法判斷內部變數為物件屬性還是全域性變數**。
    ```
var obj = {
  p1: 1,
  p2: 2,
};
with (obj) {
  p1 = 4;
  p2 = 5;
}
// 等同於
obj.p1 = 4;
obj.p2 = 5;```
- **函式:**
	- `name屬性`:返回函式的名字,常用的是獲取引數函式的名字。
    ```
var myFunc = function () {};
function test(f) {
  console.log(f.name);
}
test(myFunc) // myFunc```
	- `length屬性`:返回函式預期傳入的引數個數,即函式定義之中的引數個數。
    ```
function f(a, b) {}
f.length // 2```
	- 函式本身作用域:函式執行時所在的作用域,是定義時的作用域,而不是呼叫時所在的作用域。
    ```
var a = 1;
var x = function () {
  console.log(a);
};

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

f() // 1```
	- 引數傳遞方式:如果是原始型別的值(數值、字串、布林值),傳遞方式是`傳值傳遞(passes by value)`,在函式體內修改引數值,不會影響到函式外部;如果函式引數是複合型別的值(陣列、物件、其他函式),傳遞方式是`傳址傳遞(pass by reference)`,在函式內部修改引數,將會影響到原始值,但如果修改引數的地址指向,則不會影響原始值。
    ```
var obj = { p: 1 };
function f(o) {
  o.p = 2;
}
f(obj);
obj.p // 2

function f2(o) {
  o = { p: 3 };
}
f2(obj);
obj.p // 2```
	- `arguments物件`:包含了函式**執行時**的所有引數,這個物件只有在函式體內部才可以使用,正常模式下arguments物件可以在執行時修改,但嚴格模式下不行,它很像陣列,但它是一個物件,陣列專有的方法(比如`slice`和`forEach`)無法使用,但可以將它轉為真正的陣列(`slice`方法或遍歷填入新陣列)。
    ```
var f = function(a, b) {
  'use strict'; // 開啟嚴格模式
  arguments[0] = 3;
  arguments[1] = 2;
  return a + b;
}
f(1, 1) // 2

var args = Array.prototype.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
  args.push(arguments[i]);
}```
	- 閉包:能夠讀取其他函式內部變數的函式,由於在 JavaScript 語言中,只有函式內部的子函式才能讀取內部變數,因此可以把閉包簡單理解成“定義在一個函式內部的函式”,閉包的最大用處,一個是可以讀取函式內部的變數,另一個就是讓這些變數始終保持在記憶體中,即閉包可以使得它誕生環境一直存在,還有一個是封裝物件的私有屬性和私有方法。
    **PS:外層函式每次執行,都會生成一個新的閉包,而這個閉包又會保留外層函式的內部變數,所以記憶體消耗很大。因此不能濫用閉包,否則會造成網頁的效能問題。**
    ```
function createIncrementor(start) {
  return function () {
    return start++;
  };
}
var inc = createIncrementor(5);

inc() // 5
inc() // 6
inc() // 7```
	- 立即呼叫的函式表示式(IIFE-Immediately-Invoked Function Expression):通常情況下,只對匿名函式使用,它的目的有兩個:一是不必為函式命名,避免了汙染全域性變數;二是 IIFE 內部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變數。
    ```
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();```
	- `eval命令`:接受一個字串作為引數,並將這個字串當作語句執行,引數如果不是字串就會原樣返回,eval沒有自己的作用域,可能會因為修改當前作用域的變數造成安全問題(使用嚴格模式,eval內部宣告的變數,不會影響到外部作用域,但依然可以讀寫當前作用域的變數),最常見的場合是解析 JSON 資料的字串(應使用`JSON.parse`替代),凡是使用別名執行eval,eval內部一律是全域性作用域。
    ```
    eval('var a = 1;'); // 生成變數a
    eval(123) // 123 // 非字串原樣返回```
- **陣列:**
	- JavaScript 語言規定,物件的鍵名一律為字串,所以,陣列的鍵名其實也是字串,之所以可以用數值讀取,是因為非字串的鍵名會被轉為字串。
    ```
var arr = ['a', 'b', 'c'];
arr['0'] // 'a'
arr[0] // 'a'```
	- JavaScript 使用一個32位整數儲存陣列的元素個數,所以陣列成員最多隻有 4294967295 個(232 - 1)個,length屬性的最大值就是 4294967295。
    - `length`屬性可寫,如果設定一個小於當前成員個數的值,該陣列的成員數量會自動減少到length設定的值,可以通過將`length`設定為0來清空陣列。
    ```
var arr = [ 'a', 'b', 'c' ];
arr.length = 2;
arr // ["a", "b"]
arr.length = 0;
arr // []```
	- 陣列本質上是一種物件,所以可以為陣列新增屬性,但是這不影響length屬性的值。
    ```
var a = [];
a['p'] = 'abc';
a.length // 0```
	- `for...in`:不僅會遍歷陣列所有的數字鍵,還會遍歷非數字鍵,所以不推薦使用它對陣列進行遍歷,應該使用迴圈遍歷或`forEach`方法替代。
    ```
var a = [1, 2, 3];
a.foo = true;
for (var key in a) {
  console.log(a[key]);
}
// 1
// 2
// 3
// true

a.forEach(function (item) {
  console.log(item);
});
// 1
// 2
// 3```
	- `delete`:刪除陣列元素後不影響`length`屬性,刪除的位置形成空位,讀取時返回`undefined`,所以用`length`屬性遍歷時需要注意,是無法跳過空位的。
    ```
var a = [, , ,];
a[1] // undefined(空位與該位置值為undefined不一樣,空位在forEach、for...in、Object.keys遍歷時會跳過)```
	- 類陣列物件(array-like object):很像陣列的物件,如果一個物件的所有鍵名都是正整數或零,並且有length屬性(非動態,不會隨著成員的變化而變化),不具備陣列的特有方法,常見的有`arguments`物件、大多數Dom元素集、字串,`slice`方法可以將類陣列物件轉換為真正的陣列,除此之外可以通過`call()`把陣列方法嫁接到物件上。
    ```
var obj = {
  0: 'a',
  1: 'b',
  2: 'c',
  length: 3
};

obj[3] = 'd';
obj.length // 3

// forEach 方法(本來arguments物件無法呼叫該方法,但這種方法比直接使用陣列原生的forEach要慢,所以最好還是先將“類似陣列的物件”轉為真正的陣列,然後再直接呼叫陣列的forEach方法。)
function logArgs() {
  Array.prototype.forEach.call(arguments, function (elem, i) {
    console.log(i + '. ' + elem);
  });
}```
# 參考資料
JavaScript 語言入門教程 :[https://wangdoc.com/javascript/index.html](https://wangdoc.com/javascript/index.html)