1. 字符串
- ASCII字符可以以
\#XX
形式的十六進制表示间景,例如:
'\x41' // 等同于A
- 還可以用
\u####
表示一個Unicode字符:
'\u4e2d\u6587' // 等同于'中文'
- 由于多行字符串用
\n
寫起來比較費事豌研,所以最新的ES6標準新增了一種多行字符串的表示方法,* ... *
表示:
alert(`多行
字符串
測試`);
2. 數(shù)組
- 多維數(shù)組
舉例履肃,構(gòu)建二維數(shù)組,規(guī)模為mxn
,值全部初始化為initial
3. 對象
- JS中有一點很奇特组力,就是JavaScript規(guī)定,訪問一個對象中不存在的屬性不報錯抖拴,而是返回
undefined
燎字。
4. Map和Set
JavaScript的默認對象表示方式{}
可以視為其他語言中的Map
或Dictionary
的數(shù)據(jù)結(jié)構(gòu),即一組鍵值對阿宅。
但是JavaScript的對象有個小問題候衍,就是鍵必須是字符串。但實際上Number或者其他數(shù)據(jù)類型作為鍵也是非常合理的洒放。
為了解決這個問題蛉鹿,最新的ES6規(guī)范引入了新的數(shù)據(jù)類型Map
。
4.1 Map
Map
是一組鍵值對的結(jié)構(gòu)往湿,具有極快的查找速度妖异。
舉個例子,假設(shè)要根據(jù)同學的名字查找對應(yīng)的成績领追,如果用Array
實現(xiàn)他膳,需要兩個Array
:
var names = ['Michael', 'Bob', 'Tracy'];
var scores = [95, 75, 85];
給定一個名字,要查找對應(yīng)的成績绒窑,就先要在names中找到對應(yīng)的位置棕孙,再從scores取出對應(yīng)的成績,Array越長些膨,耗時越長散罕。
如果用Map實現(xiàn),只需要一個“名字”-“成績”的對照表傀蓉,直接根據(jù)名字查找成績欧漱,無論這個表有多大,查找速度都不會變慢葬燎。用JavaScript寫一個Map如下:
var m = new Map([['Michael', 95], ['Bob', 75],
['Tracy', 85]]);
m.get('Michael'); // 95
通過上面的例子误甚,可以看出Map
其實看成一個二維數(shù)組缚甩,Map
中的每一個元素都是一個一維數(shù)組,有一個key
和 與之對應(yīng)的一個value
窑邦。
- Map的具體操作
初始化Map
需要一個二維數(shù)組擅威,或者直接初始化一個空Map
。
由于一個key
只能對應(yīng)一個value
冈钦,所以郊丛,多次對一個key
放入value
,后面的值會把前面的值沖掉:
4.2 Set
Set
和Map
類似瞧筛,也是一組key
的集合厉熟,但不存儲value
。由于key
不能重復(fù)较幌,所以揍瑟,在Set
中,沒有重復(fù)的key
乍炉。
要創(chuàng)建一個Set
绢片,需要提供一個Array
作為輸入,或者直接創(chuàng)建一個空Set
:
var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3
重復(fù)元素在Set
中自動被過濾:
var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}
通過add(key)
方法可以添加元素到Set中岛琼,可以重復(fù)添加底循,但不會有效果:
>>> s.add(4)
>>> s
{1, 2, 3, 4}
>>> s.add(4)
>>> s
{1, 2, 3, 4}
通過delete(key)
方法可以刪除元素:
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}
5. iterable
遍歷Array
可以采用下標循環(huán),遍歷Map
和Set
就無法使用下標槐瑞。
為了統(tǒng)一集合類型此叠,ES6標準引入了新的iterable
類型,Array
随珠、Map
和Set
都屬于iterable
類型
具有iterable
類型的集合可以通過新的for ... of
循環(huán)來遍歷。for ... of
循環(huán)是ES6引入的新的語法猬错。
- iterable的使用
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
alert(x);
}
for (var x of s) { // 遍歷Set
alert(x);
}
for (var x of m) { // 遍歷Map
alert(x[0] + '=' + x[1]);
}
-
for...in
和for...of
的區(qū)別
for ... in
循環(huán)由于歷史遺留問題窗看,它遍歷的實際上是對象的屬性名稱。一個Array
數(shù)組實際上也是一個對象倦炒,它的每個元素的索引被視為一個屬性显沈。
當我們手動給Array
對象添加了額外的屬性后,for ... in
循環(huán)將帶來意想不到的意外效果:
上面的例子中逢唤,for ... in
循環(huán)將把name
包括在內(nèi)拉讯,但Array
的length
屬性卻不包括在內(nèi)。
for ... of
循環(huán)則完全修復(fù)了這些問題鳖藕,它只循環(huán)集合本身的元素:
-
iterable
內(nèi)置的forEach
方法
與for...of
方法相比魔慷,forEach
方法更好。它接收一個函數(shù)著恩,每次迭代就自動回調(diào)該函數(shù)院尔。
1.Array
的forEach
var a = ['A', 'B', 'C'];
a.forEach(function (element, index, array) {
// element: 指向當前元素的值
// index: 指向當前索引
// array: 指向Array對象本身
alert(element);
});
2. Set
與Array
類似蜻展,但Set
沒有索引,因此回調(diào)函數(shù)最多兩個參數(shù):
var s = new Set(['A', 'B', 'C']);
s.forEach(function (element, set) {
console.log(element); // 'A','B','C'
});
3. Map
的回調(diào)函數(shù)參數(shù)依次為value
邀摆、key
和map
本身
var m = new Map([[1, 'x'], [2, 'y'], [3, 'z']]);
m.forEach(function (value, key, map) {
console.log(value); // 'x','y','z'
});
6. 函數(shù)
6.1 函數(shù)的定義與調(diào)用
arguments
JavaScript還有一個免費贈送的關(guān)鍵字arguments
纵顾,它只在函數(shù)內(nèi)部起作用,和this
關(guān)鍵字一樣栋盹,并且永遠指向當前函數(shù)的調(diào)用者傳入的所有參數(shù)施逾。arguments
類似Array
,但它不是一個Array
例获。
利用arguments
汉额,你可以獲得調(diào)用者傳入的所有參數(shù)。也就是說躏敢,即使函數(shù)不定義任何參數(shù)闷愤,還是可以拿到參數(shù)的值。
實際上arguments
最常用于判斷傳入?yún)?shù)的個數(shù)件余。注意區(qū)別arguments.length
和arguments.callee.length
讥脐。前者是實際傳入的參數(shù)的個數(shù),后者是定義函數(shù)時啼器,定義的形參的個數(shù)旬渠。-
rest參數(shù)
由于JavaScript函數(shù)允許接收任意個參數(shù),于是我們就不得不用arguments
來獲取所有參數(shù):
為了獲取除了已定義參數(shù)a
端壳、b
之外的參數(shù)告丢,我們不得不用arguments
,并且循環(huán)要從索引2開始以便排除前兩個參數(shù),這種寫法很別扭亏栈,只是為了獲得額外的rest
參數(shù)粒竖,有沒有更好的方法?
DuangB妗!栗精!闯参,ES6標準引入了rest
參數(shù),上面的函數(shù)可以改寫為:
注意:現(xiàn)在瀏覽器大都還不支持...rest
的寫法悲立。 小心你的return語句
JavaScript引擎有一個在行末自動添加分號的機制鹿寨,這可能讓你栽到return語句的一個大坑:
function foo() {
return
{ name: 'foo' };
}
console.log(foo()); // undefined
---> 相當于下面:
function foo() {
return; // 自動添加了分號,相當于return undefined;
{ name: 'foo' }; // 這行語句已經(jīng)沒法執(zhí)行到了
}
所以正確的多行寫法是:
function foo() {
return { // 這里不會自動加分號薪夕,因為{表示語句尚未結(jié)束
name: 'foo'
};
}
6.2 變量的作用域
- 變量提升
變量的聲明會提升脚草,但是賦值不會被提升。
'use strict';
function foo() {
var x = 'Hello, ' + y; // 'Hello, undefined'
alert(x);
var y = 'Bob';
}
foo();
- 命名空間
全局變量會綁定到window
上原献,不同的JavaScript文件如果使用了相同的全局變量玩讳,或者定義了相同名字的頂層函數(shù)涩蜘,都會造成命名沖突,并且很難被發(fā)現(xiàn)熏纯。
減少沖突的一個方法是把自己的所有變量和函數(shù)全部綁定到一個全局變量中:
// 唯一的全局變量MYAPP:
var MYAPP = {};
// 其他變量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;
// 其他函數(shù):
MYAPP.foo = function () {
return 'foo';
};
把自己的代碼全部放入唯一的名字空間MYAPP
中同诫,會大大減少全局變量沖突的可能。
許多著名的JavaScript庫都是這么干的:jQuery
樟澜,YUI
误窖,underscore
等等。
- 局部作用域
由于JavaScript的變量作用域?qū)嶋H上是函數(shù)內(nèi)部秩贰,是沒有塊級作用域這個概念的霹俺,我們在for循環(huán)等語句塊中是無法定義具有局部作用域的變量的。
為了解決塊級作用域毒费,ES6引入了新的關(guān)鍵字let
丙唧,用let
替代var
可以申明一個塊級作用域的變量。
- 常量
由于var
和let
申明的是變量觅玻,如果要申明一個常量想际,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量溪厘,不要修改它的值”胡本。
ES6標準引入了新的關(guān)鍵字const
來定義常量,const
與let
都具有塊級作用域畸悬。
'use strict';
const PI = 3.14;
PI = 3; // 某些瀏覽器不報錯侧甫,但是無效果!
PI; // 3.14
6.3 generator
generator
(生成器)是ES6標準引入的新的數(shù)據(jù)類型蹋宦。一個generator
看上去像一個函數(shù)披粟,但可以返回多次。
先看一個generator
的栗子:
再舉個栗子冷冗,
fibnacci
數(shù)列:直接調(diào)用一個
generator
和調(diào)用函數(shù)不一樣守屉,fib(5)
僅僅是創(chuàng)建了一個generator
對象,還沒有去執(zhí)行它贾惦。調(diào)用
generator
對象有兩個方法,一是不斷地調(diào)用generator
對象的next()
方法敦捧,如上面的栗子所示须板。
generator
對象的next( )
方法
next( )
方法會執(zhí)行generator
的代碼,然后兢卵,每次遇到yield x
;就返回一個對象{value: x, done: true/false}
习瑰,然后“暫停”秽荤。返回的value
就是yield
的返回值甜奄,done
表示這個generator
是否已經(jīng)執(zhí)行結(jié)束了柠横。如果done
為true
,則value
就是return
的返回值课兄。
當執(zhí)行到done
為true
時牍氛,這個generator
對象就已經(jīng)全部執(zhí)行完畢,不要再繼續(xù)調(diào)用next()
了烟阐。使用
for...of
遍歷generator
對象
第二個方法是直接用for ... of
循環(huán)迭代generator
對象搬俊,這種方式不需要我們自己判斷done
。需要注意蜒茄,使用for...of
方法遍歷不到return
返回的值唉擂。generator
的用處
因為generator
可以在執(zhí)行過程中多次返回,所以它看上去就像一個可以記住執(zhí)行狀態(tài)的函數(shù)檀葛。利用這一點玩祟,寫一個generator
就可以實現(xiàn)需要用面向?qū)ο蟛拍軐崿F(xiàn)的功能。
generator
還有另一個巨大的好處屿聋,就是把異步回調(diào)代碼變成“同步”代碼空扎。例如Ajax,舉個栗子:
像上面的例子中,回調(diào)越多象对,為了保證代碼的執(zhí)行順序黑忱,代碼嵌套的層級越多,代碼越難看勒魔。
有了generator
的美好時代甫煞,用AJAX時可以這么寫:
看上去是同步的代碼,實際執(zhí)行是異步的冠绢。
7. 標準對象
7.1 Date
- 獲取時區(qū)
Date
對象表示的時間總是按瀏覽器所在時區(qū)顯示的抚吠,不過我們既可以顯示本地時間,也可以顯示調(diào)整后的UTC時間:
那么在JavaScript中如何進行時區(qū)轉(zhuǎn)換呢弟胀?實際上楷力,只要我們傳遞的是一個number
類型的時間戳,我們就不用關(guān)心時區(qū)轉(zhuǎn)換孵户。任何瀏覽器都可以把一個時間戳正確轉(zhuǎn)換為本地時間萧朝。 - 時間戳
時間戳是個什么東西?時間戳是一個自增的整數(shù)夏哭,它表示從1970年1月1日零時整的GMT時區(qū)開始的那一刻检柬,到現(xiàn)在的毫秒數(shù)。假設(shè)瀏覽器所在電腦的時間是準確的竖配,那么世界上無論哪個時區(qū)的電腦何址,它們此刻產(chǎn)生的時間戳數(shù)字都是一樣的里逆,所以,時間戳可以精確地表示一個時刻用爪,并且與時區(qū)無關(guān)原押。
所以,我們只需要傳遞時間戳项钮,或者把時間戳從數(shù)據(jù)庫里讀出來班眯,再讓JavaScript自動轉(zhuǎn)換為當?shù)貢r間就可以了。
要獲取當前時間戳烁巫,可以用:
7.2 正則表達式
- 貪婪匹配
需要特別指出的是署隘,正則匹配默認是貪婪匹配,也就是匹配盡可能多的字符亚隙。
由于\d+
采用貪婪匹配磁餐,直接把后面的0全部匹配了,結(jié)果0*
只能匹配空字符串了阿弃。
必須讓\d+
采用非貪婪匹配(也就是盡可能少匹配)诊霹,才能把后面的0匹配出來,加個?
就可以讓\d+
采用非貪婪匹配渣淳。
注意:上面兩個例子中使用了^
和$
脾还,這樣可以在全局上進行一次匹配。 - 全局匹配
全局匹配類似搜索入愧,因此不能使用/^...$/
鄙漏,那樣只會最多匹配一次。
8. 面向?qū)ο缶幊?/h1>
8.1 原型繼承
在傳統(tǒng)的基于Class的語言如Java棺蛛、C++中怔蚌,繼承的本質(zhì)是擴展一個已有的Class,并生成新的Subclass旁赊。
由于這類語言嚴格區(qū)分類和實例桦踊,繼承實際上是類型的擴展。但是JavaScript由于采用原型繼承终畅,我們無法直接擴展一個Class籍胯,因為根本不存在Class這種類型。
比較新穎的繼承方法的實現(xiàn):
9. 瀏覽器
由于JavaScript的出現(xiàn)就是為了能在瀏覽器中運行离福,所以杖狼,瀏覽器自然JavaScript開發(fā)者必須要關(guān)注的。
目前主流的瀏覽器分這么幾種:
-
IE 6~11
:國內(nèi)用得最多的IE瀏覽器术徊,歷來對W3C標準支持差本刽。從IE10
開始支持ES6標準鲸湃; -
Chrome
:Google
出品的基于Webkit
內(nèi)核瀏覽器赠涮,內(nèi)置了非常強悍的JavaScript引擎——V8
子寓。由于Chrome
一經(jīng)安裝就時刻保持自升級,所以不用管它的版本笋除,最新版早就支持ES6了斜友; -
Sarafi
:Apple
的Mac系統(tǒng)自帶的基于Webkit
內(nèi)核的瀏覽器,從OS X
10.7 Lion自帶的6.1版本開始支持ES6垃它,目前最新的OS X
10.10 Yosemite自帶的Sarafi版本是8.x鲜屏,早已支持ES6; -
Firefox
:Mozilla
自己研制的Gecko
內(nèi)核和JavaScript引擎OdinMonkey
国拇。早起的Firefox
按版本發(fā)布洛史,后來終于聰明地學習Chrome的做法進行自升級,時刻保持最新酱吝; -
移動設(shè)備上目前
iOS
和Android
兩大陣營分別主要使用Apple
的Safari
和Google
的Chrome
也殖,由于兩者都是Webkit
核心,結(jié)果HTML5
首先在手機上全面普及(桌面絕對是Microsoft拖了后腿)务热,對JavaScript的標準支持也很好忆嗜,最新版本均支持ES6。
其他瀏覽器如Opera
等由于市場份額太小就被自動忽略了崎岂。
另外還要注意識別各種國產(chǎn)瀏覽器捆毫,如某某安全瀏覽器,某某旋風瀏覽器冲甘,它們只是做了一個殼绩卤,其核心調(diào)用的是IE,也有號稱同時支持IE和Webkit的“雙核”瀏覽器损合。
不同的瀏覽器對JavaScript支持的差異主要是省艳,有些API的接口不一樣,比如AJAX嫁审,F(xiàn)ile接口跋炕。對于ES6標準,不同的瀏覽器對各個特性支持也不一樣律适。
在編寫JavaScript的時候辐烂,就要充分考慮到瀏覽器的差異,盡量讓同一份JavaScript代碼能運行在不同的瀏覽器上捂贿。
JavaScript可以獲取瀏覽器提供的很多對象纠修,并進行操作: - window
window
對象不但充當全局作用域,而且表示瀏覽器窗口厂僧。
window
對象有innerWidth
和innerHeight
屬性扣草,可以獲取瀏覽器窗口的內(nèi)部寬度和高度。內(nèi)部寬高是指除去菜單欄、工具欄辰妙、邊框等占位元素后鹰祸,用于顯示網(wǎng)頁的凈寬高。
注意:IE <= 8 不兼容這兩個屬性的密浑。 - navigator
navigator
對象表示瀏覽器的信息蛙婴,最常用的屬性包括:
navigator.appName:瀏覽器名稱;
navigator.appVersion:瀏覽器版本尔破;
navigator.language:瀏覽器設(shè)置的語言街图;
navigator.platform:操作系統(tǒng)類型;
navigator.userAgent:瀏覽器設(shè)定的User-Agent字符串懒构。
請注意餐济,navigator
的信息可以很容易地被用戶修改,所以JavaScript讀取的值不一定是正確的胆剧,盡量不要使用navigator.userAgent
提供的用戶代理字符串去判斷客戶端瀏覽器颤介。
正確的方法是充分利用JavaScript對不存在屬性返回undefined
的特性,直接用短路運算符||
計算:
var width = window.innerWidth || document.body.clientWidth;
- screen
screen
對象表示屏幕的信息赞赖,常用的屬性有:
screen.width:屏幕寬度滚朵,以像素為單位;
screen.height:屏幕高度前域,以像素為單位辕近;
screen.colorDepth:返回顏色位數(shù),如8匿垄、16移宅、24;
-
location
location
對象表示當前頁面的URL
信息。例如椿疗,一個完整的URL
:
可以用location.href
獲取漏峰。要獲得URL
各個部分的值,可以這么寫:
要加載一個新頁面届榄,可以調(diào)用location.assign()
浅乔,使用location.href
也是可以的。如果要重新加載當前頁面铝条,調(diào)用location.reload()
方法非常方便靖苇。 document
document
對象表示當前頁面。由于HTML
在瀏覽器中以DOM
形式表示為樹形結(jié)構(gòu)班缰,document
對象就是整個DOM
數(shù)的根節(jié)點贤壁。
document
對象還有一個cookie
屬性,可以獲取當前頁面的Cookie
埠忘。
聊聊Cookie
:
Cookie
是由服務(wù)器發(fā)送的key-value標示符脾拆。因為HTTP協(xié)議是無狀態(tài)的馒索,但是服務(wù)器要區(qū)分到底是哪個用戶發(fā)過來的請求,就可以用Cookie來區(qū)分名船。當一個用戶成功登錄后双揪,服務(wù)器發(fā)送一個Cookie
給瀏覽器,例如user=ABC123XYZ
(加密的字符串)...包帚,此后,瀏覽器訪問該網(wǎng)站時运吓,會在請求頭附上這個Cookie
渴邦,服務(wù)器根據(jù)Cookie
即可區(qū)分出用戶。
為了防止XSS
盜取Cookie
中重要的信息拘哨,服務(wù)器在設(shè)置Cookie
時可以使用httpOnly
谋梭,設(shè)定了httpOnly
的Cookie
將不能被JavaScript讀取。這一類Cookie
稱為HTTP專有cookie倦青,HTTP專有cookie可以從瀏覽器或者服務(wù)器設(shè)置瓮床,但是只能從服務(wù)器端讀取,因此JavaScript無法獲取HTTP專有cookie的值产镐。
為了確保安全隘庄,服務(wù)器端在設(shè)置Cookie時,應(yīng)該始終堅持使用httpOnly癣亚。history
history
對象保存了瀏覽器的歷史記錄丑掺,JavaScript可以調(diào)用history
對象的back()
或forward ()
,相當于用戶點擊了瀏覽器的“后退”或“前進”按鈕述雾。
這個對象屬于歷史遺留對象街州,對于現(xiàn)代Web頁面來說,由于大量使用AJAX
和頁面交互玻孟,簡單粗暴地調(diào)用history.back()
可能會讓用戶感到非常憤怒唆缴。
新手開始設(shè)計Web頁面時喜歡在登錄頁登錄成功時調(diào)用history.back()
,試圖回到登錄前的頁面黍翎。這是一種錯誤的方法面徽。
任何情況,你都不應(yīng)該使用history
這個對象了匣掸。
10. DOM操作
- element.insertBefore(
newNode
,referenceNode
); - element.insertAdjacentHTML(
positionString
,insertHTML
);
positionString:
beforeBegin afterBegin beforeEnd afterEnd
11. 表單操作
-
form
元素的submit
事件的事件監(jiān)聽函數(shù)中斗忌,return true
告訴來告訴瀏覽器繼續(xù)提交;return false
表示瀏覽器將不會提交表單旺聚,這種情況通常對應(yīng)用戶輸入有誤织阳,提示用戶錯誤信息后終止提交form。 -
沒有
name
屬性的<input>
的數(shù)據(jù)不會被提交砰粹。
12. 操作文件
在HTML表單中唧躲,可以上傳文件的唯一控件就是<input type="file">
造挽。
尤其需要注意:
當一個表單包含
<input type="file">
時,表單的enctype
必須指定為multipart/form-data
弄痹,method
必須指定為post
饭入,瀏覽器才能正確編碼并以multipart/form-data
格式發(fā)送表單的數(shù)據(jù)。
出于安全考慮肛真,瀏覽器只允許用戶點擊<input type="file">
來選擇本地文件谐丢,用JavaScript對<input type="file">
的value
賦值是沒有任何效果的。當用戶選擇了上傳某個文件后蚓让,JavaScript也無法獲得該文件的真實路徑:
通常乾忱,上傳的文件都由后臺服務(wù)器處理,JavaScript可以在提交表單時對文件擴展名做檢查历极,以便防止用戶上傳無效格式的文件窄瘟。
-
File API
由于JavaScript對用戶上傳的文件操作非常有限,尤其是無法讀取文件內(nèi)容趟卸,使得很多需要操作文件的網(wǎng)頁不得不用Flash這樣的第三方插件來實現(xiàn)蹄葱。
隨著HTML5
的普及,新增的File API
允許JavaScript讀取文件內(nèi)容锄列,獲得更多的文件信息图云。
HTML5
的File API
提供了File
和FileReader
兩個主要對象,可以獲得文件信息并讀取文件邻邮。
13. Ajax
-
兼容模式生成XHR對象
- 原生Ajax寫法步驟
當創(chuàng)建了XMLHttpRequest
對象后琼稻,要先設(shè)置onreadystatechange
的回調(diào)函數(shù)。在回調(diào)函數(shù)中饶囚,通常我們只需通過readyState === 4
判斷請求是否完成帕翻,如果已完成,再根據(jù)status === 200
(也可以寫成(status >= 200 && status < 300) || status === 304
)判斷是否是一個成功的響應(yīng)萝风。
XMLHttpRequest
對象的open()
方法有3個參數(shù)嘀掸,第一個參數(shù)指定是GET
還是POST
,第二個參數(shù)指定URL
地址规惰,第三個參數(shù)指定是否使用異步睬塌,默認是true
,所以不用寫歇万。
注意揩晴,千萬不要把第三個參數(shù)指定為false
,否則瀏覽器將停止響應(yīng)贪磺,直到AJAX請求完成硫兰。如果這個請求耗時10秒,那么10秒內(nèi)你會發(fā)現(xiàn)瀏覽器處于“假死”狀態(tài)寒锚。
最后調(diào)用send()
方法才真正發(fā)送請求劫映。GET
請求不需要參數(shù)违孝,POST
請求需要把body
部分以字符串或者FormData
對象傳進去。 - 安全限制
默認情況下泳赋,JavaScript在發(fā)送AJAX請求時雌桑,URL的域名必須和當前頁面完全一致。這就是瀏覽器的同源策略造成的祖今。
完全一致的意思是校坑,域名要相同(www.example.com和example.com不同),協(xié)議要相同(http和https不同)千诬,端口號要相同(默認是:80端口耍目,它和:8080就不同)。有的瀏覽器口子松一點大渤,允許端口不同,大多數(shù)瀏覽器都會嚴格遵守這個限制掸绞。
那是不是用JavaScript無法請求外域(就是其他網(wǎng)站)的URL了呢泵三?方法還是有的,大概有這么幾種:
1. 通過Flash插件發(fā)送HTTP請求衔掸,這種方式可以繞過瀏覽器的安全限制烫幕,但必須安裝Flash,并且跟Flash交互敞映。不過Flash用起來麻煩较曼,而且現(xiàn)在用得也越來越少了。
2. 通過在同源域名下架設(shè)一個代理服務(wù)器來轉(zhuǎn)發(fā)振愿,JavaScript負責把請求發(fā)送到代理服務(wù)器捷犹。
代理服務(wù)器再把結(jié)果返回,這樣就遵守了瀏覽器的同源策略冕末。這種方式麻煩之處在于需要服務(wù)器端額外做開發(fā)萍歉。
3.** 稱為JSONP
,它有個限制档桃,只能用GET
請求枪孩,并且要求返回JavaScript
**。這種方式跨域?qū)嶋H上是利用了瀏覽器允許跨域引用JavaScript
資源藻肄。
以163的股票查詢URL為例蔑舞,對于URL:http://api.money.126.net/data/feed/0000001,1399001?callback=refreshPrice,你將得到如下返回:
refreshPrice({"0000001":{"code": "0000001", ... });
因此我們需要首先在頁面中準備好回調(diào)函數(shù):
function refreshPrice(data) {
var p = document.getElementById('test-jsonp');
p.innerHTML = '當前價格:' +
data['0000001'].name +': ' +
data['0000001'].price + '嘹屯;' +
data['1399001'].name + ': ' +
data['1399001'].price;
}
最后用getPrice( )
函數(shù)觸發(fā)攻询,就完成了跨域加載數(shù)據(jù):
4. CORS
如果瀏覽器支持
HTML5
,那么就可以一勞永逸地使用新的跨域策略:CORS
了州弟。CORS
全稱Cross-Origin Resource Sharing
蜕窿,是HTML5
規(guī)范定義的如何跨域訪問資源谋逻。了解CORS前,我們先搞明白概念:
Origin
表示本域桐经,也就是瀏覽器當前頁面的域毁兆。當JavaScript向外域(如sina.com)發(fā)起請求后,瀏覽器收到響應(yīng)后阴挣,首先檢查Access-Control-Allow-Origin
是否包含本域气堕,如果是,則此次跨域請求成功畔咧,如果不是茎芭,則請求失敗,JavaScript將無法獲取到響應(yīng)的任何數(shù)據(jù)誓沸。可見梅桩,跨域能否成功,取決于對方服務(wù)器是否愿意給你設(shè)置一個正確
Access-Control-Allow-Origin
拜隧,決定權(quán)始終在對方手中宿百。上面這種跨域請求,稱之為“簡單請求”洪添。簡單請求包括
GET
垦页、HEAD
和POST
(POST
的Content-Type
類型僅限application/x-www-form-urlencoded
、multipart/form-data
和text/plain
)干奢,并且不能出現(xiàn)任何自定義頭(例如痊焊,X-Custom: 12345
),通常能滿足90%的需求忿峻。在引用外域資源時薄啥,除了JavaScript和CSS外,都要驗證CORS逛尚。例如罪佳,當你引用了某個第三方CDN上的字體文件時:
對于
PUT
、DELETE
以及其他類型如application/json
的POST
請求黑低,在發(fā)送AJAX
請求之前赘艳,瀏覽器會先發(fā)送一個OPTIONS
請求(稱為preflighted
請求)到這個URL上,詢問目標服務(wù)器是否接受:
OPTIONS /path/to/resource HTTP/1.1
Host: bar.com
Origin: http://bar.com
Access-Control-Request-Method: POST
服務(wù)器必須響應(yīng)并明確指出允許的Method:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://foo.com
Access-Control-Allow-Methods: POST, GET, PUT, OPTIONS
Access-Control-Max-Age: 86400
瀏覽器確認服務(wù)器響應(yīng)的Access-Control-Allow-Methods
頭確實包含將要發(fā)送的AJAX
請求的Method克握,才會繼續(xù)發(fā)送AJAX蕾管,否則粘优,拋出一個錯誤古劲。
由于以POST
、PUT
方式傳送JSON格式的數(shù)據(jù)在REST中很常見倘是,所以要跨域正確處理POST
和PUT
請求停团,服務(wù)器端必須正確響應(yīng)OPTIONS
請求旷坦。
瀏覽器對CORS
的實現(xiàn)情況:
- IE8中引入了XDR類型
XDR與XHR有一些不同之處:
cookie不會隨請求發(fā)送掏熬,也不會隨響應(yīng)返回;
只能設(shè)置請求頭部信息中的Content-Type字段秒梅;
不能訪問響應(yīng)頭部信息旗芬;
只支持GET和POST方法;
所有的XDR請求都是異步執(zhí)行的捆蜀,請求返回后疮丛,會觸發(fā)load事件,但只能訪問響應(yīng)的原始文本(responseText)辆它,不能訪問status誊薄,并且,只要響應(yīng)有效就會觸發(fā)load事件锰茉。如果失敗(包括響應(yīng)中缺少Access-Control-Allow-Origin頭部)就會觸發(fā)error事件呢蔫。
為了支持POST請求,XDR對象提供了contentType屬性飒筑,用來表示發(fā)送數(shù)據(jù)的格式片吊。
- 其他瀏覽器對CORS的支持
其他支持HTML5的瀏覽器,都通過XHR對象實現(xiàn)對CORS的原生支持扬霜。要請求位于另一個域中的資源定鸟,使用標準的XHR對象并在open( )方法中傳入絕對URL即可而涉。
跨域的XHR對象可以訪問status屬性著瓶,而且還支持同步請求。不過啼县,跨域XHR對象也會有一些限制:
不同使用setRequestHeader( )設(shè)置自定義頭部材原;
不能發(fā)送和接收cookie;
調(diào)用getAllResponseHeader( )方法總會返回空字符串季眷;
注意以下內(nèi)容余蟹,XDR和跨域XHR有如下共同屬性/方法:
abort( ):用于停止正在進行的請求
onerror:用于替代onreadystatechange檢測錯誤
onload:用于替代onreadystatechange檢測成功
responseText:用于取得響應(yīng)內(nèi)容
send( ):用于發(fā)送請求
跨瀏覽器的CORS
14. promise
在JavaScript的世界中,所有代碼都是單線程執(zhí)行的子刮。
由于這個“缺陷”威酒,導致JavaScript的所有網(wǎng)絡(luò)操作,瀏覽器事件挺峡,都必須是異步執(zhí)行葵孤。異步操作會在將來的某個時間點觸發(fā)一個函數(shù)調(diào)用,AJAX就是典型的異步操作橱赠。
這種鏈式寫法的好處在于尤仍,先統(tǒng)一執(zhí)行AJAX邏輯,不關(guān)心如何處理結(jié)果狭姨,然后宰啦,根據(jù)結(jié)果是成功還是失敗苏遥,在將來的某個時候調(diào)success函數(shù)或fail函數(shù)。
古人云:“君子一諾千金”赡模,這種“承諾將來會執(zhí)行”的對象在JavaScript中稱為Promise對象田炭。
Promise有各種開源實現(xiàn),在ES6中被統(tǒng)一規(guī)范纺裁,由瀏覽器直接支持诫肠。