廖JS課程

1. 字符串


  • ASCII字符可以以\#XX形式的十六進制表示,例如:
'\x41' // 等同于A
  • 還可以用\u####表示一個Unicode字符:
'\u4e2d\u6587' // 等同于'中文'
  • 由于多行字符串用\n寫起來比較費事,所以最新的ES6標準新增了一種多行字符串的表示方法,* ... *表示:
alert(`多行
字符串
測試`);

2. 數(shù)組


  • 多維數(shù)組
    舉例,構(gòu)建二維數(shù)組,規(guī)模為mxn,值全部初始化為initial
    構(gòu)建一個二維數(shù)組

3. 對象


  • JS中有一點很奇特,就是JavaScript規(guī)定,訪問一個對象中不存在的屬性不報錯,而是返回undefined。

4. Map和Set


JavaScript的默認對象表示方式{}可以視為其他語言中的MapDictionary的數(shù)據(jù)結(jié)構(gòu),即一組鍵值對。
 但是JavaScript的對象有個小問題,就是鍵必須是字符串。但實際上Number或者其他數(shù)據(jù)類型作為鍵也是非常合理的。
 為了解決這個問題,最新的ES6規(guī)范引入了新的數(shù)據(jù)類型Map。

4.1 Map

Map是一組鍵值對的結(jié)構(gòu),具有極快的查找速度。
 舉個例子,假設(shè)要根據(jù)同學(xué)的名字查找對應(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。
    Map的具體操作

     由于一個key只能對應(yīng)一個value,所以,多次對一個key放入value,后面的值會把前面的值沖掉:
    Map的值覆蓋

4.2 Set

SetMap類似,也是一組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),遍歷MapSet就無法使用下標。
 為了統(tǒng)一集合類型,ES6標準引入了新的iterable類型,Array、MapSet都屬于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...infor...of的區(qū)別
     for ... in循環(huán)由于歷史遺留問題,它遍歷的實際上是對象的屬性名稱。一個Array數(shù)組實際上也是一個對象,它的每個元素的索引被視為一個屬性
     當我們手動給Array對象添加了額外的屬性后,for ... in循環(huán)將帶來意想不到的意外效果:
    `for ... in`的缺陷

     上面的例子中,for ... in循環(huán)將把name包括在內(nèi),但Arraylength屬性卻不包括在內(nèi)。
     for ... of循環(huán)則完全修復(fù)了這些問題,它只循環(huán)集合本身的元素
    `for...of`彌補了`for...in`的缺陷
  • iterable內(nèi)置的forEach方法
     for...of方法相比,forEach方法更好。它接收一個函數(shù),每次迭代就自動回調(diào)該函數(shù)。
     1. ArrayforEach
 var a = ['A', 'B', 'C'];
    a.forEach(function (element, index, array) { 
         // element: 指向當前元素的值 
         // index: 指向當前索引 
         // array: 指向Array對象本身
         alert(element);
 });

