久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      你值得了解的JS高級技巧(總結)

      之前的文章《教程篇:如何用JS和API制作天氣Web應用程序(收藏)》中,給大家介紹了如何用JS和API制作天氣Web應用程序的方法。下面本篇文章給大家介紹了解JS高級技巧,有一定的參考價值,有需要的朋友可以參考一下,希望對你們有所助。

      你值得了解的JS高級技巧(總結)

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

      1. 安全的類型檢測

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

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

      但是上面的判斷在一定條件下會失敗——就是在iframe里面判斷一個父窗口的變量的時候。寫個demo驗證一下,如下主頁面的main.html

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

      iframe.html判斷一下父窗口的變量類型:

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

      iframe里面使用window.parent得到父窗口的全局window對象,這個不管跨不跨域都沒有問題,進而可以得到父窗口的變量,然后用instanceof判斷。最后運行結果如下:

      你值得了解的JS高級技巧(總結)

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

      你值得了解的JS高級技巧(總結)

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

      Array !== window.parent.Array

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

      那怎么辦呢?

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

      你值得了解的JS高級技巧(總結)

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

      ES5函數(shù)地址:https://262.ecma-international.org/5.1/#sec-15.2.4.2

      你值得了解的JS高級技巧(總結)

      也就是說這個函數(shù)的返回值是“[object”開頭,后面帶上變量類型的名稱和右括號。因此既然它是一個標準語法規(guī)范,所以可以用這個函數(shù)安全地判斷變量是不是數(shù)組。

      可以這么寫:

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

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

      有一個比較有趣的現(xiàn)象是ES6class也是返回function

      你值得了解的JS高級技巧(總結)

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

      那是不是說不能再使用instanceof判斷變量類型了?不是的,當你需要檢測父頁面的變量類型就得使用這種方法,本頁面的變量還是可以使用instanceof或者constructor的方法判斷,只要你能確保這個變量不會跨頁面。因為對于大多數(shù)人來說,很少會寫iframe的代碼,所以沒有必要搞一個比較麻煩的方式,還是用簡單的方式就好了。

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

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

      //UA的類型 getUAType: function() {     let ua = window.navigator.userAgent;     if (ua.match(/renren/i)) {         return 0;     }     else if (ua.match(/MicroMessenger/i)) {         return 1;     }     else if (ua.match(/weibo/i)) {         return 2;     }     return -1; }

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

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

      //UA的類型 getUAType: function() {     let ua = window.navigator.userAgent;     if(ua.match(/renren/i)) {         pageData.getUAType = () => 0;         return 0;     }     else if(ua.match(/MicroMessenger/i)) {         pageData.getUAType = () => 1;         return 1;     }     else if(ua.match(/weibo/i)) {         pageData.getUAType = () => 2;         return 2;     }     return -1; }

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

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

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

      連函數(shù)都不用寫了,缺點是即使沒有使用到UAType這個變量,也會執(zhí)行一次判斷,但是我們認為這個變量被用到的概率還是很高的。

      我們再舉一個比較有用的例子,由于Safari的無痕瀏覽會禁掉本地存儲,因此需要搞一個兼容性判斷:

      Data.localStorageEnabled = true; // Safari的無痕瀏覽會禁用localStorage try{     window.localStorage.trySetData = 1; } catch(e) {     Data.localStorageEnabled = false; } setLocalData: function(key, value) {      if (Data.localStorageEnabled) {         window.localStorage[key] = value;     }     else {            util.setCookie("_L_" + key, value, 1000);     } }

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

      setLocalData: function(key, value) {     if(Data.localStorageEnabled) {         util.setLocalData = function(key, value){             return window.localStorage[key];         }     } else {         util.setLocalData = function(key, value){             return util.getCookie("_L_" + key);         }     }     return util.setLocalData(key, value); }

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

      3. 函數(shù)綁定

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

      class DrawTool {     constructor() {         this.points = [];     }     handleMouseClick(event) {         this.points.push(event.latLng);     }     init() {         $map.on('click', this.handleMouseClick);     } }

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

      class DrawTool {     constructor() {         this.points = [];     }     handleMouseClick(event) {         this.points.push(event.latLng);     }     init() {         let that = this;         $map.on('click', event => that.handleMouseClick(event));     } }

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

      因為我們用了箭頭函數(shù),而箭頭函數(shù)的this還是指向父級的上下文,因此這里不用自己創(chuàng)建一個閉包,直接用this就可以:

      init() {    $map.on('click',             event => this.handleMouseClick(event));}復制代碼 這種方式更加簡單,第二種方法是使用ES5的bind函數(shù)綁定,如下代碼: init() {    $map.on('click',             this.handleMouseClick.bind(this));}

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

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

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

      4. 柯里化

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

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

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

      Function.prototype.curry = function() {     let defaultArgs = arguments;     let that = this;     return function() {         return that.apply(this,                            defaultArgs.concat(arguments));    }};

      但是由于參數(shù)不是一個數(shù)組,沒有concat函數(shù),所以需要把偽數(shù)組轉成一個偽數(shù)組,可以用Array.prototype.slice

      Function.prototype.curry = function() {     let slice = Array.prototype.slice;     let defaultArgs = slice.call(arguments);     let that = this;     return function() {         return that.apply(this,                            defaultArgs.concat(slice.call(arguments)));    }};

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

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

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

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

      這樣就方便多了:

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

      5. 防止篡改對象

      有時候你可能怕你的對象被誤改了,所以需要把它保護起來。

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

      如下代碼,當把一個對象seal之后,將不能添加和刪除屬性:

      你值得了解的JS高級技巧(總結)

      當使用嚴格模式將會拋異常:

      你值得了解的JS高級技巧(總結)

      (2)Object.freeze凍結對象

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

      你值得了解的JS高級技巧(總結)

      同時可以使用Object.isFrozen、Object.isSealed、Object.isExtensible判斷當前對象的狀態(tài)。

      (3)defineProperty凍結單個屬性

      如下圖所示,設置enumable/writablefalse,那么這個屬性將不可遍歷和寫:

      你值得了解的JS高級技巧(總結)

      6. 定時器

      怎么實現(xiàn)一個JS版的sleep函數(shù)?因為在C/C++/Java等語言是有sleep函數(shù),但是JS沒有。sleep函數(shù)的作用是讓線程進入休眠,當?shù)搅酥付〞r間后再重新喚起。你不能寫個while循環(huán)然后不斷地判斷當前時間和開始時間的差值是不是到了指定時間了,因為這樣會占用CPU,就不是休眠了。

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

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

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

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

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

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

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

      function sleep(millionSeconds) {     return new Promise(resolve =>                             setTimeout(resolve, millionSeconds)); } async function init() {     await sleep(2000);     console.log("sleep recover"); } init();

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

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

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

      關于定時器還有一個很重要的話題,那就是setTimeoutsetInterval的區(qū)別。如下圖所示:

      你值得了解的JS高級技巧(總結)

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

      let scriptBegin = Date.now(); fun1(); fun2(); // 需要執(zhí)行20ms的工作單元 function act(functionName) {     console.log(functionName, Date.now() - scriptBegin);     let begin = Date.now();     while(Date.now() - begin < 20); } function fun1() {     let fun3 = () => act("fun3");     setTimeout(fun3, 0);     act("fun1"); } function fun2() {     act("fun2 - 1");     var fun4 = () => act("fun4");     setInterval(fun4, 20);     act("fun2 - 2"); }

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

      你值得了解的JS高級技巧(總結)

      控制臺輸出:

      你值得了解的JS高級技巧(總結)

      與上面的模型分析一致。

      接著再討論最后一個話題,函數(shù)節(jié)流

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

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

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

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

      let begin = 0; let count = 0; window.onresize = function() {     count++;     let now = Date.now();     if (!begin) {         begin = now;         return;     }     if((now - begin) % 3000 < 60) {         console.log(now - begin,            count / (now - begin) * 1000);     } };

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

      你值得了解的JS高級技巧(總結)

      需要注意的是,并不是說你拉得越快,觸發(fā)得就越快。實際情況是,拉得越快觸發(fā)得越慢,因為拉動的時候頁面需要重繪,變化得越快,重繪的次數(shù)也就越多,所以導致觸發(fā)得更少了。

      mousemove事件在我的電腦的Chrome1s大概觸發(fā)60次:

      你值得了解的JS高級技巧(總結)

      如果你需要監(jiān)聽resize事件做DOM調(diào)整的話,這個調(diào)整比較費時,1s要調(diào)整40次,這樣可能會響應不過來,并且不需要調(diào)整得這么頻繁,所以要節(jié)流。

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

      function throttle(method, context) {     clearTimeout(method.tId);     method.tId = setTimeout(function() {         method.call(context);     }, 100); }

      每次執(zhí)行都要setTimeout一下,如果觸發(fā)得很快就把上一次的setTimeout清掉重新setTimeout,這樣就不會執(zhí)行很快了。但是這樣有個問題,就是這個回調(diào)函數(shù)可能永遠不會執(zhí)行,因為它一直在觸發(fā),一直在清掉tId,這樣就有點尷尬,上面代碼的本意應該是100ms內(nèi)最多觸發(fā)一次,而實際情況是可能永遠不會執(zhí)行。這種實現(xiàn)應該叫防抖,不是節(jié)流。

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

      function throttle(method, context) {     if (method.tId) {         return;     }     method.tId = setTimeout(function() {         method.call(context);         method.tId = 0;     }, 100); }

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

      你值得了解的JS高級技巧(總結)

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

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

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

      function throttle(method, context) {     // 如果是第一次觸發(fā),立刻執(zhí)行     if (typeof method.tId === "undefined") {         method.call(context);     }     if (method.tId) {         return;     }     method.tId = setTimeout(function() {         method.call(context);         method.tId = 0;     }, 100); }

      先判斷是否為第一次觸發(fā),如果是的話立刻執(zhí)行。這樣就解決了上面提到的問題,但是這個實現(xiàn)還是有問題,因為它只是全局的第一次,用戶最大化之后,隔了一會又取消最大化了就又有延遲了,并且第一次觸發(fā)會執(zhí)行兩次。那怎么辦呢?

      筆者想到了一個方法:

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

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

      你值得了解的JS高級技巧(總結)

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

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

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

      推薦學習:JS視頻教程

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號