全手打原創,轉載請標明出處: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),相當於定位符,用於跳轉到程序的任意位置,例如:
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_VALUE
和MIN_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