書單推薦javascript
# 《單頁Web應用:JavaScript從前端到後端》 http://download.csdn.net/detail/epubitbook/8720475
# 《MVC的JavaScript Web富應用開發》
http://download.csdn.net/detail/u012070181/7361155
SPA單頁Web富應用,顧名思義,就是隻有一張Web頁面的應用。瀏覽器一開始會加載必須的HTML、CSS 和 JavaScript,只有全部的操做都在這張頁面上完成,這一切都是由JavaScript來控制。所以,單頁Web應用必將包含大量的JavaScript代碼,複雜度可想而知,模塊化開發和框架設計的重要性不言而喻。css
隨着單頁Web應用的崛起,各類框架也不斷的涌現,如Vuejs、ReactJs、Angularjs、Backbone.js、Ember.Js等,還有RequireJS等模塊加載器。可是,本書沒有講解這些框架和模塊加載器,這也正是我喜歡書本的緣由之一。做者本身不多使用框架,而且框架的限制過多,一旦不符合框架自己的設計哲學,結果可能拔苗助長。———— 事實上全部的框架使用都是如此。
但不論是使用框架仍是按照書的方法開發,書中的思想都是適用的。html
第一個SPA應用 前端
<html>
<head>
<title></title>
<style type="text/css"> body{ width: 100%; height:100%; overflow:hidden; background: #777; } #spa { position: absolute; top: 8px; left: 8px; right: 8px; bottom: 8px; border-radius: 8px 8px 0 8px; background: #fff; } .spa-slider { position: absolute; bottom: 0; right: 2px; width: 300px; height: 16px; cursor: pointer; border-radius: 8px 0 0 0; background: #f00; } </style>
</head>
<body>
<div id="spa">
<div class="spa-slider"></div>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript">
var spa = (function ( $ ) { var $chatSlider,toggleSlider,onClickSlider,initModule; var configMap = { extended_height : 434, extended_title : 'Click to retract', retracted_height : 16, retracted_title : 'Click to extend', template_html : '<div class="spa-slider"></div>' }, toggleSlider = function () { var slider_height = $chatSlider.height(); if( slider_height === configMap.retracted_height) { $chatSlider.animate({height: configMap.extended_height}) .attr('title',configMap.extended_title); return true; } else if (slider_height === configMap.extended_height) { $chatSlider.animate({ height : configMap.retracted_height}) .attr('title',configMap.retracted_title); return true; } return false; }; onClickSlider = function ( event ) { toggleSlider(); return false; }; initModule = function ( $container ) { // render HTML
$container.html(configMap.template_html); $chatSlider = $container.find( '.spa-slider' ); $chatSlider.attr( 'title', configMap.retracted_title) .click( onClickSlider ); return true; }; return { initModule : initModule } }( jQuery )); $(function(){ console.log(spa); spa.initModule($('#spa')); }) </script>
</html>
變量做用域java
要麼全局,要麼局部,而javascript中惟一能定義變量做用域的語塊就只有函數。jquery
換個方式來看,函數就像監獄(prison),在函數中定義的變量就像是囚犯(prisoner).程序員
正如監獄限制囚犯不讓他們從監獄逃脫同樣,函數也限定了局部變量不讓他們逃脫到函數以外:編程
function prison () { var prisoner = 'i am local!' } prison(); console.log(prisoner); //prisoner is not defined
要是真這麼簡單就行了!!!這裏有一個Javascript 做用域的陷阱,能夠在函數中聲明全局變量。成功幫助囚犯(prisoner)越獄。那就是隻要忽略 var 關鍵字便可:後端
function prison () { prisoner = 'i am local!' // 沒有書寫 var 關鍵字 } prison(); console.log(prisoner); // i am local!
這種陷阱常常出如今for循環計數器中。因此請務必要避免這種錯誤:瀏覽器
# 錯誤的作法,沒有書寫var關鍵字 function prison () { for( i = 0 ; i < 10 ; i++) { // ...
} } prison(); console.log(i); // i is 10
# 正確的作法,養成 var 定義變量的方式 function prison () { for( var i = 0 ; i < 10 ; i++) { // ...
} } prison(); console.log(i); // i is not defined
變量提高
在JavaScript的函數中,當變量被聲明時,聲明會被提高到所在函數的頂部。而後被賦予undefined值。這就使得在函數的任意位置聲明變量都將存在於整個函數中。儘管它在賦值以前,它的值一直是undefined。
function prison () { console.log(prisoner); // undefined
var prisoner = "Lee"; console.log(prisoner); // Lee
}
全局做用域和變量提高的行爲結合時,須要注意仍然遵循變量提高的規則
var prisoner = "Mp"; function prison () { console.log(prisoner); // undefined
var prisoner = "Lee"; }
Javascript 對象 和 原型鏈
Javascript 對象是基於原型的,而當今其餘普遍使用的語言所有是基於類的對象。
- 在基於類的編程中,使用類來描述對象是什麼樣子的;
- 在基於原型的編程中,咱們須要先建立一個對象,而後告訴Javascript引擎,咱們想要更多想這樣的對象;
打個比方,若是建築是基於類的系統,則建築師會先畫出房子的藍圖,而後房子按照該藍圖建造。
若是i建築師基於原型的,建築師會先建造一所房子,而後將房子都建成像這種模樣的。
咱們使用先前囚犯的示例,對比一下兩種編程方式的不一樣。建立一名囚犯所要的條件有哪些,囚犯的屬性包括名字(name)、囚犯ID、性別(sex)和年齡(age)。
基於類的編程方式,以C#爲例
/* step 1 :定義類 */ public class Prisoner { public int sex = 0; public int age = 30; public string name; public string id; /* step 2 : 定義構造函數 */ public Prisoner ( string name, string id) { this.name = name; this.id = id; } } /* step 3 : 實例化對象 */ Prisoner firstPrisoner = new Prisoner( 'Joe', '12A' ); Prisoner secondPrisoner = new Prisoner( 'Sam', '2BC' );
基於原型的編程方式,以javascript爲例
/* step 1:定義原型 */
var info = { sex : 0, age : 18 }; /* step 2:定義對象的構造函數 */
var Prisoner = function ( name, id ) { this.name = name; this.id = id; } /* step 3:將構造函數關聯到原型 */ Prisoner.prototype = info; /* step 4 :實例化對象 */ Prisoner firstPrisoner = new Prisoner( 'Joe', '12A' ); Prisoner secondPrisoner = new Prisoner( 'Sam', '2BC' );
經過兩種不一樣的編程方式和step步驟的對比,咱們發現它們都遵循相似的順序,若是你習慣了類,則適應原型應該不難。但魔鬼隱藏在細節中,若是你沒有學習基於原型的相關知識,就以其餘語言的類編程理解方式一頭扎進Javascirpt,很容易被某些看起來簡單的東西絆倒。咱們經過上述兩個demo,來總結一下:
一、二者都首先建立了對象的模板
二、模板在基於類的編程中叫作類,在基於原型的編程中叫作原型對象
三、構造函數。在基於類的語言中,構造函數是在類的內部定義的。當實例化對象時,就十分的清晰明瞭。在Javascript中,對象的構造函數和原型對象是分開設置的。因此須要額外多一步來將它們鏈接在一塊兒。
四、實例化對象,javascript使用new操做符。這實際上違背了它基於原型的核心思想,多是試圖讓熟悉基於類繼承的開發人員更容易理解。不幸的是,大部分開發人員容易搞混淆了。
基於第4個問題,咱們的解決方案是使用 Object.create 做爲 new 操做符的代替,使用它來建立Javascript對象時,更接近原型開發的感受:
var info = { sex : 0, age : 18 }; Prisoner firstPrisoner = Object.create( info ); firstPrisoner.name = 'Joe'; firstPrisoner.id = '12A'; Prisoner secondPrisoner = Object.create( info ); secondPrisoner.name = 'Sam'; secondPrisoner.id = '2BC';
但這樣手動設置每一個對象的成員變量name和id是痛苦的,由於會有重複的代碼顯得不整潔。另外一種更優的方案是,使用object.create + 工廠模式,來建立並返回最終的對象:
var info = { sex : 0, age : 18 }; var FactoryPrisoner = function ( name, id ) { var prisoner = object.create( info ); prisoner.name = name; prisoner.id = id; return prisoner; } var firstPrisoner = FactoryPrisoner( 'Joe', '12A' ); var secondPrisoner = FactoryPrisoner( 'Sam', '2BC' );
函數 —— 更深刻的窺探
理解函數是理解Javascript的關鍵之一,是構建專業的單頁應用的重要基礎。
自執行匿名函數
在 javascript 的開發過程當中,咱們常常遇到一個問題:在全局做用域中定義的東西在每一個地方均可用。但有時候咱們不想和全部人共享這些內容,或由於這極可能覆蓋對方的內容。好比本身在開發第三方庫、建立javascript插件的時候,不但願使用者使用內部變量。
# demo 1 (function(){ var private_variable = "private"; })(); console.log(private_variable); // private_variable is not defined
# demo 2
var prison = (function(){ return "Lee is in prison"; })(); console.log(prison); // Lee is in prison
# demo 3 (function($){
console.log($); })(jQurey)
模塊模式
單頁應用很是龐大,不能定義在一個文件中。咱們應該將文件分爲一個個的模塊,每一個模塊都有它們本身的私有變量;
咱們仍然使用自執行匿名函數來控制變量的做用域,而且把代碼分爲多個文件:
var prison = (function(){ var prisoner_name = 'Lee', jail_term = '20 year term'; return { prisoner : prisoner_name, sentence : jail_term }; })() console.log(prison.prisoner_name); // undefined
console.log(prison.prisoner); // Lee
console.log(prison.sentence); // 20 year term
prison.prisoner_name = "Mp"; console.log(prison.prisoner); // Lee
這裏出現一個問題, prison.prisoner_name 沒有被更新,這裏有2個緣由:
一、它是在函數中使用了 var 關鍵詞建立的局部變量。沒法被外部訪問;
二、它不是對象或者原型上的屬性,因此它沒法訪問或者賦值;
也就是說,這些匿名函數中定義的屬性,是沒法經過外部直接調用或者賦值的。爲了能更新它們,咱們的作法是添加內部方法來訪問而且修改內部的變量
var prison = (function(){ var prisoner_name = 'Lee'; return { prisoner : prisoner_name, update_prisoner_name : function(name) { prisoner_name = name; }, out_prisoner_name : function(){ return prisoner_name; } }; })() console.log(prison.prisoner); // Lee
prison.update_prisoner_name("Mp"); console.log(prison.prisoner); // Lee
console.log(prison.out_prisoner_name()); // Mp
咱們注意到,咱們依然不能在該對象或者原型上直接訪問。咱們只能使用匿名函數返回的對象中的方法來訪問。這也是利用了【閉包】
什麼是閉包?
閉包是一種抽象的概念,理解起來可能有些困難,因此在回答什麼是閉包以前,先了解一些背景知識。
隨着程序的運行,它們會因各類事情而佔用計算機的內存,好比保存變量的值。若是程序運行了卻從不釋放再也不須要的內存,長此以往會致使電腦崩潰。
在一些語言中,如C,內存的管理都是由程序員處理的。而像Java 和 Javascript,實現了自動釋放內存的機制,當代碼再也不須要時,就自動從電腦的內存中移除它。這種自動化系統叫作垃圾回收器。關於內存是手動釋放好仍是自動釋放好這裏不討論,只要知道Javascript 有垃圾回收機制就足夠了。
那麼問題來了,javascript垃圾回收器是怎麼知道哪些東西【再也不須要的】呢?萬一把我想保留的變量移除了怎麼辦,有什麼方法可讓某些變量不被回收?
咱們先來回答第一個問題:javascript垃圾回收器比較一根筋!!
它想:既然函數已經執行完畢了,咱們應該就再也不須要訪問該函數環境中的東西了。
因此當函數執行完畢時,函數中全部建立的東西就會從內存中移除。
回答第二個問題:有的,這種方法被稱之爲閉包!
var menu, outer_function; outer_function = function () { var fruit = 'apple'; var food = 'cake';
return function () { return { food : food, fruit : fruit } } } menu = outer_function(); // inner_function console.log(menu()); // {food: "cake", fruit: "apple"}
從上面這個demo中咱們發現,儘管outer_function()函數執行完畢了。理論上內部的兩個變量 fruit 和 food 應該被銷燬。但咱們如今卻可使用menu()來獲取。
這種解決方法就叫閉包。使用閉包的套路其實很簡單:一個返回函數的函數!