<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                <h2 id="9.1">Promise</h2> Promise是JavaScript異步操作解決方案。介紹Promise之前,先對異步操作做一個詳細介紹。 ## JavaScript的異步執行 ### 概述 Javascript語言的執行環境是"單線程"(single thread)。所謂"單線程",就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行后面一個任務。 這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執行。常見的瀏覽器無響應(假死),往往就是因為某一段Javascript代碼長時間運行(比如死循環),導致整個頁面卡在這個地方,其他任務無法執行。 JavaScript語言本身并不慢,慢的是讀寫外部數據,比如等待Ajax請求返回結果。這個時候,如果對方服務器遲遲沒有響應,或者網絡不通暢,就會導致腳本的長時間停滯。 為了解決這個問題,Javascript語言將任務的執行模式分成兩種:同步(Synchronous)和異步(Asynchronous)。"同步模式"就是傳統做法,后一個任務等待前一個任務結束,然后再執行,程序的執行順序與任務的排列順序是一致的、同步的。這往往用于一些簡單的、快速的、不涉及讀寫的操作。 "異步模式"則完全不同,每一個任務分成兩段,第一段代碼包含對外部數據的請求,第二段代碼被寫成一個回調函數,包含了對外部數據的處理。第一段代碼執行完,不是立刻執行第二段代碼,而是將程序的執行權交給第二個任務。等到外部數據返回了,再由系統通知執行第二段代碼。所以,程序的執行順序與任務的排列順序是不一致的、異步的。 以下總結了"異步模式"編程的幾種方法,理解它們可以讓你寫出結構更合理、性能更出色、維護更方便的JavaScript程序。 ### 回調函數 回調函數是異步編程最基本的方法。 假定有兩個函數f1和f2,后者等待前者的執行結果。 ```javascript f1(); f2(); ``` 如果`f1`是一個很耗時的任務,可以考慮改寫`f1`,把`f2`寫成`f1`的回調函數。 ```javascript function f1(callback){ setTimeout(function () { // f1的任務代碼 callback(); }, 1000); } ``` 執行代碼就變成下面這樣: ```javascript f1(f2); ``` 采用這種方式,我們把同步操作變成了異步操作,f1不會堵塞程序運行,相當于先執行程序的主要邏輯,將耗時的操作推遲執行。 回調函數的優點是簡單、容易理解和部署,缺點是不利于代碼的閱讀和維護,各個部分之間高度[耦合](http://en.wikipedia.org/wiki/Coupling_(computer_programming))(Coupling),使得程序結構混亂、流程難以追蹤(尤其是回調函數嵌套的情況),而且每個任務只能指定一個回調函數。 ### 事件監聽 另一種思路是采用事件驅動模式。任務的執行不取決于代碼的順序,而取決于某個事件是否發生。 還是以f1和f2為例。首先,為f1綁定一個事件(這里采用的jQuery的[寫法](http://api.jquery.com/on/))。 ```javascript f1.on('done', f2); ``` 上面這行代碼的意思是,當f1發生done事件,就執行f2。然后,對f1進行改寫: ```javascript function f1(){ setTimeout(function () { // f1的任務代碼 f1.trigger('done'); }, 1000); } ``` 上面代碼中,`f1.trigger('done')`表示,執行完成后,立即觸發`done`事件,從而開始執行`f2`。 這種方法的優點是比較容易理解,可以綁定多個事件,每個事件可以指定多個回調函數,而且可以"[去耦合](http://en.wikipedia.org/wiki/Decoupling)"(Decoupling),有利于實現模塊化。缺點是整個程序都要變成事件驅動型,運行流程會變得很不清晰。 ### 發布/訂閱 "事件"完全可以理解成"信號",如果存在一個"信號中心",某個任務執行完成,就向信號中心"發布"(publish)一個信號,其他任務可以向信號中心"訂閱"(subscribe)這個信號,從而知道什么時候自己可以開始執行。這就叫做"[發布/訂閱模式](http://en.wikipedia.org/wiki/Publish-subscribe_pattern)"(publish-subscribe pattern),又稱"[觀察者模式](http://en.wikipedia.org/wiki/Observer_pattern)"(observer pattern)。 這個模式有多種[實現](http://msdn.microsoft.com/en-us/magazine/hh201955.aspx),下面采用的是Ben Alman的[Tiny Pub/Sub](https://gist.github.com/661855),這是jQuery的一個插件。 首先,f2向"信號中心"jQuery訂閱"done"信號。 ```javascript jQuery.subscribe("done", f2); ``` 然后,f1進行如下改寫: ```javascript function f1(){ setTimeout(function () { // f1的任務代碼 jQuery.publish("done"); }, 1000); } ``` jQuery.publish("done")的意思是,f1執行完成后,向"信號中心"jQuery發布"done"信號,從而引發f2的執行。 f2完成執行后,也可以取消訂閱(unsubscribe)。 ```javascript jQuery.unsubscribe("done", f2); ``` 這種方法的性質與"事件監聽"類似,但是明顯優于后者。因為我們可以通過查看"消息中心",了解存在多少信號、每個信號有多少訂閱者,從而監控程序的運行。 ## 異步操作的流程控制 如果有多個異步操作,就存在一個流程控制的問題:確定操作執行的順序,以后如何保證遵守這種順序。 ```javascript function async(arg, callback) { console.log('參數為 ' + arg +' , 1秒后返回結果'); setTimeout(function() { callback(arg * 2); }, 1000); } ``` 上面代碼的async函數是一個異步任務,非常耗時,每次執行需要1秒才能完成,然后再調用回調函數。 如果有6個這樣的異步任務,需要全部完成后,才能執行下一步的final函數。 ```javascript function final(value) { console.log('完成: ', value); } ``` 請問應該如何安排操作流程? ```javascript async(1, function(value){ async(value, function(value){ async(value, function(value){ async(value, function(value){ async(value, function(value){ async(value, final); }); }); }); }); }); ``` 上面代碼采用6個回調函數的嵌套,不僅寫起來麻煩,容易出錯,而且難以維護。 ### 串行執行 我們可以編寫一個流程控制函數,讓它來控制異步任務,一個任務完成以后,再執行另一個。這就叫串行執行。 ```javascript var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; function series(item) { if(item) { async( item, function(result) { results.push(result); return series(items.shift()); }); } else { return final(results); } } series(items.shift()); ``` 上面代碼中,函數series就是串行函數,它會依次執行異步任務,所有任務都完成后,才會執行final函數。items數組保存每一個異步任務的參數,results數組保存每一個異步任務的運行結果。 ### 并行執行 流程控制函數也可以是并行執行,即所有異步任務同時執行,等到全部完成以后,才執行final函數。 ```javascript var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; items.forEach(function(item) { async(item, function(result){ results.push(result); if(results.length == items.length) { final(results); } }) }); ``` 上面代碼中,forEach方法會同時發起6個異步任務,等到它們全部完成以后,才會執行final函數。 并行執行的好處是效率較高,比起串行執行一次只能執行一個任務,較為節約時間。但是問題在于如果并行的任務較多,很容易耗盡系統資源,拖慢運行速度。因此有了第三種流程控制方式。 ### 并行與串行的結合 所謂并行與串行的結合,就是設置一個門檻,每次最多只能并行執行n個異步任務。這樣就避免了過分占用系統資源。 ```javascript var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; var running = 0; var limit = 2; function launcher() { while(running < limit && items.length > 0) { var item = items.shift(); async(item, function(result) { results.push(result); running--; if(items.length > 0) { launcher(); } else if(running == 0) { final(); } }); running++; } } launcher(); ``` 上面代碼中,最多只能同時運行兩個異步任務。變量running記錄當前正在運行的任務數,只要低于門檻值,就再啟動一個新的任務,如果等于0,就表示所有任務都執行完了,這時就執行final函數。 ## Promise對象 ### 簡介 Promise對象是CommonJS工作組提出的一種規范,目的是為異步操作提供[統一接口](http://wiki.commonjs.org/wiki/Promises/A)。 那么,什么是Promises? 首先,它是一個對象,也就是說與其他JavaScript對象的用法,沒有什么兩樣;其次,它起到代理作用(proxy),充當異步操作與回調函數之間的中介。它使得異步操作具備同步操作的接口,使得程序具備正常的同步運行的流程,回調函數不必再一層層嵌套。 簡單說,它的思想是,每一個異步任務立刻返回一個Promise對象,由于是立刻返回,所以可以采用同步操作的流程。這個Promises對象有一個then方法,允許指定回調函數,在異步任務完成后調用。 比如,異步操作`f1`返回一個Promise對象,它的回調函數`f2`寫法如下。 ```javascript (new Promise(f1)).then(f2); ``` 這種寫法對于多層嵌套的回調函數尤其方便。 ```javascript // 傳統寫法 step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // ... }); }); }); }); // Promises的寫法 (new Promise(step1)) .then(step2) .then(step3) .then(step4); ``` 從上面代碼可以看到,采用Promises接口以后,程序流程變得非常清楚,十分易讀。 注意,為了便于理解,上面代碼的Promise對象的生成格式,做了簡化,真正的語法請參照下文。 總的來說,傳統的回調函數寫法使得代碼混成一團,變得橫向發展而不是向下發展。Promises規范就是為了解決這個問題而提出的,目標是使用正常的程序流程(同步),來處理異步操作。它先返回一個Promise對象,后面的操作以同步的方式,寄存在這個對象上面。等到異步操作有了結果,再執行前期寄放在它上面的其他操作。 Promises原本只是社區提出的一個構想,一些外部函數庫率先實現了這個功能。ECMAScript 6將其寫入語言標準,因此目前JavaScript語言原生支持Promise對象。 ### Promise接口 前面說過,Promise接口的基本思想是,異步任務返回一個Promise對象。 Promise對象只有三種狀態。 - 異步操作“未完成”(pending) - 異步操作“已完成”(resolved,又稱fulfilled) - 異步操作“失敗”(rejected) 這三種的狀態的變化途徑只有兩種。 - 異步操作從“未完成”到“已完成” - 異步操作從“未完成”到“失敗”。 這種變化只能發生一次,一旦當前狀態變為“已完成”或“失敗”,就意味著不會再有新的狀態變化了。因此,Promise對象的最終結果只有兩種。 - 異步操作成功,Promise對象傳回一個值,狀態變為`resolved`。 - 異步操作失敗,Promise對象拋出一個錯誤,狀態變為`rejected`。 Promise對象使用`then`方法添加回調函數。`then`方法可以接受兩個回調函數,第一個是異步操作成功時(變為`resolved`狀態)時的回調函數,第二個是異步操作失敗(變為`rejected`)時的回調函數(可以省略)。一旦狀態改變,就調用相應的回調函數。 ```javascript // po是一個Promise對象 po.then( console.log, console.error ); ``` 上面代碼中,Promise對象`po`使用`then`方法綁定兩個回調函數:操作成功時的回調函數`console.log`,操作失敗時的回調函數`console.error`(可以省略)。這兩個函數都接受異步操作傳回的值作為參數。 `then`方法可以鏈式使用。 ```javascript po .then(step1) .then(step2) .then(step3) .then( console.log, console.error ); ``` 上面代碼中,`po`的狀態一旦變為`resolved`,就依次調用后面每一個`then`指定的回調函數,每一步都必須等到前一步完成,才會執行。最后一個`then`方法的回調函數`console.log`和`console.error`,用法上有一點重要的區別。`console.log`只顯示回調函數`step3`的返回值,而`console.error`可以顯示`step1`、`step2`、`step3`之中任意一個發生的錯誤。也就是說,假定`step1`操作失敗,拋出一個錯誤,這時`step2`和`step3`都不會再執行了(因為它們是操作成功的回調函數,而不是操作失敗的回調函數)。Promises對象開始尋找,接下來第一個操作失敗時的回調函數,在上面代碼中是`console.error`。這就是說,Promises對象的錯誤有傳遞性。 從同步的角度看,上面的代碼大致等同于下面的形式。 ```javascript try { var v1 = step1(po); var v2 = step2(v1); var v3 = step3(v2); console.log(v3); } catch (error) { console.error(error); } ``` ### Promise對象的生成 ES6提供了原生的Promise構造函數,用來生成Promise實例。 下面代碼創造了一個Promise實例。 ```javascript var promise = new Promise(function(resolve, reject) { // 異步操作的代碼 if (/* 異步操作成功 */){ resolve(value); } else { reject(error); } }); ``` Promise構造函數接受一個函數作為參數,該函數的兩個參數分別是`resolve`和`reject`。它們是兩個函數,由JavaScript引擎提供,不用自己部署。 `resolve`函數的作用是,將Promise對象的狀態從“未完成”變為“成功”(即從`Pending`變為`Resolved`),在異步操作成功時調用,并將異步操作的結果,作為參數傳遞出去;`reject`函數的作用是,將Promise對象的狀態從“未完成”變為“失敗”(即從`Pending`變為`Rejected`),在異步操作失敗時調用,并將異步操作報出的錯誤,作為參數傳遞出去。 Promise實例生成以后,可以用`then`方法分別指定`Resolved`狀態和`Reject`狀態的回調函數。 ```javascript po.then(function(value) { // success }, function(value) { // failure }); ``` ### 用法辨析 Promise的用法,簡單說就是一句話:使用`then`方法添加回調函數。但是,不同的寫法有一些細微的差別,請看下面四種寫法,它們的差別在哪里? ```javascript // 寫法一 doSomething().then(function () { return doSomethingElse(); }); // 寫法二 doSomething().then(function () { doSomethingElse(); }); // 寫法三 doSomething().then(doSomethingElse()); // 寫法四 doSomething().then(doSomethingElse); ``` 為了便于講解,這四種寫法都再用`then`方法接一個回調函數`finalHandler`。寫法一的`finalHandler`回調函數的參數,是`doSomethingElse`函數的運行結果。 ```javascript doSomething().then(function () { return doSomethingElse(); }).then(finalHandler); ``` 寫法二的`finalHandler`回調函數的參數是`undefined`。 ```javascript doSomething().then(function () { doSomethingElse(); return; }).then(finalHandler); ``` 寫法三的`finalHandler`回調函數的參數,是`doSomethingElse`函數返回的回調函數的運行結果。 ```javascript doSomething().then(doSomethingElse()) .then(finalHandler); ``` 寫法四與寫法一只有一個差別,那就是`doSomethingElse`會接收到`doSomething()`返回的結果。 ```javascript doSomething().then(doSomethingElse) .then(finalHandler); ``` ## Promise的應用 ### 加載圖片 我們可以把圖片的加載寫成一個`Promise`對象。 ```javascript var preloadImage = function (path) { return new Promise(function (resolve, reject) { var image = new Image(); image.onload = resolve; image.onerror = reject; image.src = path; }); }; ``` ### Ajax操作 Ajax操作是典型的異步操作,傳統上往往寫成下面這樣。 ```javascript function search(term, onload, onerror) { var xhr, results, url; url = 'http://example.com/search?q=' + term; xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function (e) { if (this.status === 200) { results = JSON.parse(this.responseText); onload(results); } }; xhr.onerror = function (e) { onerror(e); }; xhr.send(); } search("Hello World", console.log, console.error); ``` 如果使用Promise對象,就可以寫成下面這樣。 ```javascript function search(term) { var url = 'http://example.com/search?q=' + term; var xhr = new XMLHttpRequest(); var result; var p = new Promise(function (resolve, reject) { xhr.open('GET', url, true); xhr.onload = function (e) { if (this.status === 200) { result = JSON.parse(this.responseText); resolve(result); } }; xhr.onerror = function (e) { reject(e); }; xhr.send(); }); return p; } search("Hello World").then(console.log, console.error); ``` 加載圖片的例子,也可以用Ajax操作完成。 ```javascript function imgLoad(url) { return new Promise(function(resolve, reject) { var request = new XMLHttpRequest(); request.open('GET', url); request.responseType = 'blob'; request.onload = function() { if (request.status === 200) { resolve(request.response); } else { reject(new Error('圖片加載失敗:' + request.statusText)); } }; request.onerror = function() { reject(new Error('發生網絡錯誤')); }; request.send(); }); } ``` ### 小結 Promise對象的優點在于,讓回調函數變成了規范的鏈式寫法,程序流程可以看得很清楚。它的一整套接口,可以實現許多強大的功能,比如為多個異步操作部署一個回調函數、為多個回調函數中拋出的錯誤統一指定處理方法等等。 而且,它還有一個前面三種方法都沒有的好處:如果一個任務已經完成,再添加回調函數,該回調函數會立即執行。所以,你不用擔心是否錯過了某個事件或信號。這種方法的缺點就是,編寫和理解都相對比較難。 <h2 id="9.2">JavaScript與有限狀態機</h2> ## 概述 有限狀態機(Finite-state machine)是一個非常有用的模型,可以模擬世界上大部分事物。 簡單說,它有三個特征: - 狀態總數(state)是有限的。 - 任一時刻,只處在一種狀態之中。 - 某種條件下,會從一種狀態轉變(transition)到另一種狀態。 它對JavaScript的意義在于,很多對象可以寫成有限狀態機。 舉例來說,網頁上有一個菜單元素。鼠標點擊,菜單顯示;鼠標再次點擊,菜單隱藏。如果使用有限狀態機描述,就是這個菜單只有兩種狀態(顯示和隱藏),鼠標會引發狀態轉變。 代碼可以寫成下面這樣: ```javascript var menu = {      // 當前狀態   currentState: 'hide',      // 綁定事件   initialize: function() {     var self = this;     self.on("click", self.transition);   },      // 狀態轉換   transition: function(event){     switch(this.currentState) {       case "hide":         this.currentState = 'show';         doSomething();         break;       case "show":         this.currentState = 'hide';         doSomething();         break;       default:         console.log('Invalid State!');         break;     }   }    }; ``` 可以看到,有限狀態機的寫法,邏輯清晰,表達力強,有利于封裝事件。一個對象的狀態越多、發生的事件越多,就越適合采用有限狀態機的寫法。 另外,JavaScript語言是一種異步操作特別多的語言,常用的解決方法是指定回調函數,但這樣會造成代碼結構混亂、難以測試和除錯等問題。有限狀態機提供了更好的辦法:把異步操作與對象的狀態改變掛鉤,當異步操作結束的時候,發生相應的狀態改變,由此再觸發其他操作。這要比回調函數、事件監聽、發布/訂閱等解決方案,在邏輯上更合理,更易于降低代碼的復雜度。 ## Javascript Finite State Machine函數庫 下面介紹一個有限狀態機的函數庫[Javascript Finite State Machine](https://github.com/jakesgordon/javascript-state-machine)。這個庫非常好懂,可以幫助我們加深理解,而且功能一點都不弱。 該庫提供一個全局對象StateMachine,使用該對象的create方法,可以生成有限狀態機的實例。 ```javascript var fsm = StateMachine.create(); ``` 生成的時候,需要提供一個參數對象,用來描述實例的性質。比如,交通信號燈(紅綠燈)可以這樣描述: ```javascript var fsm = StateMachine.create({      initial: 'green',      events: [     { name: 'warn', from: 'green', to: 'yellow' },     { name: 'stop', from: 'yellow', to: 'red' },     { name: 'ready', from: 'red', to: 'yellow' },     { name: 'go', from: 'yellow', to: 'green' }   ]    }); ``` 交通信號燈的初始狀態(initial)為green,events屬性是觸發狀態改變的各種事件,比如warn事件使得green狀態變成yellow狀態,stop事件使得yellow狀態變成red狀態等等。 生成實例以后,就可以隨時查詢當前狀態。 - fsm.current :返回當前狀態。 - fsm.is(s) :返回一個布爾值,表示狀態s是否為當前狀態。 - fsm.can(e) :返回一個布爾值,表示事件e是否能在當前狀態觸發。 - fsm.cannot(e) :返回一個布爾值,表示事件e是否不能在當前狀態觸發。 Javascript Finite State Machine允許為每個事件指定兩個回調函數,以warn事件為例: - onbefore**warn**:在warn事件發生之前觸發。 - onafter**warn**(可簡寫成onwarn) :在warn事件發生之后觸發。 同時,它也允許為每個狀態指定兩個回調函數,以green狀態為例: - onleave**green** :在離開green狀態時觸發。 - onenter**green**(可簡寫成ongreen) :在進入green狀態時觸發。 假定warn事件使得狀態從green變為yellow,上面四類回調函數的發生順序如下:onbefore**warn** → onleave**green** → onenter**yellow** → onafter**warn**。 除了為每個事件和狀態單獨指定回調函數,還可以為所有的事件和狀態指定通用的回調函數。 - onbeforeevent :任一事件發生之前觸發。 - onleavestate :離開任一狀態時觸發。 - onenterstate :進入任一狀態時觸發。 - onafterevent :任一事件結束后觸發。 如果事件的回調函數里面有異步操作(比如與服務器進行Ajax通信),這時我們可能希望等到異步操作結束,再發生狀態改變。這就要用到transition方法。 ```javascript fsm.onleavegreen = function(){   light.fadeOut('slow', function() {     fsm.transition();   });   return StateMachine.ASYNC; }; ``` 上面代碼的回調函數里面,有一個異步操作(light.fadeOut)。如果不希望狀態立即改變,就要讓回調函數返回StateMachine.ASYNC,表示狀態暫時不改變;等到異步操作結束,再調用transition方法,使得狀態發生改變。 Javascript Finite State Machine還允許指定錯誤處理函數,當發生了當前狀態不可能發生的事件時自動觸發。 ```javascript var fsm = StateMachine.create({   // ...   error: function(eventName, from, to, args, errorCode, errorMessage) {     return 'event ' + eventName + ': ' + errorMessage;   },   // ... }); ``` 比如,當前狀態是green,理論上這時只可能發生warn事件。要是這時發生了stop事件,就會觸發上面的錯誤處理函數。 Javascript Finite State Machine的基本用法就是上面這些,更詳細的介紹可以參見它的[主頁](https://github.com/jakesgordon/javascript-state-machine)。 <h2 id="9.3">MVC框架與Backbone.js</h2> ## MVC框架 隨著JavaScript程序變得越來越復雜,往往需要一個團隊協作開發,這時代碼的模塊化和組織規范就變得異常重要了。MVC模式就是代碼組織的經典模式。 (……MVC介紹。) **(1)Model** Model表示數據層,也就是程序需要的數據源,通常使用JSON格式表示。 **(2)View** View表示表現層,也就是用戶界面,對于網頁來說,就是用戶看到的網頁HTML代碼。 **(3)Controller** Controller表示控制層,用來對原始數據(Model)進行加工,傳送到View。 由于網頁編程不同于客戶端編程,在MVC的基礎上,JavaScript社區產生了各種變體框架MVP(Model-View-Presenter)、MVVM(Model-View-ViewModel)等等,有人就把所有這一類框架的各種模式統稱為MV*。 框架的優點在于合理組織代碼、便于團隊合作和未來的維護,缺點在于有一定的學習成本,且限制你只能采取它的寫法。 ## 零框架解決方案 MVC框架(尤其是大型框架)有一個嚴重的缺點,就是會產生用戶的重度依賴。一旦框架本身出現問題或者停止更新,用戶的處境就會很困難,維護和更新成本極高。 ES6的到來,使得JavaScript語言有了原生的模塊解決方案。于是,開發者有了另一種選擇,就是不使用MVC框架,只使用各種單一用途的模塊庫,組合完成一個項目。下面是可供選擇的各種用途的模塊列表。 輔助功能庫(Helper Libraries) - [moment.js](http://momentjs.com/):日期和時間的標準化 - [underscore.js](http://underscorejs.org/) / [Lo-Dash](https://lodash.com/):一系列函數式編程的功能函數 路由庫(Routing) - [router.js](https://github.com/tildeio/router.js/):Ember.js使用的路由庫 - [route-recognizer](https://github.com/tildeio/route-recognizer):功能全面的路由庫 - [page.js](https://github.com/visionmedia/page.js):類似Express路由的庫 - [director](https://github.com/flatiron/director):同時支持服務器和瀏覽器的路由庫 Promise庫 - [RSVP.js](https://github.com/tildeio/rsvp.js):ES6兼容的Promise庫 - [ES6-Promise](https://github.com/jakearchibald/es6-promise):RSVP.js的子集,但是全面兼容ES6 - [q](https://github.com/kriskowal/q):最常用的Promise庫之一,AngularJS用了它的精簡版 - [native-promise-only](https://github.com/getify/native-promise-only):嚴格符合ES6的Promise標準,同時兼容老式瀏覽器 客戶端與服務器的通信庫 - [fetch](https://github.com/github/fetch):實現window.fetch功能 - [qwest](https://github.com/pyrsmk/qwest):支持XHR2和Promise的Ajax庫 - [jQuery](https://github.com/jquery/jquery):jQuery 2.0支持按模塊打包,因此可以創建一個純Ajax功能庫 動畫庫(Animation) - [cssanimevent](https://github.com/magnetikonline/cssanimevent):兼容老式瀏覽器的CSS3動畫庫 - [Velocity.js](http://julian.com/research/velocity/):性能優秀的動畫庫 輔助開發庫(Development Assistance) - [LogJS](https://github.com/bfattori/LogJS):輕量級的logging功能庫 - [UserTiming.js](https://github.com/nicjansma/usertiming.js):支持老式瀏覽器的高精度時間戳庫 流程控制和架構(Flow Control/Architecture) - [ondomready](https://github.com/tubalmartin/ondomready):類似jQuery的ready()方法,符合AMD規范 - [script.js](https://github.com/ded/script.js]):異步的腳本加載和依賴關系管理庫 - [async](https://github.com/caolan/async):瀏覽器和node.js的異步管理工具庫 - [Virtual DOM](https://github.com/Matt-Esch/virtual-dom):react.js的一個替代方案,參見[Virtual DOM and diffing algorithm](https://gist.github.com/Raynos/8414846) 數據綁定(Data-binding) - Object.observe():Chrome已經支持該方法,可以輕易實現雙向數據綁定 模板庫(Templating) - [Mustache](http://mustache.github.io/):大概是目前使用最廣的不含邏輯的模板系統 微框架(Micro-Framework) 某些情況下,可以使用微型框架,作為項目開發的起點。 - [bottlejs](https://github.com/young-steveo/bottlejs):提供惰性加載、中間件鉤子、裝飾器等功能 - [Stapes.js](http://hay.github.io/stapes/#top):微型MVC框架 - [soma.js](http://somajs.github.io/somajs/site/):提供一個松耦合、易測試的架構 - [knockout](http://knockoutjs.com/):最流行的微框架之一,主要關注UI ## Backbone的加載 ```html <script src="/javascripts/lib/jquery.js"></script> <script src="/javascripts/lib/underscore.js"></script> <script src="/javascripts/lib/backbone.js"></script> <script src="/javascripts/jst.js"></script> <script src="/javascripts/router.js"></script> <script src="/javascripts/init.js"></script> ``` ## Backbone的用法 Backbone是最早的JavaScript MVC框架,也是最簡化的一個框架。它的設計思想是,只提供最基本的功能,給用戶提供最大的自由。這意味著,好的一面是它沒有一整套規則,強制你接受,壞的一面是很多功能你必須自己實現。Backbone的體積相當小,最小化后只有30多KB。 定義一個對象,表示Web應用。 ```javascript var AppName = { Models :{}, Views :{}, Collections :{}, Controllers :{} }; ``` 上面代碼表示,應用由四部分組成:Model、Collection、Controller和View。 定義Model,表示數據的一個基本單位。 ```javascript AppName.Models.Person = Backbone.Model.extend({ urlRoot: "/persons" }); ``` 定義Collection,表示Model的集合。 ```javascript AppName.Collections.Library = Backbone.Collection.extend({ model: AppName.Models.Book }); ``` 上面代碼表示,Collection對象必須有model屬性,指明由哪一個model構成。 定義一個View。 ```javascript AppName.Views.Modals.AcceptDecline = Backbone.View.Extend({ el: ".modal-accept", events: { "ajax:success .link-accept" :"acceptSuccess", "ajax:error .link-accept" :"acceptError" }, acceptSuccess :function(evt, response) { this.$el.modal("hide"); alert('Cool! Thanks'); }, acceptError :function(evt, response) { var $modalContent = this.$el.find('.panel-modal'); $modalContent.append("Something was wrong!"); } }); ``` View對象必須有el屬性,指明當前View綁定的DOM節點,events屬性指明事件和對應的方法。 定義一個Controller。 ```javascript AppName.Controllers.Person = {}; AppName.Controllers.Person.show = function(id) { var aMa = new AppName.Models.Person({id: id}); aMa.updateAge(25); aMa.fetch().done(function(){ var view = new AppName.Views.Show({model: aMa}); }); }; ``` 最后,定義路由,啟動應用程序。 ```javascript var Workspace = Backbone.Router.extend({ routes: { "*" :"wholeApp", "users/:id" :"usersShow", "users/:id/orders/" :"ordersIndex" }, wholeApp :AppName.Controller.Application.default, usersShow :AppName.Controller.Users.show, ordersIndex :AppName.Controller.Orders.index }); new Workspace(); Backbone.history.start({pushState: true}); ``` ## Backbone.View ### 基本用法 Backbone.View方法用于定義視圖類。 ```javascrip var AppView = Backbone.View.extend({ render: function(){ $('main').append('<h1>一級標題</h1>'); } }); ``` 上面代碼通過Backbone.View的extend方法,定義了一個視圖類AppView。該類內部有一個render方法,用于將視圖放置在網頁上。 使用的時候,需要先新建視圖類的實例,然后通過實例,調用render方法,從而讓視圖在網頁上顯示。 ```javascript var appView = new AppView(); appView.render(); ``` 上面代碼新建視圖類AppView的實例appView,然后調用appView.render,網頁上就會顯示指定的內容。 新建視圖實例時,通常需要指定Model。 ```javascript var document = new Document({ model: doc }); ``` ### initialize方法 視圖還可以定義initialize方法,生成實例的時候,會自動調用該方法對實例初始化。 ```javascript var AppView = Backbone.View.extend({ initialize: function(){ this.render(); }, render: function(){ $('main').append('<h1>一級標題</h1>'); } }); var appView = new AppView(); ``` 上面代碼定義了initialize方法之后,就省去了生成實例后,手動調用appView.render()的步驟。 ### el屬性,$el屬性 除了直接在render方法中,指定“視圖”所綁定的網頁元素,還可以用視圖的el屬性指定網頁元素。 ```javascript var AppView = Backbone.View.extend({ el: $('main'), render: function(){ this.$el.append('<h1>一級標題</h1>'); } }); ``` 上面的代碼與render方法直接綁定網頁元素,效果完全一樣。上面代碼中,除了el屬性,還是$el屬性,前者代表指定的DOM元素,后者則表示該DOM元素對應的jQuery對象。 ### tagName屬性,className屬性 如果不指定el屬性,也可以通過tagName屬性和className屬性指定。 ```javascript var Document = Backbone.View.extend({ tagName: "li", className: "document", render: function() { // ... } }); ``` ### template方法 視圖的template屬性用來指定網頁模板。 ```javascript var AppView = Backbone.View.extend({ template: _.template("<h3>Hello <%= who %><h3>"), }); ``` 上面代碼中,underscore函數庫的template函數,接受一個模板字符串作為參數,返回對應的模板函數。有了這個模板函數,只要提供具體的值,就能生成網頁代碼。 ```javascript var AppView = Backbone.View.extend({ el: $('#container'), template: _.template("<h3>Hello <%= who %><h3>"), initialize: function(){ this.render(); }, render: function(){ this.$el.html(this.template({who: 'world!'})); } }); ``` 上面代碼的render就調用了template方法,從而生成具體的網頁代碼。 實際應用中,一般將模板放在script標簽中,為了防止瀏覽器按照JavaScript代碼解析,type屬性設為text/template。 ```html <script type="text/template" data-name="templateName"> <!-- template contents goes here --> </script> ``` 可以使用下面的代碼編譯模板。 ```javascript window.templates = {}; var $sources = $('script[type="text/template"]'); $sources.each(function(index, el) { var $el = $(el); templates[$el.data('name')] = _.template($el.html()); }); ``` ### events屬性 events屬性用于指定視圖的事件及其對應的處理函數。 ```javascript var Document = Backbone.View.extend({ events: { "click .icon": "open", "click .button.edit": "openEditDialog", "click .button.delete": "destroy" } }); ``` 上面代碼中一個指定了三個CSS選擇器的單擊事件,及其對應的三個處理函數。 ### listento方法 listento方法用于為特定事件指定回調函數。 ```javascript var Document = Backbone.View.extend({ initialize: function() { this.listenTo(this.model, "change", this.render); } }); ``` 上面代碼為model的change事件,指定了回調函數為render。 ### remove方法 remove方法用于移除一個視圖。 ```javascript updateView: function() { view.remove(); view.render(); }; ``` ### 子視圖(subview) 在父視圖中可以調用子視圖。下面就是一種寫法。 ```javascript render : function (){ this.$el.html(this.template()); this.child = new Child(); this.child.appendTo($.('.container-placeholder').render(); } ``` ## Backbone.Events `Backbone.Events`是一個事件對象。任何繼承了這個對象的對象,都具備了`Backbone.Events`的事件接口,可以調用on和trigger方法,發布和訂閱消息。 ```javascript var EventChannel = _.extend({}, Backbone.Events); ``` 下面是一些例子。 ```javascript var channel = $.extend( {}, Backbone.Events ); channel.on('remove-node', function(msg) { // code to remove the node }); channel.trigger( 'remove-node', msg ); // 'msg' can be everything: String, number, object and so forth // also we can pass more than one message like the example below channel.on('add-node', function(node, callback) { // code to add a new node callback(); } ); channel.trigger('add-node', { label: 'I am a new node', color: 'black' }, function() { console.log( 'I am a callback' ); }); ``` ## Backbone.Router Router是Backbone提供的路由對象,用來將用戶請求的網址與后端的處理函數一一對應。 首先,新定義一個Router類。 ```javascript Router = Backbone.Router.extend({ routes: { } }); ``` ## routes屬性 Backbone.Router對象中,最重要的就是routes屬性。它用來設置路徑的處理方法。 routes屬性是一個對象,它的每個成員就代表一個路徑處理規則,鍵名為路徑規則,鍵值為處理方法。 如果鍵名為空字符串,就代表根路徑。 ```javascript routes: { '': 'phonesIndex', }, phonesIndex: function () { new PhonesIndexView({ el: 'section#main' }); } ``` 星號代表任意路徑,可以設置路徑參數,捕獲具體的路徑值。 ```javascript var AppRouter = Backbone.Router.extend({ routes: { "*actions": "defaultRoute" } }); var app_router = new AppRouter; app_router.on('route:defaultRoute', function(actions) { console.log(actions); }) ``` 上面代碼中,根路徑后面的參數,都會被捕獲,傳入回調函數。 路徑規則的寫法。 ```javascript var myrouter = Backbone.Router.extend({ routes: { "help": "help", "search/:query": "search" }, help: function() { ... }, search: function(query) { ... } }); routes: { "help/:page": "help", "download/*path": "download", "folder/:name": "openFolder", "folder/:name-:mode": "openFolder" } router.on("route:help", function(page) { ... }); ``` ## Backbone.history 設置了router以后,就可以啟動應用程序。Backbone.history對象用來監控url的變化。 ```javascript App = new Router(); $(document).ready(function () { Backbone.history.start({ pushState: true }); }); ``` 打開pushState方法。如果應用程序不在根目錄,就需要指定根目錄。 ```javascript Backbone.history.start({pushState: true, root: "/public/search/"}) ``` ## Backbone.Model Model代表單個的對象實體。 ```javascript var User = Backbone.Model.extend({ defaults: { name: '', email: '' } }); var user = new User(); ``` 上面代碼使用extend方法,生成了一個User類,它代表model的模板。然后,使用new命令,生成一個Model的實例。defaults屬性用來設置默認屬性,上面代碼表示user對象默認有name和email兩個屬性,它們的值都等于空字符串。 生成實例時,可以提供各個屬性的具體值。 ```javascript var user = new User ({ id: 1, name: 'name', email: 'name@email.com' }); ``` 上面代碼在生成實例時,提供了各個屬性的具體值。 ### idAttribute屬性 Model實例必須有一個屬性,作為區分其他實例的主鍵。這個屬性的名稱,由idAttribute屬性設定,一般是設為id。 ```javascript var Music = Backbone.Model.extend({ idAttribute: 'id' }); ``` ### get方法 get方法用于返回Model實例的某個屬性的值。 ```javascript var user = new User({ name: "name", age: 24}); var age = user.get("age"); // 24 var name = user.get("name"); // "name" ``` ### set方法 set方法用于設置Model實例的某個屬性的值。 ```javascript var User = Backbone.Model.extend({ buy: function(newCarsName){ this.set({car: newCarsName }); } }); var user = new User({name: 'BMW',model:'i8',type:'car'}); user.buy('Porsche'); var car = user.get("car"); // ‘Porsche’ ``` ### on方法 on方法用于監聽對象的變化。 ```javascript var user = new User({name: 'BMW',model:'i8'}); user.on("change:name", function(model){ var name = model.get("name"); // "Porsche" console.log("Changed my car’s name to " + name); }); user.set({name: 'Porsche'}); // Changed my car’s name to Porsche ``` 上面代碼中的on方法用于監聽事件,“change:name”表示name屬性發生變化。 ### urlroot屬性 該屬性用于指定服務器端對model進行操作的路徑。 ```javascript var User = Backbone.Model.extend({ urlRoot: '/user' }); ``` 上面代碼指定,服務器對應該Model的路徑為/user。 ### fetch事件 fetch事件用于從服務器取出Model。 ```javascript var user = new User ({id: 1}); user.fetch({ success: function (user){ console.log(user.toJSON()); } }) ``` 上面代碼中,user實例含有id屬性(值為1),fetch方法使用HTTP動詞GET,向網址“/user/1”發出請求,從服務器取出該實例。 ### save方法 save方法用于通知服務器新建或更新Model。 如果一個Model實例不含有id屬性,則save方法將使用POST方法新建該實例。 ```javascript var User = Backbone.Model.extend({ urlRoot: '/user' }); var user = new User (); var userDetails = { name: 'name', email: 'name@email.com' }; user.save(userDetails, { success: function (user) { console.log(user.toJSON()); } }) ``` 上面代碼先在類中指定Model對應的網址是/user,然后新建一個實例,最后調用save方法。它有兩個參數,第一個是實例對象的具體屬性,第二個參數是一個回調函數對象,設定success事件(保存成功)的回調函數。具體來說,save方法會向/user發出一個POST請求,并將{name: 'name', email: 'name@email.com'}作為數據提供。 如果一個Model實例含有id屬性,則save方法將使用PUT方法更新該實例。 ```javascript var user = new User ({ id: 1, name: '張三', email: 'name@email.com' }); user.save({name: '李四'}, { success: function (model) { console.log(user.toJSON()); } }); ``` 上面代碼中,對象實例含有id屬性(值為1),save將使用PUT方法向網址“/user/1”發出請求,從而更新該實例。 ### destroy方法 destroy方法用于在服務器上刪除該實例。 ```javascript var user = new User ({ id: 1, name: 'name', email: 'name@email.com' }); user.destroy({ success: function () { console.log('Destroyed'); } }); ``` 上面代碼的destroy方法,將使用HTTP動詞DELETE,向網址“/user/1”發出請求,刪除對應的Model實例。 ## Backbone.Collection Collection是同一類Model的集合,比如Model是動物,Collection就是動物園;Model是單個的人,Collection就是一家公司。 ```javascript var Song = Backbone.Model.extend({}); var Album = Backbone.Collection.extend({ model: Song }); ``` 上面代碼中,Song是Model,Album是Collection,而且Album有一個model屬性等于Song,因此表明Album是Song的集合。 ### add方法,remove方法 Model的實例可以直接放入Collection的實例,也可以用add方法添加。 ```javascript var song1 = new Song({ id: 1 ,name: "歌名1", artist: "張三" }); var song2 = new Music ({id: 2,name: "歌名2", artist: "李四" }); var myAlbum = new Album([song1, song2]); var song3 = new Music({ id: 3, name: "歌名3",artist:"趙五" }); myAlbum.add(song3); ``` remove方法用于從Collection實例中移除一個Model實例。 ```javascript myAlbum.remove(1); ``` 上面代碼表明,remove方法的參數是model實例的id屬性。 ### get方法,set方法 get方法用于從Collection中獲取指定id的Model實例。 ```javascript myAlbum.get(2)) ``` ### fetch方法 fetch方法用于從服務器取出Collection數據。 ```javascript var songs = new Backbone.Collection; songs.url = '/songs'; songs.fetch(); ``` ## Backbone.events ```javascript var obj = {}; _.extend(obj, Backbone.Events); obj.on("show-message", function(msg) { $('#display').text(msg); }); obj.trigger("show-message", "Hello World"); ``` <h2 id="9.4">嚴格模式</h2> ## 概述 ### 設計目的 除了正常運行模式,ECMAScript 5添加了第二種運行模式:“嚴格模式”(strict mode)。顧名思義,這種模式使得JavaScript在更嚴格的條件下運行。 設立”嚴格模式“的目的,主要有以下幾個: - 消除JavaScript語法的一些不合理、不嚴謹之處,減少一些怪異行為; - 增加更多報錯的場合,消除代碼運行的一些不安全之處,保證代碼運行的安全; - 提高編譯器效率,增加運行速度; - 為未來新版本的JavaScript做好鋪墊。 “嚴格模式”體現了JavaScript更合理、更安全、更嚴謹的發展方向。 同樣的代碼,在”正常模式“和”嚴格模式“中,可能會有不一樣的運行結果。一些在"正常模式"下可以運行的語句,在"嚴格模式"下將不能運行。掌握這些內容,有助于更細致深入地理解JavaScript,讓你變成一個更好的程序員。 ### 啟用方法 進入“嚴格模式”的標志,是一行字符串`use strict`。 ```javascript 'use strict'; ``` 老版本的瀏覽器會把它當作一行普通字符串,加以忽略。新版本的瀏覽器就會進入嚴格模式。 “嚴格模式”可以用于整個腳本,也可以只用于單個函數。 **(1) 針對整個腳本文件** 將`use strict`放在腳本文件的第一行,則整個腳本都將以“嚴格模式”運行。如果這行語句不在第一行就無效,整個腳本會以“正常模式”運行。(嚴格地說,只要前面不是產生實際運行結果的語句,`use strict`可以不在第一行,比如直接跟在一個空的分號后面,或者跟在注釋后面。) ```html <script> 'use strict'; console.log('這是嚴格模式'); </script> <script> console.log('這是正常模式'); </script> ``` 上面的代碼表示,一個網頁文件中依次有兩段JavaScript代碼。前一個`<script>`標簽是嚴格模式,后一個不是。 如果字符串`use strict`出現在代碼中間,則不起作用,即嚴格模式必須從代碼一開始就生效。 兩個不同模式的腳本合并成一個文件,如果嚴格模式的腳本在前,則合并后的腳本都是”嚴格模式“;如果正常模式的腳本在前,則合并后的腳本都是”正常模式“。總之,這兩種情況下,合并后的結果都是不正確的。因此,建議在多個腳本需要合并的場合,”嚴格模式“只在函數中打開,不針對整個腳本打開。 **(2)針對單個函數** 將“use strict”放在函數體的第一行,則整個函數以“嚴格模式”運行。 ```javascript function strict() { 'use strict'; return '這是嚴格模式'; } function notStrict() { return '這是正常模式'; } ``` **(3)腳本文件的變通寫法** 因為在腳本文件第一行放置`use strict`不利于文件合并,所以更好的做法是,借用第二種方法,將整個腳本文件放在一個立即執行的匿名函數之中。 ```javascript (function () { "use strict"; // some code here })(); ``` ## 顯式報錯 嚴格模式使得JavaScript的語法變得更嚴格,更多的操作會顯式報錯。其中有些操作,在正常模式下只會默默地失敗,不會報錯。 ### 字符串的length屬性不可寫 嚴格模式下,設置字符串的`length`屬性,會報錯。 ```javascript 'use strict'; 'abc'.length = 5; ``` 實際上,嚴格模式下,對只讀屬性賦值,或者刪除不可配置(nonconfigurable)屬性都會報錯。 ### eval、arguments不可用作函數名 使用`eval`,或者在函數內部使用`arguments`,作為標識名,將會報錯。 下面的語句都會報錯。 ```javascript 'use strict'; eval = 17; arguments++; ++eval; var obj = { set p(arguments) { } }; var eval; try { } catch (arguments) { } function x(eval) { } function arguments() { } var y = function eval() { }; var f = new Function("arguments", "'use strict'; return 17;"); ``` ### 只讀屬性不可寫 正常模式下,對一個對象的只讀屬性進行賦值,不會報錯,只會默默地失敗。嚴格模式下,將報錯。 ```javascript 'use strict'; var o = {}; Object.defineProperty(o, 'v', { value: 1, writable: false }); o.v = 2; // 報錯 ``` ### 只設置了賦值器的屬性不可寫 嚴格模式下,對一個只設置了賦值器(getter)的屬性賦值,會報錯。 ```javascript "use strict"; var o = { get v() { return 1; } }; o.v = 2; // 報錯 ``` ### 禁止擴展的對象不可擴展 嚴格模式下,對禁止擴展的對象添加新屬性,會報錯。 ```javascript 'use strict'; var o = {}; Object.preventExtensions(o); o.v = 1; // 報錯 ``` ### 禁止刪除不可刪除的屬性 嚴格模式下,刪除一個不可刪除的屬性,會報錯。 ```javascript 'use strict'; delete Object.prototype; // 報錯 ``` ### 函數不能有重名的參數 正常模式下,如果函數有多個重名的參數,可以用`arguments[i]`讀取。嚴格模式下,這屬于語法錯誤。 ```javascript function f(a, a, b) { // 語法錯誤 'use strict'; return a + b; } ``` ### 禁止八進制的前綴0表示法 正常模式下,整數的第一位如果是`0`,表示這是八進制數,比如`0100`等于十進制的64。嚴格模式禁止這種表示法,整數第一位為`0`,將報錯。 ```javascript "use strict"; var n = 0100; // SyntaxError ``` ## 增強的安全措施 嚴格模式增強了安全保護,從語法上防止了一些不小心會出現的錯誤。 ### 全局變量顯式聲明 在正常模式中,如果一個變量沒有聲明就賦值,默認是全局變量。嚴格模式禁止這種用法,全局變量必須顯式聲明。 ```javascript 'use strict'; v = 1; // 報錯,v未聲明 for (i = 0; i < 2; i++) { // 報錯,i未聲明 // ... } function f() { x = 123; } f() // 報錯,未聲明就創建一個全局變量 ``` 因此,嚴格模式下,變量都必須先用`var`命令聲明,然后再使用。 ### 禁止this關鍵字指向全局對象 正常模式下,函數內部的`this`可能會指向全局對象,嚴格模式禁止這種用法,避免無意間創造全局變量。 ```javascript // 正常模式 function f() { console.log(this === window); } f() // true // 嚴格模式 function f() { 'use strict'; console.log(this === undefined); } f() // true ``` 這種限制對于構造函數尤其有用。使用構造函數時,有時忘了加`new`,這時`this`不再指向全局對象,而是報錯。 ```javascript function f() { 'use strict'; this.a = 1; }; f();// 報錯,this未定義 ``` 嚴格模式下,函數直接調用時(不使用`new`調用),函數內部的`this`表示`undefined`,因此可以用`call`、`apply`和`bind`方法,將任意值綁定在`this`上面。 ```javascript 'use strict'; function fun() { return this; } fun() //undefined fun.call(2) // 2 fun.apply(null) // null fun.call(undefined) // undefined fun.bind(true)() // true ``` ### 禁止使用fn.callee、fn.caller 函數內部不得使用`fn.caller`、`fn.arguments`,否則會報錯。這意味著不能在函數內部得到調用棧了。 ```javascript function f1() { 'use strict'; f1.caller; // 報錯 f1.arguments; // 報錯 } f1(); ``` ### 禁止使用arguments.callee、arguments.caller `arguments.callee`和`arguments.caller`是兩個歷史遺留的變量,從來沒有標準化過,現在已經取消了。正常模式下調用它們沒有什么作用,但是不會報錯。嚴格模式明確規定,函數內部使用`arguments.callee`、`arguments.caller`將會報錯。 ```javascript 'use strict'; var f = function() { return arguments.callee; }; f(); // 報錯 ``` ### 禁止刪除變量 嚴格模式下無法刪除變量,如果使用`delete`命令刪除一個變量,會報錯。只有對象的屬性,且屬性的描述對象的`configurable`屬性設置為true,才能被`delete`命令刪除。 ```javascript "use strict"; var x; delete x; // 語法錯誤 var o = Object.create(null, { x: { value: 1, configurable: true } }); delete o.x; // 刪除成功 ``` ## 靜態綁定 JavaScript語言的一個特點,就是允許“動態綁定”,即某些屬性和方法到底屬于哪一個對象,不是在編譯時確定的,而是在運行時(runtime)確定的。 嚴格模式對動態綁定做了一些限制。某些情況下,只允許靜態綁定。也就是說,屬性和方法到底歸屬哪個對象,必須在編譯階段就確定。這樣做有利于編譯效率的提高,也使得代碼更容易閱讀,更少出現意外。 具體來說,涉及以下幾個方面。 ### 禁止使用with語句 嚴格模式下,使用`with`語句將報錯。因為`with`語句無法在編譯時就確定,某個屬性到底歸屬哪個對象,從而影響了編譯效果。 ```javascript 'use strict'; var v = 1; with (o) { // SyntaxError v = 2; } ``` ### 創設eval作用域 正常模式下,JavaScript語言有兩種變量作用域(scope):全局作用域和函數作用域。嚴格模式創設了第三種作用域:`eval`作用域。 正常模式下,`eval`語句的作用域,取決于它處于全局作用域,還是函數作用域。嚴格模式下,`eval`語句本身就是一個作用域,不再能夠在其所運行的作用域創設新的變量了,也就是說,`eval`所生成的變量只能用于`eval`內部。 ```javascript (function () { 'use strict'; var x = 2; console.log(eval('var x = 5; x')) // 5 console.log(x) // 2 })() ``` 注意,如果希望`eval`語句也使用嚴格模式,有兩種方式。 ```javascript // 方式一 function f1(str){ 'use strict'; return eval(str); } f1('undeclared_variable = 1'); // 報錯 // 方式二 function f2(str){ return eval(str); } f2('"use strict";undeclared_variable = 1') // 報錯 ``` 上面兩種寫法,`eval`內部使用的都是嚴格模式。 ### arguments不再追蹤參數的變化 變量`arguments`代表函數的參數。嚴格模式下,函數內部改變參數與`arguments`的聯系被切斷了,兩者不再存在聯動關系。 ```javascript function f(a) { a = 2; return [a, arguments[0]]; } f(1); // 正常模式為[2, 2] function f(a) { "use strict"; a = 2; return [a, arguments[0]]; } f(1); // 嚴格模式為[2, 1] ``` 上面代碼中,改變函數的參數,不會反應到`arguments`對象上來。 ## 向下一個版本的JavaScript過渡 JavaScript語言的下一個版本是ECMAScript 6,為了平穩過渡,嚴格模式引入了一些ES6語法。 ### 函數必須聲明在頂層 JavaScript的新版本ES6會引入“塊級作用域”。為了與新版本接軌,嚴格模式只允許在全局作用域或函數作用域的頂層聲明函數。也就是說,不允許在非函數的代碼塊內聲明函數。 ```javascript "use strict"; if (true) { function f1() { } // 語法錯誤 } for (var i = 0; i < 5; i++) { function f2() { } // 語法錯誤 } ``` 上面代碼在`if`代碼塊和`for`代碼塊中聲明了函數,在嚴格模式下都會報錯。 ### 保留字 為了向將來JavaScript的新版本過渡,嚴格模式新增了一些保留字:implements, interface, let, package, private, protected, public, static, yield。 使用這些詞作為變量名將會報錯。 ```javascript function package(protected) { // 語法錯誤 'use strict'; var implements; // 語法錯誤 } ``` 此外,ECMAscript第五版本身還規定了另一些保留字(`class`, `enum`, `export`, `extends`, `import`, `super`),以及各大瀏覽器自行增加的`const`保留字,也是不能作為變量名的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看