2. SetArray類似,但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、keymap本身

    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.lengtharguments.callee.length前者是實際傳入的參數(shù)的個數(shù),后者是定義函數(shù)時,定義的形參的個數(shù)。

  • rest參數(shù)
     由于JavaScript函數(shù)允許接收任意個參數(shù),于是我們就不得不用arguments來獲取所有參數(shù):

    使用`arguments`獲取額外傳入的參數(shù)

     為了獲取除了已定義參數(shù)a、b之外的參數(shù),我們不得不用arguments,并且循環(huán)要從索引2開始以便排除前兩個參數(shù),這種寫法很別扭,只是為了獲得額外的rest參數(shù),有沒有更好的方法?
     Duang?。。?/strong>,ES6標準引入了rest參數(shù),上面的函數(shù)可以改寫為:
    `rest`的使用

     注意:現(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可以申明一個塊級作用域的變量
使用`let`聲明一個塊級作用域
  • 常量
     由于varlet申明的是變量,如果要申明一個常量,在ES6之前是不行的,我們通常用全部大寫的變量來表示“這是一個常量,不要修改它的值”。
     ES6標準引入了新的關(guān)鍵字const來定義常量,constlet都具有塊級作用域。
'use strict';
 const PI = 3.14;
 PI = 3; // 某些瀏覽器不報錯,但是無效果!
 PI; // 3.14

6.3 generator

generator(生成器)是ES6標準引入的新的數(shù)據(jù)類型。一個generator看上去像一個函數(shù),但可以返回多次。
 先看一個generator的栗子:

`generator`的小栗子

 再舉個栗子,fibnacci數(shù)列:
`generator`對象中的 `next( )`方法介紹

 直接調(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é)束了。如果donetrue,則value就是return的返回值。
     當執(zhí)行到donetrue時,這個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,舉個栗子:

**為了保證異步事件的執(zhí)行順序,需要嵌套,十分丑陋**

像上面的例子中,回調(diào)越多,為了保證代碼的執(zhí)行順序,代碼嵌套的層級越多,代碼越難看。
  有了generator的美好時代,用AJAX時可以這么寫:

使用`generator`展開異步事件

  看上去是同步的代碼,實際執(zhí)行是異步的

7. 標準對象


7.1 Date

  • 獲取時區(qū)
     Date對象表示的時間總是按瀏覽器所在時區(qū)顯示的,不過我們既可以顯示本地時間,也可以顯示調(diào)整后的UTC時間
    獲取時區(qū)

     那么在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標準;
  • ChromeGoogle出品的基于Webkit內(nèi)核瀏覽器,內(nèi)置了非常強悍的JavaScript引擎——V8。由于Chrome一經(jīng)安裝就時刻保持自升級,所以不用管它的版本,最新版早就支持ES6了;
  • SarafiApple的Mac系統(tǒng)自帶的基于Webkit內(nèi)核的瀏覽器,從OS X10.7 Lion自帶的6.1版本開始支持ES6,目前最新的OS X 10.10 Yosemite自帶的Sarafi版本是8.x,早已支持ES6;
  • FirefoxMozilla自己研制的Gecko內(nèi)核和JavaScript引擎OdinMonkey。早起的Firefox按版本發(fā)布,后來終于聰明地學(xué)習(xí)Chrome的做法進行自升級,時刻保持最新;
  • 移動設(shè)備上目前iOSAndroid兩大陣營分別主要使用AppleSafariGoogleChrome,由于兩者都是Webkit核心,結(jié)果HTML5首先在手機上全面普及(桌面絕對是Microsoft拖了后腿),對JavaScript的標準支持也很好,最新版本均支持ES6。
     其他瀏覽器如Opera等由于市場份額太小就被自動忽略了。
     另外還要注意識別各種國產(chǎn)瀏覽器,如某某安全瀏覽器,某某旋風(fēng)瀏覽器,它們只是做了一個殼,其核心調(diào)用的是IE,也有號稱同時支持IE和Webkit的“雙核”瀏覽器
     不同的瀏覽器對JavaScript支持的差異主要是,有些API的接口不一樣,比如AJAX,F(xiàn)ile接口。對于ES6標準,不同的瀏覽器對各個特性支持也不一樣。
     在編寫JavaScript的時候,就要充分考慮到瀏覽器的差異,盡量讓同一份JavaScript代碼能運行在不同的瀏覽器上。
     JavaScript可以獲取瀏覽器提供的很多對象,并進行操作:
  • window
     window對象不但充當全局作用域,而且表示瀏覽器窗口。
     window對象有innerWidthinnerHeight屬性,可以獲取瀏覽器窗口的內(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

    一條完整的URL

     可以用location.href獲取。要獲得URL各個部分的值,可以這么寫:
    使用`location` 對象的不同屬性獲取 `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è)定了httpOnlyCookie將不能被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也無法獲得該文件的真實路徑

JS無法獲取上傳文件的真實路徑

 通常,上傳的文件都由后臺服務(wù)器處理,JavaScript可以在提交表單時對文件擴展名做檢查,以便防止用戶上傳無效格式的文件。

  • File API
     由于JavaScript對用戶上傳的文件操作非常有限,尤其是無法讀取文件內(nèi)容,使得很多需要操作文件的網(wǎng)頁不得不用Flash這樣的第三方插件來實現(xiàn)。
     隨著HTML5的普及,新增的File API允許JavaScript讀取文件內(nèi)容,獲得更多的文件信息。
     HTML5File API提供了FileFileReader兩個主要對象,可以獲得文件信息并讀取文件

13. Ajax


  • 兼容模式生成XHR對象


    兼容模式生成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.comexample.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ù)器實現(xiàn)外域訪問

     代理服務(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ù):

動態(tài)加入`<script>`元素

 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、HEADPOSTPOSTContent-Type類型僅限application/x-www-form-urlencoded、multipart/form-datatext/plain),并且不能出現(xiàn)任何自定義頭(例如,X-Custom: 12345),通常能滿足90%的需求。
 在引用外域資源時,除了JavaScript和CSS外,都要驗證CORS。例如,當你引用了某個第三方CDN上的字體文件時:
使用@font-face引用 `第三方CDN`上的字體

 對于PUT、DELETE以及其他類型如application/jsonPOST請求,在發(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中很常見,所以要跨域正確處理POSTPUT請求,服務(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

跨瀏覽器的`CORS`實現(xiàn)

14. promise


在JavaScript的世界中,所有代碼都是單線程執(zhí)行的。
 由于這個“缺陷”,導(dǎo)致JavaScript的所有網(wǎng)絡(luò)操作,瀏覽器事件,都必須是異步執(zhí)行。異步操作會在將來的某個時間點觸發(fā)一個函數(shù)調(diào)用,AJAX就是典型的異步操作。

使用promise可以實現(xiàn)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ī)范,由瀏覽器直接支持

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容