廖雪峰 JavaScript 學習筆記
- JavaScript是世界上最流行的指令碼語言,JavaScript是一種執行在瀏覽器中的解釋型的程式語言。
- 在Web世界裡,只有JavaScript能跨平臺、跨瀏覽器驅動網頁,與使用者互動。
- 新興的Node.js把JavaScript引入到了伺服器端,JavaScript已經變成了全能型選手。
- JavaScript確實很容易上手,但其精髓卻不為大多數開發人員所熟知。編寫高質量的JavaScript程式碼更是難上加難。
JavaScript簡介
- 由於網景公司希望能在靜態HTML頁面上新增一些動態效果,於是叫Brendan Eich這哥們在兩週之內設計出了JavaScript語言。你沒看錯,這哥們只用了10天時間。
- 為什麼起名叫JavaScript?原因是當時Java語言非常紅火,所以網景公司希望借Java的名氣來推廣,但事實上JavaScript除了語法上有點像Java,其他部分基本上沒啥關係。
快速入門
- // 以雙斜槓開頭直到行末的是註釋,註釋是給人看的,會被瀏覽器忽略
- /* 在這中間的也是註釋,將被瀏覽器忽略 */
- 如果你對自己還有更高的要求,可以研究開發者工具的“原始碼(Sources)”,掌握斷點、單步執行等高階除錯技巧。
基本語法
- JavaScript的語法和Java語言類似,每個語句以
;
結束,語句塊用{...}
,可以巢狀。
- JavaScript每個語句的結尾最好加
;
確保執行結果與期望一致。 - 花括號{…}內語句最好縮排,可以幫助整理程式碼。
- JavaScript每個語句的結尾最好加
資料型別和變數
計算機能處理的有數值、文字、圖形、音訊、視訊、網頁等各種各樣的資料,不同的資料,需要定義不同的資料型別。
在JavaScript中定義了以下幾種資料型別:
1. Number: JavaScript不區分整數和浮點數,統一用Number表示
2. 字串: 字串是以單引號’或雙引號”括起來的任意文字
3. 布林值:布林值只有true
、false
兩種值.與、或、非 分別用 &&,||,!
表示
4. 陣列: 陣列是一組按順序排列的集合,集合的每個值稱為元素。JavaScript的陣列可以包括任意資料型別。[1, 2, 3.14, 'Hello', null, true];
new Array(1, 2, 3);
5. 物件:JavaScript的物件是一組由鍵-值組成的無序集合。物件的鍵都是字串型別,值可以是任意資料型別。每個鍵又稱為物件的屬性。獲取一個物件的屬性,我們用
物件變數.屬性名
的方式
- 十六進位制字首:
0x
。例如:0xff00
,0xa5b4c3d2
- JavaScript允許對任意資料型別做比較
- 相等運算子:
==
比較會轉換資料型別後比較,===
比較不會轉自動換資料型別,直接比較 NaN
這個特殊的Number
與所有其他值都不相等,包括它自己- 唯一能判斷
NaN
的方法是通過isNaN()
函式 - 浮點數的相等比較: 因為計算機無法精確表示無限迴圈小數。要比較兩個浮點數是否相等,只能計算它們之差的絕對值,看是否小於某個閾值
Math.abs(1 / 3 - (1 - 2 / 3)) < 0.0000001; // true
- 陣列的元素可以通過索引來訪問。索引的起始值為
0
- 申明一個變數用
var
語句,變數名也可以用中文,但是,請不要給自己找麻煩。 - 變數本身型別不固定的語言稱之為動態語言,與之對應的是靜態語言。Java是靜態語言
- 變數沒有通過
var
申明就被使用,該變數就自動被申明為全域性變數,使用var
申明的變數則不是全域性變數,它的範圍被限制在該變數被申明的函式體內,為了修補JavaScript這一嚴重設計缺陷,ECMA在後續規範中推出了strict模式.啟用strict模式的方法是在JavaScript程式碼的第一行寫上:'use strict';
字串
- 轉義字元:
'I\'m \"OK\"!';
表示I'm "OK"!
- 十六進位制
\x41
可以表示ASCII
字元"A"
,個Unicode字元可用\u####
表示 - 多行字串:用反引號表示,鍵盤的ESC下方。類似
python
中'''...'''
- 模板字串:把多個字串連線起來,可以用
+
號連線。ES6
用${var}
自動替換字串中的變數,此時字串引號改為反引號。類似python3
中'{}'.fromat(var)
字串操作:
s.length,s[0]
- 字串是不可變的,字串的某個索引賦值無意義
- JavaScript字串常用方法,呼叫方法返回新字串:
s='Hello';s.toUpperCase();s.toLowerCase();s.indexOf('w');s.substring(0,5);s.substring(7);
陣列
JavaScript陣列常用方法:
1. Array
的長度:arr.length
2. arr.indexOf(var);
3. arr.slice(0,3);arr.slice(3);arr.slice();
4. arr.push('A', 'B');arr.pop();
5. arr.unshift('A', 'B');arr.shift();
6. arr.sort();
7. arr.reverse();
8. arr.concat([1, 2, 3]);
,類似python
的lst.extend
9. arr.join('-');
10. splice()
方法是修改Array的“萬能方法”,它可以從指定的索引開始刪除若干元素,然後再從該位置新增若干元素:
var arr = ['Microsoft', 'Apple', 'Yahoo', 'AOL', 'Excite', 'Oracle'];
// 從索引2開始刪除3個元素,然後再新增兩個元素:
arr.splice(2, 3, 'Google', 'Facebook'); // 返回刪除的元素 ['Yahoo', 'AOL', 'Excite']
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
// 只刪除,不新增:
arr.splice(2, 2); // ['Google', 'Facebook']
arr; // ['Microsoft', 'Apple', 'Oracle']
// 只新增,不刪除:
arr.splice(2, 0, 'Google', 'Facebook'); // 返回[],因為沒有刪除任何元素
arr; // ['Microsoft', 'Apple', 'Google', 'Facebook', 'Oracle']
Array
的索引可賦值- 直接給
Array
的length
賦一個新的值會導致Array
大小的變化,不建議直接修改Array的大小 slice()
就是對應String
的substring()
版本- sort()直接修改當前Array的元素位置
- concat()方法返回新的Array
- 如果Array的元素不是字串,將自動轉換為字串後再
join
。
物件
JavaScript的物件是一種無序的集合資料型別,它由若干鍵值對組成。用{…}表示物件,鍵值對以xxx: xxx
形式申明,用,
隔開
JavaScript陣列常用方法:
1. 新增age屬性:xiaoming.age = 18;
2. 刪除age屬性: delete xiaoming.age;
3. 判斷屬性存在:'age' in xiaoming;
4. 判斷屬性是否自身擁有:xiaoming.hasOwnProperty('name');
- 屬性名包含特殊字元,就必須用”括起來,訪問該屬性必須用[‘xxx’]來訪問
- 訪問不存在的屬性不報錯,而是返回undefined
- JavaScript的物件是動態型別,你可以自由地給一個物件新增或刪除屬性
- 如果
in
判斷一個屬性存在,這個屬性不一定是xiaoming
的,它可能是xiaoming
繼承得到的
條件判斷
JavaScript使用if () { ... } else { ... }
來進行條件判斷
- 語句塊只包含一條語句,那麼可以省略
{}
,建議永遠都要寫上{}
- JavaScript把
null、undefined、0、NaN
和空字串”視為false
,其他值一概視為true
,因此上述程式碼條件判斷的結果是true
。
迴圈
for
迴圈
var x = 0;
var i;
for (i=1; i<=10000; i++) { // 分號間隔
x = x + i;
}
x; // 50005000
i=1; i<=10000; i++
分別為初始條件、判斷條件、遞增條件for
迴圈最常用的地方是利用索引來遍歷陣列for
迴圈的3個條件都是可以省略的,如果沒有退出迴圈的判斷條件,就必須使用break
語句退出迴圈,否則就是死迴圈for
迴圈的一個變體是for (var key in o) in
迴圈,它可以把一個物件的所有屬性依次迴圈出來- 由於Array也是物件,而它的每個元素的索引被視為物件的屬性,因此,for … in迴圈可以直接迴圈出Array的索引
var a = ['A', 'B', 'C'];
for (var i in a) {
console.log(i); // '0', '1', '2'
console.log(a[i]); // 'A', 'B', 'C'
}
while
迴圈
while
迴圈只有一個判斷條件,條件滿足,就不斷迴圈,條件不滿足時則退出迴圈。
var x = 0;
var n = 99;
while (n > 0) {
x = x + n;
n = n - 2;
}
x; // 2500
do ... while
迴圈
var n = 0;
do {
n = n + 1;
} while (n < 100);
n; // 100
do { ... } while()
迴圈體至少會執行1次,而for
和while
迴圈則可能一次都不執行。
Map和Set 資料型別
Map和Set是ES6標準新增的資料型別
Map
var m = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]]); // 二維陣列初始化 Map
m.get('Michael'); // 95
var m = new Map(); // 初始化一個空 Map
m.set('Adam', 67); // 新增新的 key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在 key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 刪除 key 'Adam'
- Map是一組鍵值對的結構,具有極快的查詢速度。
- 多次對一個key放入value,後面的值會把前面的值沖掉
Set 集合
var s = new Set(); // 初始化一個空 Set
var s2 = new Set([1, 2, 3]); // 陣列初始化 Set
s.add(4); // 新增元素
s.delete(3); // 刪除元素
- Set和Map類似,也是一組不重複
key
的集合,且不儲存value
iterable
for ... of
迴圈
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
for (var x of a) { // 遍歷Array
console.log(x);
}
for (var x of s) { // 遍歷Set
console.log(x);
}
for (var x of m) { // 遍歷Map
console.log(x[0] + '=' + x[1]);
}
for ... of
迴圈和for ... in
迴圈有何區別?
答:for ... in
遍歷的實際上是物件的屬性名稱,會把新增屬性包括在內。for ... of
迴圈,它只迴圈集合本身的元素
iterable內建的forEach方法
var a = ['A', 'B', 'C'];
var s = new Set(['A', 'B', 'C']);
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
a.forEach(function (element, index, array) {
// element: 指向當前元素的值
// index: 指向當前索引
// array: 指向Array物件本身
console.log(element + ', index = ' + index);
});
for ... of
迴圈是ES6
引入的新的語法。具有iterable
型別的集合可以通過新的for ... of
迴圈來遍歷- 一個Array陣列實際上也是一個物件,它的每個元素的索引被視為一個屬性
- iterable內建的forEach方法接收一個函式,每次迭代就自動回撥該函式
- Set與Array類似,但Set沒有索引,因此回撥函式的前兩個引數都是元素本身
- Map的回撥函式引數依次為value、key和map本身
- 由於JavaScript的函式呼叫不要求引數必須一致,可以忽略
函式
函式定義和呼叫
function myabs(x) { //第一種定義函式的方式
// var abs = function (x) { //第二種定義函式的方式,匿名函式,賦值給了變數myabs
if (typeof x !== 'number') { // 引數檢查
throw 'Not a number';
}
if (x >= 0) {
return x;
} else {
return -x;
}
}
- 函式執行到return時結束,並將結果返回
- 沒有return語句,函式執行完畢後也會返回結果,只是結果為undefined,類似
python
返回None
- 第二種方式按照完整語法需要在函式體末尾加一個;,表示賦值語句結束。
- JavaScript允許傳入任意個引數而不影響呼叫
- JavaScript關鍵字arguments,只在函式內部起作用,並且永遠指向當前函式的呼叫者傳入的所有引數。arguments類似Array但它不是一個Array
- 利用arguments,即使函式不定義任何引數,還是可以拿到引數的值
rest引數
function foo(a, b, ...rest) {
console.log('a = ' + a);
console.log('b = ' + b);
console.log(rest);
}
foo(1, 2, 3, 4, 5);
// 結果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]
foo(1);
- rest引數只能寫在最後,前面用…標識
變數作用域與解構賦值
- var申明的變數實際上是有作用域的
- 函式體內部申明變數的作用域為整個函式體,在函式體外不可引用
- JavaScript的函式可以巢狀,內部函式可以訪問外部函式定義的變數,反過來則不行
- 內部函式定義了與外部函式重名的變數,則內部函式的變數將“遮蔽”外部函式的變數
- 在函式內部定義變數時,請嚴格遵守“在函式內部首先申明所有變數”這一規則。最常見的做法是用一個var申明函式內部用到的所有變數
- 不在任何函式內定義的變數就具有全域性作用域(只有一個)。實際上,JavaScript預設有一個全域性物件window,全域性作用域的變數實際上被繫結到window的一個屬性
- 全域性變數會繫結到window上,為避免造成命名衝突,並且很難被發現。減少衝突的一個方法是把自己的所有變數和函式全部繫結到一個全域性變數中
// 唯一的全域性變數MYAPP:
var MYAPP = {};
// 其他變數:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函式:
MYAPP.foo = function () {
return 'foo';
};
- JavaScript的變數作用域實際上是函式內部,for迴圈等語句塊中定義的迴圈變數在函式內部均有效。為了解決塊級作用域,ES6引入了新的關鍵字let,用let替代var可以申明一個塊級作用域的變數
- var和let申明的是變數
- ES6標準引入了新的關鍵字const來定義常量,const與let都具有塊級作用域
解構賦值
從ES6開始,JavaScript引入瞭解構賦值,可以同時對一組變數進行賦值。
var [x, y, z] = ['hello', 'JavaScript', 'ES6'];
// x, y, z分別被賦值為陣列對應元素:
console.log('x = ' + x + ', y = ' + y + ', z = ' + z);
// x = hello, y = JavaScript, z = ES6
let [x, [y, z]] = ['hello', ['JavaScript', 'ES6']];
var person = {
name: '小明',
age: 20,
gender: 'male',
passport: 'G-12345678',
school: 'No.4 middle school'
};
var {name, age, passport} = person;
// name, age, passport分別被賦值為對應屬性:
console.log('name = ' + name + ', age = ' + age + ', passport = ' + passport);
var {name, single=true} = person;
- 使用解構賦值可以減少程式碼量
- 對陣列元素進行解構賦值時,多個變數要用[…]括起來
- 對一個物件進行解構賦值時,同樣可以直接對巢狀的物件屬性進行賦值,只要保證對應的層次是一致的
- 解構賦值如果對應的屬性不存在,變數將被賦值為
undefined
- 解構賦值還可以使用預設值,這樣就避免了不存在的屬性返回undefined的問題
- 交換兩個變數
[x, y] = [y, x]
- 如果一個函式接收一個物件作為引數,那麼,可以使用解構直接把物件的屬性繫結到變數中。例如,下面的函式可以快速建立一個Date物件
function buildDate({year, month, day, hour=0, minute=0, second=0}) {
return new Date(year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second);
}
// 只需要year、month和day這三個屬性
buildDate({ year: 2017, month: 1, day: 1 });
// Sun Jan 01 2017 00:00:00 GMT+0800 (CST)
方法
繫結到物件上的函式稱為方法
var xiaoming = {
name: '小明',
birth: 1990,
age: function () {
var y = new Date().getFullYear();
return y - this.birth;
}
};
xiaoming.age; // function xiaoming.age()
xiaoming.age(); // 今年呼叫是25,明年呼叫就變成26了
- 在一個方法內部,this是一個特殊變數,它始終指向當前物件,也就是xiaoming這個變數
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25, 正常結果
getAge(); // NaN
- 以物件的方法形式呼叫,比如xiaoming.age(),該函式的this指向被呼叫的物件,也就是xiaoming,這是符合我們預期的。
- 如果單獨呼叫函式,比如getAge(),此時,該函式的this指向全域性物件,也就是window
// 用apply修復getAge()呼叫
function getAge() {
var y = new Date().getFullYear();
return y - this.birth;
}
var xiaoming = {
name: '小明',
birth: 1990,
age: getAge
};
xiaoming.age(); // 25
getAge.apply(xiaoming, []); // 25, this指向xiaoming, 引數為空
apply()類似的方法是call(),唯一區別是:apply()把引數打包成Array再傳入;call()把引數按順序傳入。
高階函式
一個函式就可以接收另一個函式作為引數,稱之為高階函式
map/reduce
- map()方法定義在JavaScript的Array中,我們呼叫Array的map()方法,傳入我們自己的函式,就得到了一個新的Array作為結果
- map()傳入的引數是pow,即函式物件本身,把運算規則抽象