javascript函數語言程式設計要掌握的知識點講解
閱讀目錄
- 理解call和apply 及arguments.callee
- 閉包的理解
- javascript中的this詳解
- 理解函式引用和函式呼叫的區別
- 理解js中的鏈式呼叫
- 理解使用函式實現歷史記錄–提高效能
- 理解通過Function擴充套件型別
- 理解使用模組模式編寫程式碼
- 理解惰性實列化
- 推薦分支函式(解決相容問題的更好的方法)
- 惰性載入函式(也是解決相容問題的)
- 理解函式節流
一:理解call和apply 及arguments.callee
ECMAScript3給Function的原型定義了兩個方法,他們是Function.prototype.call 和 Function.prototype.apply. 其實他們的作用是一樣的,只是傳遞的引數不一樣而已;
1. apply; 接受2個引數,第一個引數指定了函式體內this物件的指向,第二個引數為一個類似陣列的集合,比如如下程式碼:
1 2 3 4 5 6 7 8 9 |
var yunxi = function(a,b){ console.log([a,b]); // [1,2] console.log(this === window); // true }; yunxi.apply(null,[1,2]); |
如上程式碼,我們第一個引數傳入null,函式體內預設會指向與宿主物件,即window物件;因此我們可以在yunxi函式內列印下值為true即可看到:
下面我們來看看使用call方法的例項如下:
1 2 3 4 5 6 7 8 9 |
var yunxi = function(a,b){ console.log([a,b]); // [1,2] console.log(this === window); // true }; yunxi.call(null,1,2); |
可以看到 call方法的第二個引數是以逗號隔開的引數;
那麼call和apply用在什麼地方呢?
1. call和apply 最常見的用途是改變函式體內的this指向,如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var longen = { name:'yunxi' }; var longen2 = { name: '我叫塗根華' }; var name = "我是來測試的"; var getName = function(){ return this.name; }; console.log(getName()); // 列印 "我是來測試的"; console.log(getName.call(longen)); // 列印 yunxi console.log(getName.call(longen2)); // 列印 "我叫塗根華" |
第一次呼叫 getName()方法,因為它是普通函式呼叫,所以它的this指向與window,因此列印出全域性物件的name的值;
第二次呼叫getName.call(longen); 執行這句程式碼後,getName這個方法的內部指標this指向於longen這個物件了,因此列印this.name實際上是longen.name,因此返回的是name=”yunxi”;
但是this指標也有列外的情況,比如一個點選元素,當我們點選一個元素的時候,this指標就指向與那個點選元素,但是當我們在內部再包含一個函式後,在函式內再繼續呼叫this的話,那麼現在的this指標就指向了window了;比如如下程式碼:
1 2 3 4 5 6 7 |
document.getElementById("longen").onclick = function(){ console.log(this); // this 就指向於div元素物件了 var func = function(){ console.log(this); // 列印出window物件 } func(); } |
如上程式碼。可以看到外部this指向與被點選的那個元素,內部普通函式呼叫,this指標都是指向於window物件。但是我們可以使用call或者apply方法來改變this的指標的;如下程式碼:
1 2 3 4 5 6 7 |
document.getElementById("longen").onclick = function(){ console.log(this); // this 就指向於div元素物件了 var func = function(){ console.log(this); // 就指向於div元素物件了 } func.call(this); } |
如上程式碼我們使用call方法呼叫func函式,使this指向與func這個物件了,當然上面的方法我們還可以不使用call或者apply方法來改變this的指標,我們可以在外部先使用一個變數來儲存this的指標,在內部呼叫的時候我們可以使用哪個變數即可,如下程式碼演示:
1 2 3 4 5 6 7 8 |
document.getElementById("longen").onclick = function(){ console.log(this); // this 就指向於div元素物件了 var self = this; var func = function(){ console.log(self); // 就指向於div元素物件了 } func(); } |
arguments.callee的理解
callee是arguments的一個屬性,它可以被用作當前函式或函式體執行的環境中,或者說呼叫一個匿名函式;返回的是當前正在被執行的Function物件;簡單的來說就是當前執行環境的函式被呼叫時候,arguments.callee物件會指向與自身,就是當前的那個函式的引用;
如下程式碼:
1 2 3 4 5 6 7 8 9 10 |
var count = 1; var test = function() { console.log(count + " -- " + (test.length == arguments.callee.length) ); // 列印出 1 -- true 2 -- true 3 -- true if (count++ ) { // 呼叫test()函式自身 arguments.callee(); } }; test(); |
arguments.callee()的含義是呼叫當前正在執行的函式自身,比如上面的test的匿名函式;
Function.prototype.bind介紹
目前很多高階瀏覽器都支援Function.prototype.bind方法,該方法用來指定函式內部的this指向。為了支援各個瀏覽器,我們也可以自己來簡單的模擬一個~
如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Function.prototype.bind = function(context) { var self = this; return function(){ return self.apply(context,arguments); } } var yunxi = { name: 'yunxi' }; var func = function(){ console.log(this.name); // yunxi }.bind(yunxi); func(); |
如上程式碼所示:func這個函式使用呼叫bind這個方法,並且把物件yunxi作為引數傳進去,然後bind函式使用return返回一個函式,當我們呼叫func()執行這個方法的時候,其實我們就是在呼叫bind方法內的return返回的那個函式,在返回的那個函式內context的上下文其實就是我們以引數yunxi物件傳進去的,因此this指標指向與yunxi這個物件了~ 所以列印出this.name 就是yunxi那個物件的name了;
除了上面我們看到的介紹apply或者call方法可以改變this指標外,我們還可以使用call或者apply來繼承物件的方法;實質也就是改變this的指標了;
比如有如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
var Yunxi = function(name){ this.name = name; }; var Longen = function(){ Yunxi.apply(this,arguments); }; Longen.prototype.getName = function(){ return this.name; }; var longen = new Longen("tugenhua"); console.log(longen.getName()); // 列印出tugenhua |
如上程式碼:我先例項化Longen這個物件,把引數傳進去,之後使用Yunxi.apply(this,arguments)這句程式碼來改變Longen這個物件的this的指標,使他指向了Yunxi這個物件,因此Yunxi這個物件儲存了longen這個例項化物件的引數tugenhua,因此當我們呼叫longen.getName這個方法的時候,我們返回this.name,即我們可以認為返回的是 Yunxi.name 因此返回的是 tugenhua,我們只是借用了下Yunxi這個物件內的this.name來儲存Longen傳進去的引數而已;
二:閉包的理解
閉包的結構有如下2個特性
1.封閉性:外界無法訪問閉包內部的資料,如果在閉包內宣告變數,外界是無法訪問的,除非閉包主動向外界提供訪問介面;
2.永續性:一般的函式,呼叫完畢之後,系統自動登出函式,而對於閉包來說,在外部函式被呼叫之後,閉包結構依然儲存在
系統中,閉包中的資料依然存在,從而實現對資料的持久使用。
缺點:
使用閉包會佔有記憶體資源,過多的使用閉包會導致記憶體溢位等.
如下程式碼:
1 2 3 4 5 6 7 8 9 |
function a(x) { var a = x; var b = function(){ return a; } return b; } var b = a(1); console.log(b()); // 1 |
首先在a函式內定義了2個變數,1個是儲存引數,另外一個是閉包結構,在閉包結構中儲存著b函式內的a變數,預設情況下,當a函式呼叫完之後a變數會自動銷燬的,但是由於閉包的影響,閉包中使用了外界的變數,因此a變數會一直儲存在記憶體當中,因此變數a引數沒有隨著a函式銷燬而被釋放,因此引申出閉包的缺點是:過多的使用閉包會佔有記憶體資源,或記憶體溢位等肯能性;
1 2 3 4 5 6 7 8 9 10 11 |
// 經典的閉包實列如下: function f(x){ //外部函式 var a = x; // 外部函式的區域性變數,並傳遞引數 var b = function(){ // 內部函式 return a; // 訪問外部函式中的區域性變數 }; a++; // 訪問後,動態更新外部函式的變數 return b; // 返回內部函式 } var c = f(5); // 呼叫外部函式並且賦值 console.log(c()); // 呼叫內部函式,返回外部函式更新後的值為6 |
下面我們來看看如下使用閉包的列子
在如下程式碼中有2個函式,f函式的功能是:把陣列型別的引數中每個元素的值分別封裝在閉包結構中,然後把閉包儲存在一個陣列中,並返回這個陣列,但是在函式e中呼叫函式f並向其傳遞一個陣列[“a”,”b”,”c”],然後遍歷返回函式f返回陣列,我們執行列印後發現都是c undefined,那是因為在執行f函式中的迴圈時候,把值雖然儲存在temp中,但是每次迴圈後temp值在不斷的變化,當for迴圈結束後,此時temp值為c,同時i變為3,因此當呼叫的時候 列印出來的是temp為3,arrs[3]變為undefined;因此列印出 c undefined
解決閉包的缺陷我們可以再在外面包一層函式,每次迴圈的時候,把temp引數和i引數傳遞進去 如程式碼二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// 程式碼一 function f(x) { var arrs = []; for(var i = 0; i ) { var temp = x[i]; arrs.push(function(){ console.log(temp + ' ' +x[i]); // c undefined }); } return arrs; } function e(){ var ar = f(["a","b","c"]); for(var i = 0,ilen = ar.length; i ) { ar[i](); } } e(); // 程式碼二: function f2(x) { var arrs = []; for(var i = 0; i ) { var temp = x[i]; (function(temp,i){ arrs.push(function(){ console.log(temp + ' ' +x[i]); // c undefined }); })(temp,i); } return arrs; } function e2(){ var ar = f2(["a","b","c"]); for(var i = 0,ilen = ar.length; i ) { ar[i](); } } e2(); |
三:javascript中的this詳解
this的指向常見的有如下幾點需要常用到:
- 全域性物件的this是指向與window;
- 作為普通函式呼叫。
- 作為物件方法呼叫。
- 構造器呼叫。
- Function.prototype.call 或 Function.prototype.apply呼叫。
下面我們分別來介紹一下:
1. 全域性物件的this;
1 2 3 4 5 6 7 8 9 |
console.log(this); // this指向於window setTimeout() 和 setInterval()函式內部的this指標是指向於window的,如下程式碼: function test(){ console.log(11); } setTimeout(function(){ console.log(this === window); // true this.test(); // 11 }); |
2. 作為普通函式呼叫;
1 2 3 4 5 6 |
如下程式碼: var name = "longen"; function test(){ return this.name; } console.log(test()); // longen |
當作為普通函式呼叫時候,this總是指向了全域性物件,在瀏覽器當中,全域性物件一般指的是window;
3. 作為物件的方法呼叫。
如下程式碼:
1 2 3 4 5 6 7 8 |
var obj = { "name": "我的花名改為云溪了,就是為了好玩", getName: function(){ console.log(this); // 在這裡this指向於obj物件了 console.log(this.name); // 列印 我的花名改為云溪了,就是為了好玩 } }; obj.getName(); // 物件方法呼叫 |
但是呢,我們不能像如下一樣呼叫物件了,如下呼叫物件的話,this還是執行了window,如下程式碼:
1 2 3 4 5 6 7 8 9 10 |
var name = "全域性物件名字"; var obj = { "name": "我的花名改為云溪了,就是為了好玩", getName: function(){ console.log(this); // window console.log(this.name); // 全域性物件名字 } }; var yunxi = obj.getName; yunxi(); |
執行yunxi()函式,還是會像呼叫普通函式一樣,this指向了window的;
4. 構造器呼叫。
Javascript中不像Java一樣,有類的概念,而JS中只能通過構造器建立物件,通過new 物件,當new運算子呼叫函式時候,該函式會返回一個物件,一般情況下,構造器裡面的this就是指向返回的這個物件;
如下程式碼:
1 2 3 4 5 |
var Obj = function(){ this.name = "yunxi"; }; var test = new Obj(); console.log(test.name); // yunxi |
注意:構造器函式第一個字母需要大寫,這是為了區分普通函式還是構造器函式而言;
如上程式碼:通過呼叫 new Obj()方法 返回值儲存到test變數中,那麼test就是那個物件了,所以內部的this就指向與test物件了,因此test.name就引用到了內部的this.name 即輸出 “yunxi”字串;
但是也有例外的情況,比如構造器顯示地返回了一個物件的話,那麼這次繼續呼叫的話,那麼會最終會返回這個物件,比如如下程式碼:
1 2 3 4 5 6 7 8 |
var obj = function(){ this.name = "yunxi"; return { "age": "27" } }; var test = new obj(); console.log(test.name); // undefined |
那麼繼續呼叫的話,會返回unedfined,因為返回的是那個物件,物件裡面沒有name這個屬性,因此值為undefined;
四:理解函式引用和函式呼叫的區別
看下面的程式碼分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// 函式引用 程式碼一 function f(){ var x = 5; return x; } var a = f; var b = f; console.log(a===b); // true // 函式呼叫 程式碼二 function f2() { var x = 5; return x; } var a2 = f2(); var b2 = f2(); console.log(a2 === b2); // 函式呼叫 程式碼三 function f3(){ var x = 5; return function(){ return x; } } var a3 = f3(); var b3 = f3(); console.log(a3 === b3); // false |
如上的程式碼:程式碼一和程式碼二分部是函式引用和函式呼叫的列子,返回都為true,程式碼三也是函式呼叫的列子,返回且為false
我們現在來理解下函式引用和函式呼叫的本質區別:當引用函式時候,多個變數記憶體儲存的是函式的相同的入口指標,因此對於同一個函式來講,無論多少個變數引用,他們都是相等的,因為對於引用型別(物件,陣列,函式等)都是比較的是記憶體地址,如果他們記憶體地址一樣的話,說明是相同的;但是對於函式呼叫來講,比如程式碼三;每次呼叫的時候,都被分配一個新的記憶體地址,所以他們的記憶體地址不相同,因此他們會返回false,但是對於程式碼二來講,我們看到他們沒有返回函式,只是返回數值,他們比較的不是記憶體地址,而是比較值,所以他們的值相等,因此他們也返回true,我們也可以看看如下實列化一個物件的列子,他們也被分配到不同的記憶體地址,因此他們也是返回false的;如下程式碼測試:
1 2 3 4 5 6 |
function F(){ this.x = 5; } var a = new F(); var b = new F(); console.log(a === b); // false |
五:理解js中的鏈式呼叫
我們使用jquery的時候,jquery的簡單的語法及可實現鏈式呼叫方法,現在我們自己也封裝一個鏈式呼叫的方法,來理解下 jquery中如何封裝鏈式呼叫 無非就是每次呼叫一個方法的時候 給這個方法返回this即可,this指向該物件自身,我們看看程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
// 定義一個簡單的物件,每次呼叫物件的方法的時候,該方法都返回該物件自身 var obj = { a: function(){ console.log("輸出a"); return this; }, b:function(){ console.log("輸出b"); return this; } }; console.log(obj.a().b()); // 輸出a 輸出b 輸出this指向與obj這個物件 // 下面我們再看下 上面的通過Function擴充套件型別新增方法的demo如下: Function.prototype.method = function(name,func) { if(!this.prototype[name]) { this.prototype[name] = func; return this; } } String.method('trim',function(){ return this.replace(/^s+|s+$/g,''); }); String.method('log2',function(){ console.log("鏈式呼叫"); return this; }); String.method('r',function(){ return this.replace(/a/,''); }); var str = " abc "; console.log(str.trim().log2().r()); // 輸出鏈式呼叫和 bc |
六:理解使用函式實現歷史記錄–提高效能
函式可以使用物件去記住先前操作的結果,從而避免多餘的運算。比如我們現在測試一個費波納茨的演算法,我們可以使用遞迴函式計算fibonacci數列,一個fibonacci數字是之前兩個fibonacci數字之和,最前面的兩個數字是0和1;程式碼如下:
1 2 3 4 5 6 7 8 9 |
var count = 0; var fibonacci = function(n) { count++; return n ); }; for(var i = 0; i ) { console.log(i+":"+fibonacci(i)); } console.log(count); // 453 |
我們可以看到如上 fibonacci函式總共呼叫了453次,for迴圈了11次,它自身呼叫了442次,如果我們使用下面的記憶函式的話,那麼就可以減少他們的運算次數,從而提高效能;
思路:先使用一個臨時陣列儲存儲存結果,當函式被呼叫的時候,先看是否已經有儲存結果 如果有的話,就立即返回這個儲存結果,否則的話,呼叫函式運算下;程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var count2 = 0; var fibonacci2 = (function(){ var memo = [0,1]; var fib = function(n) { var result = memo[n]; count2++; if(typeof result !== 'number') { result = fib(n-1) + fib(n-2); memo[n] = result; } return result; }; return fib; })(); for(var j = 0; j ) { console.log(j+":"+fibonacci2(j)); } console.log(count2); // 29 |
這個函式也返回了同樣的結果,但是隻呼叫了函式29次,迴圈了11次,也就是說函式自身呼叫了18次,從而減少無謂的函式的呼叫及運算,下面我們可以把這個函式進行抽象化,以構造帶記憶功能的函式,如下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
var count3 = 0; var memoizer = function(memo,formula) { var recur = function(n) { var result = memo[n]; count3++; // 這句程式碼只是說明執行函式多少次,在程式碼中並無作用,實際使用上可以刪掉 if(typeof result !== 'number') { result = formula(recur,n); memo[n] = result; } return result; }; return recur; }; var fibonacci3 = memoizer([0,1],function(recur,n){ return recur(n-1) + recur(n-2); }); // 呼叫方式如下 for(var k = 0; k ) { console.log(k+":"+fibonacci3(k)); } console.log(count3); // 29 |
如上封裝 memoizer 裡面的引數是實現某個方法的計算公式,具體的可以根據需要自己手動更改,這邊的思路無非就是想習慣使用物件去儲存臨時值,從而減少不必要的取值儲存值的操作;
七:理解通過Function擴充套件型別
javascript 允許為語言的基本資料型別定義方法。通過Object.prototype新增原型方法,該方法可被所有的物件使用。
這對函式,字串,數字,正則和布林值都適用,比如如下現在給Function.prototype增加方法,使該方法對所有函式都可用,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
Function.prototype.method = function(name,func) { if(!this.prototype[name]) { this.prototype[name] = func; return this; } } Number.method('integer',function(){ return Math[this this); }); console.log((-10/3).integer()); // -3 String.method('trim',function(){ return this.replace(/^s+|s+$/g,''); }); console.log(" abc ".trim()); // abc |
八:理解使用模組模式編寫程式碼
使用函式和閉包可以構建模組,所謂模組,就是一個提供介面卻隱藏狀態與實現的函式或物件。使用函式構建模組的優點是:減少全域性變數的使用;
比如如下:我想為String擴充套件一個方法,該方法的作用是尋找字串中的HTML字元字型並將其替換為對應的字元;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// 如下程式碼: Function.prototype.method = function(name,func) { if(!this.prototype[name]) { this.prototype[name] = func; return this; } } String.method('deentityify',function(){ var entity = { quot: '"', It: ', gt: '>' }; return function(){ return this.replace(/&([^&;]+);/g,function(a,b){ var r = entity[b]; return typeof r === 'string' ? r : a; }); } }()); console.log("&It;">".deentityify()); // |
模組模式利用函式作用域和閉包來建立繫結物件與私有成員的關聯,比如在上面的deentityify()方法才有權訪問字元實體表entity這個資料物件;
模組開發的一般形式是:定義了私有變數和函式的函式,利用閉包建立可以訪問到的私有變數和函式的特權函式,最後返回這個特權函式,或把他們儲存到可以訪問的地方。
模組模式一般會結合例項模式使用。javascript的例項就是使用物件字面量表示法建立的。物件的屬性值可以是數值或者函式,並且屬性值在該物件的生命週期中不會發生變化;比如如下程式碼屬於模組模式:定義了一個私有變數name屬性,和一個例項模式(物件字面量obj)並且返回這個物件字面量obj,物件字面量中的方法與私有變數name進行了繫結;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// 比如如下經典的模組模式 var MODULE = (function(){ var name = "tugenhua"; var obj = { setName: function() { this.name = name; }, getName: function(){ return this.name; } }; return obj; })(); MODULE.setName() console.log(MODULE.getName()); // tugenhua |
九:理解惰性實列化
在頁面中javascript初始化執行的時候就例項化類,如果在頁面中沒有使用這個實列化的物件,就會造成一定的記憶體浪費和效能損耗;這時候,我們可以使用惰性實列化來解決這個問題,惰性就是把實列化推遲到需要使用它的時候才去做,做到 “按需供應”;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// 惰性實列化程式碼如下 var myNamespace = function(){ var Configure = function(){ var privateName = "tugenhua"; var privateGetName = function(){ return privateName; }; var privateSetName = function(name) { privateName = name; }; // 返回單列物件 return { setName: function(name) { privateSetName(name); }, getName: function(){ return privateGetName(); } } }; // 儲存Configure實列 var instance; return { init: function(){ // 如果不存在實列,就建立單列實列 if(!instance) { instance = Configure(); } // 建立Configure單列 for(var key in instance) { if(instance.hasOwnProperty(key)) { this[key] = instance[key]; } } this.init = null; return this; } } }(); // 呼叫方式 myNamespace.init(); var name = myNamespace.getName(); console.log(name); // tugenhua |
如上程式碼是惰性化實列程式碼:它包括一個單體Configure實列,直接返回init函式,先判斷該單體是否被實列化,如果沒有被實列化的話,則建立並執行實列化並返回該實列化,如果已經實列化了,則返回現有實列;執行完後,則銷燬init方法,只初始化一次
十:推薦分支函式(解決相容問題的更好的方法)
分支函式的作用是:可以解決相容問題if或者else的重複判斷的問題,我們一般的做法是:根據相容的不同寫if,else等,這些判斷來實現相容,但是這樣明顯就有一個缺點,每次執行這個函式的時候,都需要進行if和else的檢測,效率明顯不高,我們現在使用分支函式來實現當初始化的時候進行一些檢測,在之後的執行程式碼過程中,程式碼就無需檢測了;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// 我們先來看看傳統的封裝ajax請求的函式 //建立XMLHttpRequest物件: var xmlhttp; function createxmlhttp(){ if (window.XMLHttpRequest){ // code for IE7+, Firefox, Chrome, Opera, Safari xmlhttp=new XMLHttpRequest(); } else{ // code for IE6, IE5 xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); } } // 下面我們看看分支函式程式碼如下: var XHR = (function(){ var standard = { createXHR : function() { return new XMLHttpRequest(); } }; var oldActionXObject = { createXHR : function(){ return new ActiveXObject("Microsoft.XMLHTTP"); } }; var newActionXObject = { createXHR : function(){ return new ActiveXObject("Msxml2.XMLHTTP"); } }; if(standard.createXHR) { return standard; }else { try{ newActionXObject.createXHR(); return newActionXObject; }catch(e){ oldActionXObject.createXHR(); return oldActionXObject; } } })(); console.log(XHR.createXHR()); //xmlHttpRequest物件 |
上面的程式碼就是分支函式,分支的原理是:宣告幾個不同名稱的物件,且為該不同名稱物件宣告一個相同的方法,然後根據不同的瀏覽器設計來實現,接著開始進行瀏覽器檢測,並且根據瀏覽器檢測來返回哪一個物件,不論返回的是哪一個物件,最後它一致對外的介面都是createXHR方法的;
十一:惰性載入函式(也是解決相容問題的)
和上面分支的原理是一樣的,程式碼也可以按照上面的推薦分支風格編碼的;解決的問題也是解決多個if條件判斷的;程式碼如下:
1 2 3 4 5 6 7 8 9 |
// 程式碼如下: var addEvent = function(el,type,handler){ addEvent = el.addEventListener ? function(el,type,handler){ el.addEventListener(type,handler,false); } : function(el,type,handler) { el.attachEvent("on" + type,handler); } addEvent(el,type,handler); }; |
惰性載入函式也是在函式內部改變自身的一種方式,在重複執行的時候就不會再進行檢測的;惰性載入函式的分支只會執行一次,即第一次呼叫的時候,其優點如下:
1. 要執行的適當程式碼只有在實際呼叫函式時才執行。
2. 第一次呼叫該函式的時候,緊接著內部函式也會執行,但是正因為這個,所以後續繼續呼叫該函式的話,後續的呼叫速度會很快;因此避免了多重條件;
十二:理解函式節流
DOM操作的互動需要更多的記憶體和CPU時間,連續進行過多的DOM相關的操作可能會導致瀏覽器變慢甚至崩潰,函式節流的設計思想是讓某些程式碼可以在間斷的情況下連續重複執行,實現該方法可以使用定時器對該函式進行節流操作;
比如:第一次呼叫函式的時候,建立一個定時器,在指定的時間間隔下執行程式碼。當第二次執行的時候,清除前一次的定時器並設定另一個,將其替換成一個新的定時器;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 如下簡單函式節流程式碼演示 var throttle = { timeoutId: null, // 需要執行的方法 preformMethod: function(){ }, // 初始化需要呼叫的方法 process: function(){ clearTimeout(this.timeoutId); var self = this; self.timeoutId = setTimeout(function(){ self.preformMethod(); },100); } }; // 執行操作 throttle.process(); |
函式節流解決的問題是一些程式碼(比如事件)無間斷的執行,這可能會影響瀏覽器的效能,比如瀏覽器變慢或者直接崩潰。比如對於mouseover事件或者click事件,比如點選tab項選單,無限的點選,有可能會導致瀏覽器會變慢操作,這時候我們可以使用函式節流的操作來解決;