自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JavaScript高級(jí)程序設(shè)計(jì)高級(jí)技巧

開(kāi)發(fā) 前端
本篇是看的《JS高級(jí)程序設(shè)計(jì)》第23章《高級(jí)技巧》做的讀書(shū)分享。本篇按照書(shū)里的思路根據(jù)自己的理解和經(jīng)驗(yàn),進(jìn)行擴(kuò)展延伸,同時(shí)指出書(shū)里的一些問(wèn)題。將會(huì)討論安全的類(lèi)型檢測(cè)、惰性載入函數(shù)、凍結(jié)對(duì)象、定時(shí)器等話(huà)題。

本篇是看的《JS高級(jí)程序設(shè)計(jì)》第23章《高級(jí)技巧》做的讀書(shū)分享。本篇按照書(shū)里的思路根據(jù)自己的理解和經(jīng)驗(yàn),進(jìn)行擴(kuò)展延伸,同時(shí)指出書(shū)里的一些問(wèn)題。將會(huì)討論安全的類(lèi)型檢測(cè)、惰性載入函數(shù)、凍結(jié)對(duì)象、定時(shí)器等話(huà)題。

[[202322]]

1. 安全的類(lèi)型檢測(cè)

這個(gè)問(wèn)題是怎么安全地檢測(cè)一個(gè)變量的類(lèi)型,例如判斷一個(gè)變量是否為一個(gè)數(shù)組。通常的做法是使用instanceof,如下代碼所示: 

  1. let data = [1, 2, 3]; 
  2. console.log(data instanceof Array); //true 

 但是上面的判斷在一定條件下會(huì)失敗——就是在iframe里面判斷一個(gè)父窗口的變量的時(shí)候。寫(xiě)個(gè)demo驗(yàn)證一下,如下主頁(yè)面的main.html:

  1. <script> 
  2.     window.global = { 
  3.         arrayData: [1, 2, 3] 
  4.     } 
  5.     console.log("parent arrayData installof Array: " +  
  6.           (window.global.arrayData instanceof Array)); 
  7. </script> 
  8. <iframe src="iframe.html"></iframe> 

在iframe.html判斷一下父窗口的變量類(lèi)型:

  1. <script> 
  2.     console.log("iframe window.parent.global.arrayData instanceof Array: " +  
  3.         (window.parent.global.arrayData instanceof Array)); 
  4. </script> 

在iframe里面使用window.parent得到父窗口的全局window對(duì)象,這個(gè)不管跨不跨域都沒(méi)有問(wèn)題,進(jìn)而可以得到父窗口的變量,然后用instanceof判斷。***運(yùn)行結(jié)果如下:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

可以看到父窗口的判斷是正確的,而子窗口的判斷是false,因此一個(gè)變量明明是Array,但卻不是Array,這是為什么呢?既然這個(gè)是父子窗口才會(huì)有的問(wèn)題,于是試一下把Array改成父窗口的Array,即window.parent.Array,如下圖所示: 

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

這次返回了true,然后再變換一下其它的判斷,如上圖,***可以知道根本原因是上圖***一個(gè)判斷:

  1. Array !== window.parent.Array 

它們分別是兩個(gè)函數(shù),父窗口定義了一個(gè),子窗口又定義了一個(gè),內(nèi)存地址不一樣,內(nèi)存地址不一樣的Object等式判斷不成立,而window.parent.arrayData.constructor返回的是父窗口的Array,比較的時(shí)候是在子窗口,使用的是子窗口的Array,這兩個(gè)Array不相等,所以導(dǎo)致判斷不成立。

那怎么辦呢?

由于不能使用Object的內(nèi)存地址判斷,可以使用字符串的方式,因?yàn)樽址腔绢?lèi)型,字符串比較只要每個(gè)字符都相等就好了。ES5提供了這么一個(gè)方法Object.prototype.toString,我們先小試牛刀,試一下不同變量的返回值:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

可以看到如果是數(shù)組返回”[object Array]”, ES5對(duì)這個(gè)函數(shù) 是這么規(guī)定的:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

