第一章 面向對象的Javascript
1.1 多態在面向對象設計中的應用
多態最根本好處在於,你不必詢問對象“你是什么類型”而后根據得到的答案調用對象的某個行為--你只管調用行為就好,剩下的一切多態會搞定
換句話說就是:多態的最根本作用就是把過程化的條件分支語句轉化為對象的多態性,從而消除這些條件分支語句
例子:假設有一個地圖應用,每個地圖API提供商都提供了show方法,負責在頁面上顯示地圖,首先我們用一些分支條件語句來實現一個調用方法renderMap
此時一旦需要增加搜搜地圖的應用,就必須改動renderMap函數,繼續在里面堆砌分支條件語句
我們必須將程序中相同的部分抽象出來,那就是顯示整個地圖
那么此時,即使以后增加了搜搜地圖,renderMap也不需要做什么改變
1.2 對象會記住它的原型
就Javascript的真正實現來說,不能說對象有原型,而只能說對象的構造器有原型,對“對象把請求委托給它自己的原型”這句話, 更好的說法是對象把請求委托給它的構造器原型,
javascript對象通過一個名為_proto_的隱藏屬性,來將請求轉交給它的構造器,對象的_proto_會指向的它的構造器對象,實際上,_proto_就是對象跟“對象構造器原型”聯系起來的紐帶
1.3 如果對象無法響應請求,會把請求委托給它的構造器原型
當對象需要a需要借用對象b的能力時,可以選擇性的把對象a的構造器的原型指向對象b,從而達到繼承的目的
- 首先,嘗試遍歷對象a的所有屬性,沒有找到name這個屬性
- 查找name屬性的這個請求被委托給對象a的構造器原型,它被a._proto_記錄着並且指向A.prototype,而A.prototype指向obj.
- 在對象obj中找到了name屬性,並返回了它的值
當我們期望得到一個“類”繼承自另外一個“類"的效果時,往往會使用下面的方式
第二章 this、call和apply
2.1 this的指向
this的指向大致可以分為以下4種:
- 作為對象的方法調用
- 作為普通函數調用
- 構造器調用
- Function.prototype.call或Function.prototype.apply調用(動態的改編傳入函數的this)
例子:在div節點事件函數內部,有一個局部的callback方法,callback在調用時,內部的this會指向window
而我們想讓它指向div節點,可以用一個變量保存div節點的引用
2.2 call和apply的區別
apply接受兩個參數,第一個參數指定了函數體內的this對象的指向,第二個參數為一個帶下標的集合,可以為數組,也可以是類數組,apply方法將這個集合中的元素作為參數傳遞給被調用的函數
call傳入的參數不固定,第一個也是代表函數體內this的指向,第二個參數開始,每個參數依次被傳入函數
javascript不計較形參實參和順序上的區別,javascript內部的參數就是用一個數組來表示的,所以apply使用的更頻繁一點,我們不必關心具體有多少個參數要傳入給函數,只需要一股腦的推過去就好了
如果我們明確知道函數接受多少個參數,而且想一目了然的表達形參和實參的關系,那么也可以用call來傳送參數
第三章 閉包和高階函數
3.1 變量的生命周期
假設頁面上有5個div節點,我們通過循環來給每個div綁定onclick事件,按照索引順序,點擊第一個div時彈出0,點擊第二個div時彈出1,依次類推,代碼如下:
但由於onclick是異步出發,當事件觸發時,for循環早已結束,此時i已經是5,所以輸出一直是5,解決方法是把每次循環的i封閉起來,當在事件函數中順着作用於鏈從內到外查找i時,會先找到被封閉在閉包環境中的i
3.2 封裝變量
把一些不需要暴露在全局的變量封裝成私有變量,即封閉在函數內部, 減少頁面中的全局變量
封閉在函數內部的變量或函數,只會在初始化閉包函數的時候執行一次,以后每一次調用函數,將不再執行封閉在函數內部的代碼,這也是閉包的一大好處
3.3 閉包和面向對象設計
通常面向對象能實現的思想,用閉包也能實現,先來看看跟閉包相關的代碼:
如果換成面向對象的寫法,就是:
3.4 高階函數
高階函數是指至少滿足下列條件之一的函數:
- 函數作為參數被傳遞
- 函數作為返回值輸出
1 函數作為參數被傳遞
將一些難以復用的代碼提取出來,用回調函數的形式傳入處理函數,如
Array.prototype.sort的使用可以看到,我們的目的是對數組進行排序,這是不變的部分,而使用什么規則去排序,這是可變的部分,把可變的部分封裝在函數參數里,動態傳入
3.5 函數柯里化(function currying)
currying 又稱部分求值,一個currying首先會接受一個參數,接受這個參數之后,並不會馬上求值,而是繼續返回另外一個函數,剛才傳入的參數在函數形成的閉包中被保存起來,待到函數被真正需要求值的時候,之前傳入的所有參數都會一次性求值
首先來看下一種currying函數的實現,方便了解其思想
再來看一個通用的function currying() {} ,function currying() {}接受一個參數,即將要被currying的函數
3.6 函數節流
假設一個函數被觸發的概率非常高,就需要采取一些函數節流的方式實現
3.7 分時函數
有些函數需要用戶主動調用,但因為一些客觀原因,這些函數會嚴重影響頁面性能,比如說我們要在短時間內要往頁面中大量加載DOM節點,處理這一類問題的解決方案就是使用timeChunk函數讓創建節點的工作分批進行,比如1秒創建1000個節點,改為每200毫秒創建8個節點
timeChunk函數接受3個參數,第一個參數是創建節點所用到的數據,第二個參數是創建節點邏輯的函數,第三個參數表示每一批創建的節點數量
3.8 惰性加載函數
比如我們需要一個要在瀏覽器中綁定時間的addEvent函數
這個函數的缺點就是,當它每次被調用的時候都會執行里面的if條件分支語句
第二種方案就是,在代碼加載的時候執行一次判斷,以便讓addEvent包裹正確邏輯的函數,代碼如下
但目前這個函數還是有一個缺點,假設我們從頭到尾都沒有使用過addEvent函數呢?
第三種方案就是所使用的惰性加載方案,在第一次進入條件分支的時候,在函數內部會重寫這個函數,重寫之后的函數就是我們期望的addEvent函數,在下一次進入addEvent函數的時候,addEvent就不存在那些分支判斷