本文為作者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
方法可以用來判斷一個值是否為NaN
。isNaN
只對數值有效,如果傳入其他值,會被先轉成數值.也就是說,isNaN
為true
的值,有可能不是NaN
,而是一個字串。
isFinite()
isFinite
方法返回一個布林值,表示某個值是否為正常的數值。除了Infinity
、-Infinity
、NaN
和undefined
這幾個值會返回false
,isFinite
對於其他的數值都會返回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
複製程式碼
上面程式碼中,引用物件obj
的foo
屬性時,如果使用點運算子,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
很像陣列,但它是一個物件。陣列專有的方法(比如slice
和forEach
),不能在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
方法。