也就是說(shuō)這個(gè)函數(shù)的返回值是“[object ”開(kāi)頭,后面帶上變量類(lèi)型的名稱(chēng)和右括號(hào)。因此既然它是一個(gè)標(biāo)準(zhǔn)語(yǔ)法規(guī)范,所以可以用這個(gè)函數(shù)安全地判斷變量是不是數(shù)組。

可以這么寫(xiě): 

  1. Object.prototype.toString.call([1, 2, 3]) === 
  2.     "[object Array]" 

 

注意要使用call,而不是直接調(diào)用,call的***個(gè)參數(shù)是context執(zhí)行上下文,把數(shù)組傳給它作為執(zhí)行上下文。

有一個(gè)比較有趣的現(xiàn)象是ES6的class也是返回function:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

所以可以知道class也是用function實(shí)現(xiàn)的原型,也就是說(shuō)class和function本質(zhì)上是一樣的,只是寫(xiě)法上不一樣。

那是不是說(shuō)不能再使用instanceof判斷變量類(lèi)型了?不是的,當(dāng)你需要檢測(cè)父頁(yè)面的變量類(lèi)型就得使用這種方法, 本頁(yè)面的變量還是可以使用instanceof或者constructor的方法判斷 ,只要你能確保這個(gè)變量不會(huì)跨頁(yè)面。因?yàn)閷?duì)于大多數(shù)人來(lái)說(shuō),很少會(huì)寫(xiě)iframe的代碼,所以沒(méi)有必要搞一個(gè)比較麻煩的方式,還是用簡(jiǎn)單的方式就好了。

2. 惰性載入函數(shù)

有時(shí)候需要在代碼里面做一些兼容性判斷,或者是做一些UA的判斷,如下代碼所示:

 

  1. //UA的類(lèi)型 
  2. getUAType: function() { 
  3.     let ua = window.navigator.userAgent; 
  4.     if (ua.match(/renren/i)) { 
  5.         return 0; 
  6.     } 
  7.     else if (ua.match(/MicroMessenger/i)) { 
  8.         return 1; 
  9.     } 
  10.     else if (ua.match(/weibo/i)) { 
  11.         return 2; 
  12.     } 
  13.     return -1; 

 這個(gè)函數(shù)的作用是判斷用戶(hù)是在哪個(gè)環(huán)境打開(kāi)的網(wǎng)頁(yè),以便于統(tǒng)計(jì)哪個(gè)渠道的效果比較好。

這種類(lèi)型的判斷都有一個(gè)特點(diǎn),就是它的結(jié)果是死的,不管執(zhí)行判斷多少次,都會(huì)返回相同的結(jié)果,例如用戶(hù)的UA在這個(gè)網(wǎng)頁(yè)不可能會(huì)發(fā)生變化(除了調(diào)試設(shè)定的之外)。所以為了優(yōu)化,才有了惰性函數(shù)一說(shuō),上面的代碼可以改成: 

  1. //UA的類(lèi)型 
  2. getUAType: function() { 
  3.     let ua = window.navigator.userAgent; 
  4.     if(ua.match(/renren/i)) { 
  5.         pageData.getUAType = () => 0; 
  6.         return 0; 
  7.     } 
  8.     else if(ua.match(/MicroMessenger/i)) { 
  9.         pageData.getUAType = () => 1; 
  10.         return 1; 
  11.     } 
  12.     else if(ua.match(/weibo/i)) { 
  13.         pageData.getUAType = () => 2; 
  14.         return 2; 
  15.     } 
  16.     return -1; 

 在每次判斷之后,把getUAType這個(gè)函數(shù)重新賦值,變成一個(gè)新的function,而這個(gè)function直接返回一個(gè)確定的變量,這樣以后的每次獲取都不用再判斷了,這就是惰性函數(shù)的作用。你可能會(huì)說(shuō)這么幾個(gè)判斷能優(yōu)化多少時(shí)間呢,這么點(diǎn)時(shí)間對(duì)于用戶(hù)來(lái)說(shuō)幾乎是沒(méi)有區(qū)別的呀。確實(shí)如此,但是作為一個(gè)有追求的碼農(nóng),還是會(huì)想辦法盡可能優(yōu)化自己的代碼,而不是只是為了完成需求完成功能。并且當(dāng)你的這些優(yōu)化累積到一個(gè)量的時(shí)候就會(huì)發(fā)生質(zhì)變。我上大學(xué)的時(shí)候C++的老師舉了一個(gè)例子,說(shuō)有個(gè)系統(tǒng)比較慢找她去看一下,其中她做的一個(gè)優(yōu)化是把小數(shù)的雙精度改成單精度,***是快了不少。

但其實(shí)上面的例子我們有一個(gè)更簡(jiǎn)單的實(shí)現(xiàn),那就是直接搞個(gè)變量存起來(lái)就好了: 

  1. let ua = window.navigator.userAgent; 
  2. let UAType = ua.match(/renren/i) ? 0 : 
  3.                 ua.match(/MicroMessenger/i) ? 1 : 
  4.                 ua.match(/weibo/i) ? 2 : -1; 

 連函數(shù)都不用寫(xiě)了,缺點(diǎn)是即使沒(méi)有使用到UAType這個(gè)變量,也會(huì)執(zhí)行一次判斷,但是我們認(rèn)為這個(gè)變量被用到的概率還是很高的。

我們?cè)倥e一個(gè)比較有用的例子,由于Safari的無(wú)痕瀏覽會(huì)禁掉本地存儲(chǔ),因此需要搞一個(gè)兼容性判斷: 

  1. Data.localStorageEnabled = true
  2. // Safari的無(wú)痕瀏覽會(huì)禁用localStorage 
  3. try{ 
  4.     window.localStorage.trySetData = 1; 
  5. } catch(e) { 
  6.     Data.localStorageEnabled = false
  7.   
  8. setLocalData: function(key, value) {  
  9.     if (Data.localStorageEnabled) { 
  10.         window.localStorage[key] = value; 
  11.     } 
  12.     else {    
  13.         util.setCookie("_L_" + key, value, 1000); 
  14.     } 

 在設(shè)置本地?cái)?shù)據(jù)的時(shí)候,需要判斷一下是不是支持本地存儲(chǔ),如果是的話(huà)就用localStorage,否則改用cookie??梢杂枚栊院瘮?shù)改造一下: 

  1. setLocalData: function(key, value) { 
  2.     if(Data.localStorageEnabled) { 
  3.         util.setLocalData = function(key, value){ 
  4.             return window.localStorage[key]; 
  5.         } 
  6.     } else { 
  7.         util.setLocalData = function(key, value){ 
  8.             return util.getCookie("_L_" + key); 
  9.         } 
  10.     } 
  11.     return util.setLocalData(key, value); 

 這里可以減少一次if/else的判斷,但好像不是特別實(shí)惠,畢竟為了減少一次判斷,引入了一個(gè)惰性函數(shù)的概念,所以你可能要權(quán)衡一下這種引入是否值得,如果有三五個(gè)判斷應(yīng)該還是比較好的。

3. 函數(shù)綁定

有時(shí)候要把一個(gè)函數(shù)當(dāng)作參數(shù)傳遞給另一個(gè)函數(shù)執(zhí)行,此時(shí)函數(shù)的執(zhí)行上下文往往會(huì)發(fā)生變化,如下代碼: 

  1. class DrawTool { 
  2.     constructor() { 
  3.         this.points = []; 
  4.     } 
  5.     handleMouseClick(event) { 
  6.         this.points.push(event.latLng); 
  7.     } 
  8.     init() { 
  9.         $map.on('click', this.handleMouseClick); 
  10.     } 

 click事件的執(zhí)行回調(diào)里面this不是指向了DrawTool的實(shí)例了,所以里面的this.points將會(huì)返回undefined。***種解決方法是使用閉包,先把this緩存一下,變成that: 

  1. class DrawTool { 
  2.     constructor() { 
  3.         this.points = []; 
  4.     } 
  5.     handleMouseClick(event) { 
  6.         this.points.push(event.latLng); 
  7.     } 
  8.     init() { 
  9.         let that = this; 
  10.         $map.on('click', event => that.handleMouseClick(event)); 
  11.     } 

 由于回調(diào)函數(shù)是用that執(zhí)行的,而that是指向DrawTool的實(shí)例子,因此就沒(méi)有問(wèn)題了。相反如果沒(méi)有that它就用的this,所以就要看this指向哪里了。

因?yàn)槲覀冇昧思^函數(shù),而箭頭函數(shù)的this還是指向父級(jí)的上下文,因此這里不用自己創(chuàng)建一個(gè)閉包,直接用this就可以: 

  1. init() { 
  2.     $map.on('click',  
  3.             event => this.handleMouseClick(event)); 

 這種方式更加簡(jiǎn)單,第二種方法是使用ES5的bind函數(shù)綁定,如下代碼: 

  1. init() { 
  2.     $map.on('click',  
  3.             this.handleMouseClick.bind(this)); 

 這個(gè)bind看起來(lái)好像很神奇,但其實(shí)只要一行代碼就可以實(shí)現(xiàn)一個(gè)bind函數(shù): 

  1. Function.prototype.bind = function(context) { 
  2.     return () => this.call(context); 

 就是返回一個(gè)函數(shù),這個(gè)函數(shù)的this是指向的原始函數(shù),然后讓它c(diǎn)all(context)綁定一下執(zhí)行上下文就可以了。

4. 柯里化

柯里化就是函數(shù)和參數(shù)值結(jié)合產(chǎn)生一個(gè)新的函數(shù),如下代碼,假設(shè)有一個(gè)curry的函數(shù): 

  1. function add(a, b) { 
  2.     return a + b; 
  3.   
  4. let add1 = add.curry(1); 
  5. console.log(add1(5)); // 6 
  6. console.log(add1(2)); // 3 

 怎么實(shí)現(xiàn)這樣一個(gè)curry的函數(shù)?它的重點(diǎn)是要返回一個(gè)函數(shù),這個(gè)函數(shù)有一些閉包的變量記錄了創(chuàng)建時(shí)的默認(rèn)參數(shù),然后執(zhí)行這個(gè)返回函數(shù)的時(shí)候,把新傳進(jìn)來(lái)的參數(shù)和默認(rèn)參數(shù)拼一下變成完整參數(shù)列表去調(diào)原本的函數(shù),所以有了以下代碼: 

  1. Function.prototype.curry = function() { 
  2.     let defaultArgs = arguments; 
  3.     let that = this; 
  4.     return function(){ 
  5.         return that.apply(null,  
  6.                           arguments.concat(defulatArgs)); 
  7.     } 

 但是由于參數(shù)不是一個(gè)數(shù)組,沒(méi)有concat函數(shù),所以需要把偽數(shù)組轉(zhuǎn)成一個(gè)偽數(shù)組,可以用Array.prototype.slice: 

  1. Function.prototype.curry = function() { 
  2.     let slice = Array.prototype.slice; 
  3.     let defaultArgs = slice.call(arguments); 
  4.     let that = this; 
  5.     return function() { 
  6.         return that.apply(null,  
  7.                           arguments.concat(slice.call(defulatArgs))); 
  8.     } 

 現(xiàn)在舉一下柯里化一個(gè)有用的例子,當(dāng)需要把一個(gè)數(shù)組降序排序的時(shí)候,需要這樣寫(xiě): 

  1. let data = [1,5,2,3,10]; 
  2. data.sort((a, b) => b - a); // [10, 5, 3, 2, 1] 

 給sort傳一個(gè)函數(shù)的參數(shù),但是如果你的降序操作比較多,每次都寫(xiě)一個(gè)函數(shù)參數(shù)還是有點(diǎn)煩的,因此可以用柯里化把這個(gè)參數(shù)固化起來(lái): 

  1. Array.prototype.sortDescending =  
  2.                  Array.prototype.sort.curry((a, b) => b - a); 

 這樣就方便多了: 

  1. let data = [1,5,2,3,10]; 
  2. data.sortDescending(); 
  3.   
  4. console.log(data); // [10, 5, 3, 2, 1] 

 5. 防止篡改對(duì)象

有時(shí)候你可能怕你的對(duì)象被誤改了,所以需要把它保護(hù)起來(lái)。

(1)—Object.seal防止新增和刪除屬性

如下代碼,當(dāng)把一個(gè)對(duì)象seal之后,將不能添加和刪除屬性:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

當(dāng)使用嚴(yán)格模式將會(huì)拋異常:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

(2)Object.freeze凍結(jié)對(duì)象

這個(gè)是不能改屬性值,如下圖所示:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

同時(shí)可以使用Object.isFrozen、Object.isSealed、Object.isExtensible判斷當(dāng)前對(duì)象的狀態(tài)。

(3)defineProperty凍結(jié)單個(gè)屬性

如下圖所示,設(shè)置enumable/writable為false,那么這個(gè)屬性將不可遍歷和寫(xiě):

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

6. 定時(shí)器

怎么實(shí)現(xiàn)一個(gè)JS版的sleep函數(shù)?因?yàn)樵贑/C++/Java等語(yǔ)言是有sleep函數(shù),但是JS沒(méi)有。sleep函數(shù)的作用是讓線程進(jìn)入休眠,當(dāng)?shù)搅酥付〞r(shí)間后再重新喚起。你不能寫(xiě)個(gè)while循環(huán)然后不斷地判斷當(dāng)前時(shí)間和開(kāi)始時(shí)間的差值是不是到了指定時(shí)間了,因?yàn)檫@樣會(huì)占用CPU,就不是休眠了。

這個(gè)實(shí)現(xiàn)比較簡(jiǎn)單,我們可以使用setTimeout + 回調(diào): 

  1. function sleep(millionSeconds, callback) { 
  2.     setTimeout(callback, millionSeconds); 
  3. // sleep 2秒 
  4. sleep(2000, () => console.log("sleep recover")); 

但是使用回調(diào)讓我的代碼不能夠和平常的代碼一樣像瀑布流一樣寫(xiě)下來(lái),我得搞一個(gè)回調(diào)函數(shù)當(dāng)作參數(shù)傳值。于是想到了Promise,現(xiàn)在用Promise改寫(xiě)一下: 

  1. function sleep(millionSeconds) { 
  2.     return new Promise(resolve =>  
  3.                              setTimeout(resolve, millionSeconds)); 
  4. sleep(2000).then(() => console.log("sleep recover")); 

 但好像還是沒(méi)有辦法解決上面的問(wèn)題,仍然需要傳遞一個(gè)函數(shù)參數(shù)。

雖然使用Promise本質(zhì)上是一樣的,但是它有一個(gè)resolve的參數(shù),方便你告訴它什么時(shí)候異步結(jié)束,然后它就可以執(zhí)行then了,特別是在回調(diào)比較復(fù)雜的時(shí)候,使用Promise還是會(huì)更加的方便。

ES7新增了兩個(gè)新的屬性async/await用于處理的異步的情況,讓異步代碼的寫(xiě)法就像同步代碼一樣,如下async版本的sleep: 

  1. function sleep(millionSeconds) { 
  2.     return new Promise(resolve =>  
  3.                            setTimeout(resolve, millionSeconds)); 
  4.   
  5. async function init() { 
  6.     await sleep(2000); 
  7.     console.log("sleep recover"); 
  8.   
  9. init(); 

 相對(duì)于簡(jiǎn)單的Promise版本,sleep的實(shí)現(xiàn)還是沒(méi)變。不過(guò)在調(diào)用sleep的前面加一個(gè)await,這樣只有sleep這個(gè)異步完成了,才會(huì)接著執(zhí)行下面的代碼。同時(shí)需要把代碼邏輯包在一個(gè)async標(biāo)記的函數(shù)里面,這個(gè)函數(shù)會(huì)返回一個(gè)Promise對(duì)象,當(dāng)里面的異步都執(zhí)行完了就可以then了:

  1. init().then(() => console.log("init finished")); 

ES7的新屬性讓我們的代碼更加地簡(jiǎn)潔優(yōu)雅。

關(guān)于定時(shí)器還有一個(gè)很重要的話(huà)題,那就是setTimeout和setInterval的區(qū)別。如下圖所示:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

setTimeout是在當(dāng)前執(zhí)行單元都執(zhí)行完才開(kāi)始計(jì)時(shí),而setInterval是在設(shè)定完計(jì)時(shí)器后就立馬計(jì)時(shí)??梢杂靡粋€(gè)實(shí)際的例子做說(shuō)明,這個(gè)例子我在《JS與多線程》這篇文章里面提到過(guò),這里用代碼實(shí)際地運(yùn)行一下,如下代碼所示: 

  1. let scriptBegin = Date.now(); 
  2. fun1(); 
  3. fun2(); 
  4.   
  5. // 需要執(zhí)行20ms的工作單元 
  6. function act(functionName) { 
  7.     console.log(functionName, Date.now() - scriptBegin); 
  8.     let begin = Date.now(); 
  9.     while(Date.now() - begin < 20); 
  10. function fun1() { 
  11.     let fun3 = () => act("fun3"); 
  12.     setTimeout(fun3, 0); 
  13.     act("fun1"); 
  14. function fun2() { 
  15.     act("fun2 - 1"); 
  16.     var fun4 = () => act("fun4"); 
  17.     setInterval(fun4, 20); 
  18.     act("fun2 - 2"); 

 這個(gè)代碼的執(zhí)行模型是這樣的:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

控制臺(tái)輸出:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

與上面的模型分析一致。

接著再討論***一個(gè)話(huà)題,函數(shù)節(jié)流

7. 函數(shù)節(jié)流throttling

節(jié)流的目的是為了不想觸發(fā)執(zhí)行得太快,如:

  • —監(jiān)聽(tīng)input觸發(fā)搜索
  • —監(jiān)聽(tīng)resize做響應(yīng)式調(diào)整
  • —監(jiān)聽(tīng)mousemove調(diào)整位置

我們先看一下,resize/mousemove事件1s種能觸發(fā)多少次,于是寫(xiě)了以下驅(qū)動(dòng)代碼: 

  1. let begin = 0; 
  2. let count = 0; 
  3. window.onresize = function() { 
  4.     count++; 
  5.     let now = Date.now(); 
  6.     if (!begin) { 
  7.         begin = now; 
  8.         return
  9.     } 
  10.     if((now - begin) % 3000 < 60) { 
  11.         console.log(now - begin
  12.            count / (now - begin) * 1000); 
  13.     } 
  14. }; 

 當(dāng)把窗口拉得比較快的時(shí)候,resize事件大概是1s觸發(fā)40次:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

需要注意的是,并不是說(shuō)你拉得越快,觸發(fā)得就越快。實(shí)際情況是,拉得越快觸發(fā)得越慢,因?yàn)槔瓌?dòng)的時(shí)候頁(yè)面需要重繪,變化得越快,重繪的次數(shù)也就越多,所以導(dǎo)致觸發(fā)得更少了。

mousemove事件在我的電腦的Chrome上1s大概觸發(fā)60次:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

如果你需要監(jiān)聽(tīng)resize事件做DOM調(diào)整的話(huà),這個(gè)調(diào)整比較費(fèi)時(shí),1s要調(diào)整40次,這樣可能會(huì)響應(yīng)不過(guò)來(lái),并且不需要調(diào)整得這么頻繁,所以要節(jié)流。

怎么實(shí)現(xiàn)一個(gè)節(jié)流呢,書(shū)里是這么實(shí)現(xiàn)的: 

  1. function throttle(method, context) { 
  2.     clearTimeout(method.tId); 
  3.     method.tId = setTimeout(function() { 
  4.         method.call(context); 
  5.     }, 100); 

 每次執(zhí)行都要setTimeout一下,如果觸發(fā)得很快就把上一次的setTimeout清掉重新setTimeout,這樣就不會(huì)執(zhí)行很快了。但是這樣有個(gè)問(wèn)題,就是這個(gè)回調(diào)函數(shù)可能永遠(yuǎn)不會(huì)執(zhí)行,因?yàn)樗恢痹谟|發(fā),一直在清掉tId,這樣就有點(diǎn)尷尬,上面代碼的本意應(yīng)該是100ms內(nèi)最多觸發(fā)一次,而實(shí)際情況是可能永遠(yuǎn)不會(huì)執(zhí)行。

把上面的代碼稍微改造一下: 

  1. function throttle(method, context) { 
  2.     if (method.tId) { 
  3.         return
  4.     } 
  5.     method.tId = setTimeout(function() { 
  6.         method.call(context); 
  7.         method.tId = 0; 
  8.     }, 100); 

這個(gè)實(shí)現(xiàn)就是正確的,每100ms最多執(zhí)行一次回調(diào),原理是在setTimeout里面把tId給置成0,這樣能讓下一次的觸發(fā)執(zhí)行。實(shí)際實(shí)驗(yàn)一下:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

大概每100ms就執(zhí)行一次,這樣就達(dá)到我們的目的。

但是這樣有一個(gè)小問(wèn)題,就是每次執(zhí)行都是要延遲100ms,有時(shí)候用戶(hù)可能就是***化了窗口,只觸發(fā)了一次resize事件,但是這次還是得延遲100ms才能執(zhí)行,假設(shè)你的時(shí)間是500ms,那就得延遲半秒,因此這個(gè)實(shí)現(xiàn)不太理想。

需要優(yōu)化,如下代碼所示: 

  1. function throttle(method, context) { 
  2.     // 如果是***次觸發(fā),立刻執(zhí)行 
  3.     if (typeof method.tId === "undefined") { 
  4.         method.call(context); 
  5.     } 
  6.     if (method.tId) { 
  7.         return
  8.     } 
  9.     method.tId = setTimeout(function() { 
  10.         method.call(context); 
  11.         method.tId = 0; 
  12.     }, 100); 

先判斷是否為***次觸發(fā),如果是的話(huà)立刻執(zhí)行。這樣就解決了上面提到的問(wèn)題,但是這個(gè)實(shí)現(xiàn)還是有問(wèn)題,因?yàn)樗皇侨值?**次,用戶(hù)***化之后,隔了一會(huì)又取消***化了就又有延遲了,并且***次觸發(fā)會(huì)執(zhí)行兩次。那怎么辦呢?

筆者想到了一個(gè)方法: 

  1. function throttle(method, context) { 
  2.     if (!method.tId) { 
  3.         method.call(context); 
  4.         method.tId = 1; 
  5.         setTimeout(() => method.tId = 0, 100); 
  6.     } 

每次觸發(fā)的時(shí)候立刻執(zhí)行,然后再設(shè)定一個(gè)計(jì)時(shí)器,把tId置成0,實(shí)際的效果如下:

JS高級(jí)程序設(shè)計(jì)高級(jí)技巧

這個(gè)實(shí)現(xiàn)比之前的實(shí)現(xiàn)還要簡(jiǎn)潔,并且能夠解決延遲的問(wèn)題。

—所以通過(guò)節(jié)流,把執(zhí)行次數(shù)降到了1s執(zhí)行10次,節(jié)流時(shí)間也可以控制,但同時(shí)失去了靈敏度,如果你需要高靈敏度就不應(yīng)該使用節(jié)流,例如做一個(gè)拖拽的應(yīng)用。如果拖拽節(jié)流了會(huì)怎么樣?用戶(hù)會(huì)發(fā)現(xiàn)拖起來(lái)一卡一卡的。

筆者重新看了高程的《高級(jí)技巧》的章節(jié)結(jié)合自己的理解和實(shí)踐總結(jié)了這么一篇文章,我的體會(huì)是如果看書(shū)看博客只是當(dāng)作睡前讀物看一看其實(shí)收獲不是很大,沒(méi)有實(shí)際地把書(shū)里的代碼實(shí)踐一下,沒(méi)有結(jié)合自己的編碼經(jīng)驗(yàn),就不能用自己的理解去融入這個(gè)知識(shí)點(diǎn),從而轉(zhuǎn)化為自己的知識(shí)。你可能會(huì)說(shuō)我看了之后就會(huì)印象啊,有印象還是好的,但是你花了那么多時(shí)間看了那本書(shū)只是得到了一個(gè)印象,你自己都沒(méi)有實(shí)踐過(guò)的印象,這個(gè)印象又有多靠譜呢。如果別人問(wèn)到了這個(gè)印象,你可能會(huì)回答出一些連不起來(lái)的碎片,就會(huì)給人一種背書(shū)的感覺(jué)。還有有時(shí)候書(shū)里可能會(huì)有一些錯(cuò)誤或者過(guò)時(shí)的東西,只有實(shí)踐了才能出真知。

 

責(zé)任編輯:未麗燕 來(lái)源: 人人網(wǎng)FED Team
相關(guān)推薦

2010-04-13 14:59:20

Unix操作系統(tǒng)

2024-01-15 17:26:26

JavaScriptWeb開(kāi)發(fā)

2011-08-01 11:56:45

Google搜索

2013-12-18 10:34:42

OpenMP線程

2013-12-12 16:30:20

Lua腳本語(yǔ)言

2009-02-25 14:51:05

應(yīng)用程序設(shè)計(jì)ASP.NET.NET

2021-08-04 09:32:05

Typescript 技巧Partial

2014-08-01 12:57:31

linuxheartbeatlvs

2014-08-14 15:38:33

linux集群

2023-03-13 16:08:00

JavaScript數(shù)組函數(shù)

2024-05-24 08:04:12

技巧管理器數(shù)據(jù)庫(kù)

2012-11-27 17:41:16

2014-07-28 10:27:37

linux集群

2009-12-04 10:53:06

VS WEB

2010-12-28 10:12:39

PHP

2014-04-04 13:48:04

2022-11-07 16:06:15

TypeScript開(kāi)發(fā)技巧

2010-01-11 10:41:05

C++編程

2023-09-04 15:48:05

JavaScript語(yǔ)言

2009-12-25 16:36:45

WPF程序設(shè)計(jì)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)