<font id="zqva1"></font>
<rt id="zqva1"></rt>
  • <tt id="zqva1"></tt>
    <cite id="zqva1"></cite>

    <cite id="zqva1"><noscript id="zqva1"></noscript></cite>
      <rp id="zqva1"><meter id="zqva1"></meter></rp>

        <cite id="zqva1"></cite>
          <b id="zqva1"></b>
          <rp id="zqva1"></rp>
          <cite id="zqva1"></cite>

          <rt id="zqva1"></rt>

        1. <rp id="zqva1"></rp>

          JavaScript 優雅的實現方式包含你可能不知道的知識點【轉】

          時間:?2017-12-26閱讀:?988標簽:?js知識作者:?轉載

          有些東西很好用,但是你未必知道;有些東西你可能用過,但是你未必知道原理。

          實現一個目的有多種途徑,俗話說,條條大路通羅馬。很多內容來自平時的一些收集以及過往博客文章底下的精彩評論,收集整理拓展一波,發散一下大家的思維以及拓展一下知識面。

          茴字有四種寫法,233333..., 文末有彩蛋有驚喜。

          1、簡短優雅地實現 sleep 函數

          很多語言都有 sleep 函數,顯然 js 沒有,那么如何能簡短優雅地實現這個方法?

          1.1 普通版

          function sleep(sleepTime) {
          	for(var start = +new Date; +new Date - start <= sleepTime;) {}
          }
          var t1 = +new Date()
          sleep(3000)
          var t2 = +new Date()
          console.log(t2 - t1)

          優點:簡單粗暴,通俗易懂。
          缺點:這是最簡單粗暴的實現,確實 sleep 了,也確實卡死了,CPU 會飆升,無論你的服務器 CPU 有多么 Niubility。

          1.2 Promise 版本

          function sleep(time) {
            return new Promise(resolve => setTimeout(resolve, time))
          }
          
          const t1 = +new Date()
          sleep(3000).then(() => {
            const t2 = +new Date()
            console.log(t2 - t1)
          })

          優點:這種方式實際上是用了 setTimeout,沒有形成進程阻塞,不會造成性能和負載問題。
          缺點:雖然不像 callback 套那么多層,但仍不怎么美觀,而且當我們需要在某過程中需要停止執行(或者在中途返回了錯誤的值),還必須得層層判斷后跳出,非常麻煩,而且這種異步并不是那么徹底,還是看起來別扭。

          1.3 Generator 版本

          function sleep(delay) {
            return function(cb) {
              setTimeout(cb.bind(this), delay)
            };
          }
          
          function* genSleep() {
            const t1 = +new Date()
            yield sleep(3000)
            const t2 = +new Date()
            console.log(t2 - t1)
          }
          
          async(genSleep)
          
          function async(gen) {
            const iter = gen()
            function nextStep(it) {
              if (it.done) return
              if (typeof it.value === "function") {
                it.value(function(ret) {
                  nextStep(iter.next(ret))
                })
              } else {
                nextStep(iter.next(it.value))
              }
            }
            nextStep(iter.next())
          }

          優點:同 Promise 優點,另外代碼就變得非常簡單干凈,沒有 then 那么生硬和惡心。
          缺點:但不足也很明顯,就是每次都要執行 next() 顯得很麻煩,雖然有 (第三方包)可以解決,但就多包了一層,不好看,錯誤也必須按co的邏輯來處理,不爽。

          co之所以這么火并不是沒有原因的,當然不是僅僅實現 sleep 這么無聊的事情,而是它活生生的借著generator/yield 實現了很類似 async/await 的效果!這一點真是讓我三觀盡毀刮目相看。

          const co = require("co")
          function sleep(delay) {
            return function(cb) {
              setTimeout(cb.bind(this), delay)
            }
          }
          
          co(function*() {
            const t1 = +new Date()
            yield sleep(3000)
            const t2 = +new Date()
            console.log(t2 - t1)
          })

          1.4 Async/Await 版本

          function sleep(delay) {
            return new Promise(reslove => {
              setTimeout(reslove, delay)
            })
          }
          
          !async function test() {
            const t1 = +new Date()
            await sleep(3000)
            const t2 = +new Date()
            console.log(t2 - t1)
          }()

          優點:同 Promise 和 Generator 優點。 Async/Await 可以看做是 Generator 的語法糖,Async 和 Await 相較于 * 和 yield 更加語義,另外各個函數都是扁平的,不會產生多余的嵌套,代碼更加清爽易讀。
          缺點: ES7 語法存在兼容性問題,有 babel 一切兼容性都不是問題

          至于 Async/Await 比 Promise 和 Generator 的好處可以參考這兩篇文章:
          Async/Await 比 Generator 的四個改進點
          關于Async/Await替代Promise的6個理由

          1.5 不要忘了開源的力量

          在 javascript 優雅的寫 sleep 等于如何優雅的不優雅,2333

          這里有 C++ 實現的模塊:https://github.com/ErikDubbelboer/node-sleep

          const sleep = require("sleep")
          
          const t1 = +new Date()
          sleep.msleep(3000)
          const t2 = +new Date()
          console.log(t2 - t1)

          優點:能夠實現更加精細的時間精確度,而且看起來就是真的 sleep 函數,清晰直白。
          缺點:缺點需要安裝這個模塊,^_^,這也許算不上什么缺點。

          從一個間簡簡單單的 sleep 函數我們就就可以管中窺豹,看見 JavaScript 近幾年來不斷快速的發展,不單單是異步編程這一塊,還有各種各樣的新技術和新框架,見證了 JavaScript 的繁榮。

          你可能不知道的前端知識點:Async/Await是目前前端異步書寫最優雅的一種方式

          2、獲取時間戳

          上面第一個用多種方式實現 sleep 函數,我們可以發現代碼有 +new Date()獲取時間戳的用法,這只是其中的一種,下面就說一下其他兩種以及 +new Date()的原理。

          2.1 普通版

          var timestamp=new Date().getTime()

          優點:具有普遍性,大家都用這個
          缺點:目前沒有發現

          2.2 進階版

          var timestamp = (new Date()).valueOf()

          valueOf 方法返回對象的原始值(Primitive,'Null','Undefined','String','Boolean','Number'五種基本數據類型之一),可能是字符串、數值或 bool 值等,看具體的對象。

          優點:說明開發者原始值有一個具體的認知,讓人眼前一亮。
          缺點: 目前沒有發現

          2.3 終極版

          var timestamp = +new Date()

          優點:對 JavaScript 隱式轉換掌握的比較牢固的一個表現
          缺點:目前沒有發現

          現在就簡單分析一下為什么 +new Date() 拿到的是時間戳。

          一言以蔽之,這是隱式轉換的玄學,實質還是調用了 valueOf() 的方法。

          我們先看看 ECMAScript 規范對一元運算符的規范:

          一元+ 運算符

          一元 + 運算符將其操作數轉換為 Number 類型并反轉其正負。注意負的 +0 產生 -0,負的 -0 產生 +0。產生式 UnaryExpression : - UnaryExpression 按照下面的過程執行。

          1. 令 expr 為解釋執行 UnaryExpression 的結果 .
          2. 令 oldValue 為 ToNumber(GetValue(expr)).
          3. 如果 oldValue is NaN ,return NaN.
          4. 返回 oldValue 取負(即,算出一個數字相同但是符號相反的值)的結果。

          +new Date() 相當于 ToNumber(new Date())

          我們再來看看 ECMAScript 規范對 ToNumber 的定義:


          我們知道 new Date() 是個對象,滿足上面的 ToPrimitive(),所以進而成了 ToPrimitive(new Date())。

          接著我們再來看看 ECMAScript 規范對 ToPrimitive 的定義,一層一層來,抽絲剝繭。


          這個 ToPrimitive 剛開始可能不太好懂,我來大致解釋一下吧:

          ToPrimitive(obj,preferredType)

          JavaScript 引擎內部轉換為原始值 ToPrimitive(obj,preferredType) 函數接受兩個參數,第一個 obj 為被轉換的對象,第二個preferredType 為希望轉換成的類型(默認為空,接受的值為 Number 或 String)

          在執行 ToPrimitive(obj,preferredType) 時如果第二個參數為空并且 obj 為 Date 的實例時,此時 preferredType 會被設置為 String,其他情況下 preferredType 都會被設置為Number 如果 preferredType 為 Number,ToPrimitive 執行過程如下:

          1. 如果obj為原始值,直接返回;
          2. 否則調用 obj.valueOf(),如果執行結果是原始值,返回之;
          3. 否則調用 obj.toString(),如果執行結果是原始值,返回之;
          4. 否則拋異常。

          如果 preferredType 為 String,將上面的第2步和第3步調換,即:

          1. 如果obj為原始值,直接返回;
          2. 否則調用 obj.toString(),如果執行結果是原始值,返回之;
          3. 否則調用 obj.valueOf(),如果執行結果是原始值,返回之;
          4. 否則拋異常。

          首先我們要明白 obj.valueOf() 和 obj.toString() 還有原始值分別是什么意思,這是弄懂上面描述的前提之一:

          toString 用來返回對象的字符串表示。

          var obj = {};
          console.log(obj.toString());//[object Object]
          
          var arr2 = [];
          console.log(arr2.toString());//""空字符串
          
          var date = new Date();
          console.log(date.toString());//Sun Feb 28 2016 13:40:36 GMT+0800 (中國標準時間)

          valueOf 方法返回對象的原始值,可能是字符串、數值或 bool 值等,看具體的對象。

          var obj = {
            name: "obj"
          }
          console.log(obj.valueOf()) //Object {name: "obj"}
          
          var arr1 = [1]
          console.log(arr1.valueOf()) //[1]
          
          var date = new Date()
          console.log(date.valueOf())//1456638436303
          如代碼所示,三個不同的對象實例調用valueOf返回不同的數據

          原始值指的是 'Null','Undefined','String','Boolean','Number','Symbol' 6種基本數據類型之一,上面已經提到過這個概念,這里再次申明一下。

          最后分解一下其中的過程:+new Date():

          1. 運算符 new 的優先級高于一元運算符 +,所以過程可以分解為:
            var time=new Date();
            +time
          2. 根據上面提到的規則相當于:ToNumber(time)
          3. time 是個日期對象,根據 ToNumber 的轉換規則,所以相當于:ToNumber(ToPrimitive(time))
          4. 根據 ToPrimitive 的轉換規則:ToNumber(time.valueOf()),time.valueOf() 就是 原始值 得到的是個時間戳,假設 time.valueOf()=1503479124652
          5. 所以 ToNumber(1503479124652) 返回值是 1503479124652 這個數字。
          6. 分析完畢

          你可能不知道的前端知識點:隱式轉換的妙用

          3、數組去重

          注:暫不考慮對象字面量,函數等引用類型的去重,也不考慮 NaN, undefined, null等特殊類型情況。

          數組樣本:[1, 1, '1', '2', 1]

          3.1 普通版

          無需思考,我們可以得到 O(n^2) 復雜度的解法。定義一個變量數組 res 保存結果,遍歷需要去重的數組,如果該元素已經存在在 res 中了,則說明是重復的元素,如果沒有,則放入 res 中。

          var a = [1, 1, '1', '2', 1]
          function unique(arr) {
              var res = []
              for (var i = 0, len = arr.length; i < len; i++) {
                  var item = arr[i]
                  for (var j = 0, len = res.length; j < jlen; j++) {
                      if (item === res[j]) //arr數組的item在res已經存在,就跳出循環
                          break
                  }
                  if (j === jlen) //循環完畢,arr數組的item在res找不到,就push到res數組中
                      res.push(item)
              }
              return res
          }
          console.log(unique(a)) // [1, 2, "1"]

          優點: 沒有任何兼容性問題,通俗易懂,沒有任何理解成本
          缺點: 看起來比較臃腫比較繁瑣,時間復雜度比較高O(n^2)

          3.2 進階版

          var a =  [1, 1, '1', '2', 1]
          function unique(arr) {
              return arr.filter(function(ele,index,array){
                  return array.indexOf(ele) === index//很巧妙,這樣篩選一對一的,過濾掉重復的
              })
          }
          console.log(unique(a)) // [1, 2, "1"]

          優點:很簡潔,思維也比較巧妙,直觀易懂。
          缺點:不支持 IE9 以下的瀏覽器,時間復雜度還是O(n^2)

          3.3 時間復雜度為O(n)

          var a =  [1, 1, '1', '2', 1]
          function unique(arr) {
              var obj = {}
              return arr.filter(function(item, index, array){
                  return obj.hasOwnProperty(typeof item + item) ? 
                  false : 
                  (obj[typeof item + item] = true)
              })
          }
          
          console.log(unique(a)) // [1, 2, "1"]

          優點:hasOwnProperty 是對象的屬性(名稱)存在性檢查方法。對象的屬性可以基于 Hash 表實現,因此對屬性進行訪問的時間復雜度可以達到O(1);
          filter 是數組迭代的方法,內部還是一個 for 循環,所以時間復雜度是 O(n)。
          缺點:不兼容 IE9 以下瀏覽器,其實也好解決,把 filter 方法用 for 循環代替或者自己模擬一個 filter 方法。

          3.4 終極版

          以 Set 為例,ES6 提供了新的數據結構 Set。它類似于數組,但是成員的值都是唯一的,沒有重復的值。

          const unique = a => [...new Set(a)]

          優點:ES6 語法,簡潔高效,我們可以看到,去重方法從原始的 14 行代碼到 ES6 的 1 行代碼,其實也說明了 JavaScript 這門語言在不停的進步,相信以后的開發也會越來越高效。
          缺點:兼容性問題,現代瀏覽器才支持,有 babel 這些都不是問題。

          你可能不知道的前端知識點:ES6 新的數據結構 Set 去重

          4、數字格式化 1234567890 --> 1,234,567,890

          4.1 普通版

          function formatNumber(str) {
            let arr = [],
              count = str.length
          
            while (count >= 3) {
              arr.unshift(str.slice(count - 3, count))
              count -= 3
            }
          
            // 如果是不是3的倍數就另外追加到上去
            str.length % 3 && arr.unshift(str.slice(0, str.length % 3))
          
            return arr.toString()
          
          }
          console.log(formatNumber("1234567890")) // 1,234,567,890

          優點:自我感覺比網上寫的一堆 for循環 還有 if-else 判斷的邏輯更加清晰直白。
          缺點:太普通

          4.2 進階版

          function formatNumber(str) {
          
            // ["0", "9", "8", "7", "6", "5", "4", "3", "2", "1"]
            return str.split("").reverse().reduce((prev, next, index) => {
              return ((index % 3) ? next : (next + ',')) + prev
            })
          }
          
          console.log(formatNumber("1234567890")) // 1,234,567,890

          優點:把 JS 的 API 玩的了如指掌
          缺點:可能沒那么好懂,不過讀懂之后就會發出我怎么沒想到的感覺

          4.3 正則版

          function formatNumber(str) {
            return str.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
          }
          
          console.log(formatNumber("123456789")) // 1,234,567,890

          下面簡單分析下正則/\B(?=(\d{3})+(?!\d))/g:

          1. /\B(?=(\d{3})+(?!\d))/g:正則匹配邊界\B,邊界后面必須跟著(\d{3})+(?!\d);
          2. (\d{3})+:必須是1個或多個的3個連續數字;
          3. (?!\d):第2步中的3個數字不允許后面跟著數字;
          4. (\d{3})+(?!\d):所以匹配的邊界后面必須跟著3*n(n>=1)的數字。

          最終把匹配到的所有邊界換成,即可達成目標。

          優點:代碼少,濃縮的就是精華
          缺點:需要對正則表達式的位置匹配有一個較深的認識,門檻大一點

          4.4 API版

          (123456789).toLocaleString('en-US')  // 1,234,567,890

          如圖,你可能還不知道 JavaScript 的 toLocaleString 還可以這么玩。


          還可以使用 Intl對象 - MDN

          Intl 對象是 ECMAScript 國際化 API 的一個命名空間,它提供了精確的字符串對比,數字格式化,日期和時間格式化。Collator,NumberFormat 和 DateTimeFormat 對象的構造函數是 Intl 對象的屬性。

          new Intl.NumberFormat().format(1234567890) // 1,234,567,890

          優點:簡單粗暴,直接調用 API
          缺點:Intl兼容性不太好,不過 toLocaleString的話 IE6 都支持

          你可能不知道的前端知識點:Intl對象 和 toLocaleString的方法。

          5、交換兩個整數

          let a = 3,b = 4 變成 a = 4, b = 3

          5.1 普通版

          首先最常規的辦法,引入一個 temp 中間變量

          let a = 3,b = 4
          let temp = a
          a = b
          b = temp
          console.log(a, b)

          優點:一眼能看懂就是最好的優點
          缺點:硬說缺點就是引入了一個多余的變量

          5.2 進階版

          在不引入中間變量的情況下也能交互兩個變量

          let a = 3,b = 4
          a += b
          b = a - b
          a -= b
          console.log(a, b)

          優點:比起樓上那種沒有引入多余的變量,比上面那一種稍微巧妙一點
          缺點:當然缺點也很明顯,整型數據溢出,比如說對于32位字符最大表示有符號數字是2147483647,也就是Math.pow(2,31)-1,如果是2147483645和2147483646交換就失敗了。

          5.3 終極版

          利用一個數異或本身等于0和異或運算符合交換率。

          let a = 3,b = 4
            a ^= b
            b ^= a
            a ^= b
          console.log(a, b)

          下面用豎式進行簡單說明:(10進制化為二進制)

              a = 011
          (^) b = 100
          則  a = 111(a ^ b的結果賦值給a,a已變成了7)
          (^) b = 100
          則  b = 011(b^a的結果賦給b,b已經變成了3)
          (^) a = 111
          則  a = 100(a^b的結果賦給a,a已經變成了4)
          

          從上面的豎式可以清楚的看到利用異或運算實現兩個值交換的基本過程。

          下面從深層次剖析一下:

          1.對于開始的兩個賦值語句,a = a ^ b,b = b ^ a,相當于b = b ^ (a ^ b) = a ^ b ^ b,而b ^ b 顯然等于0。因此b = a ^ 0,顯然結果為a。
          2. 同理可以分析第三個賦值語句,a = a ^ b = (a ^ b) ^ a = b

          注:

          1. ^ 即”異或“運算符。

          它的意思是判斷兩個相應的位值是否為”異“,為”異"(值不同)就取真(1);否則為假(0)。

          1. ^ 運算符的特點是與0異或,保持原值;與本身異或,結果為0。

          優點:不存在引入中間變量,不存在整數溢出
          缺點:前端對位操作這一塊可能了解不深,不容易理解

          5.4 究極版

          熟悉 ES6 語法的人當然不會對解構陌生

          var a = 3,b = 4;
          [b, a] = [a, b]

          其中的解構的原理,我暫時還沒讀過 ES6的規范,不知道具體的細則,不過我們可以看看 babel 是自己編譯的,我們可以看出點門路。

          哈哈,簡單粗暴,不知道有沒有按照 ES 的規范,其實可以扒一扒 v8的源碼,chrome 已經實現這種解構用法。


          這個例子和前面的例子編寫風格有何不同,你如果細心的話就會發現這兩行代碼多了一個分號,對于我這種編碼不寫分號的潔癖者,為什么加一個分號在這里,其實是有原因的,這里就簡單普及一下,建議大家還是寫代碼寫上分號

          5.4 ECMAScript 自動分號;插入(作為補充,防止大家以后踩坑)

          盡管 JavaScript 有 C 的代碼風格,但是它不強制要求在代碼中使用分號,實際上可以省略它們。

          JavaScript 不是一個沒有分號的語言,恰恰相反上它需要分號來就解析源代碼。 因此 JavaScript 解析器在遇到由于缺少分號導致的解析錯誤時,會自動在源代碼中插入分號。

          5.4.1例子
          var foo = function() {
          } // 解析錯誤,分號丟失
          test()

          自動插入分號,解析器重新解析。

          var foo = function() {
          }; // 沒有錯誤,解析繼續
          test()
          5.4.2工作原理

          下面的代碼沒有分號,因此解析器需要自己判斷需要在哪些地方插入分號。

          (function(window, undefined) {
              function test(options) {
                  log('testing!')
          
                  (options.list || []).forEach(function(i) {
          
                  })
          
                  options.value.test(
                      'long string to pass here',
                      'and another long string to pass'
                  )
          
                  return
                  {
                      foo: function() {}
                  }
              }
              window.test = test
          
          })(window)
          
          (function(window) {
              window.someLibrary = {}
          })(window)
          

          下面是解析器"猜測"的結果。

          (function(window, undefined) {
              function test(options) {
          
                  // 沒有插入分號,兩行被合并為一行
                  log('testing!')(options.list || []).forEach(function(i) {
          
                  }); // <- 插入分號
          
                  options.value.test(
                      'long string to pass here',
                      'and another long string to pass'
                  ); // <- 插入分號
          
                  return; // <- 插入分號, 改變了 return 表達式的行為
                  { // 作為一個代碼段處理
                      foo: function() {}
                  }; // <- 插入分號
              }
              window.test = test; // <- 插入分號
          
          // 兩行又被合并了
          })(window)(function(window) {
              window.someLibrary = {}; // <- 插入分號
          })(window); //<- 插入分號
          

          解析器顯著改變了上面代碼的行為,在另外一些情況下也會做出錯誤的處理。

          5.4.3 ECMAScript對自動分號插入的規則

          我們翻到7.9章節,看看其中插入分號的機制和原理,清楚只寫以后就可以盡量以后少踩坑

          **必須用分號終止某些 ECMAScript 語句 ( 空語句 , 變量聲明語句 , 表達式語句 , do-while 語句 , continue 語句 , break 語句 , return 語句 ,throw 語句 )。這些分號總是明確的顯示在源文本里。然而,為了方便起見,某些情況下這些分號可以在源文本里省略。描述這種情況會說:這種情況下給源代碼的 token 流自動插入分號。**??


          還是比較抽象,看不太懂是不是,不要緊,我們看看實際例子,總結出幾個規律就行,我們先不看抽象的,看著頭暈,看看具體的總結說明, 化抽象為具體 。

          首先這些規則是基于兩點:

          1. 以換行為基礎;
          2. 解析器會盡量將新行并入當前行,當且僅當符合ASI規則時才會將新行視為獨立的語句。
          5.4.3.1 ASI的規則

          1. 新行并入當前行將構成非法語句,自動插入分號。

          if(1 < 10) a = 1
          console.log(a)
          // 等價于
          if(1 < 10) a = 1;
          console.log(a);

          2. 在continue,return,break,throw后自動插入分號

          return
          {a: 1}
          // 等價于
          return;
          {a: 1};

          3. ++、--后綴表達式作為新行的開始,在行首自動插入分號

          a
          ++
          c
          // 等價于
          a;
          ++c;

          4. 代碼塊的最后一個語句會自動插入分號

          function(){ a = 1 }
          // 等價于
          function(){ a = 1; }
          5.4.3.2 No ASI的規則

          1. 新行以 ( 開始

          var a = 1
          var b = a
          (a+b).toString()
          // 會被解析為以a+b為入參調用函數a,然后調用函數返回值的toString函數
          var a = 1
          var b =a(a+b).toString()

          2. 新行以 [ 開始

          var a = ['a1', 'a2']
          var b = a
          [0,1].slice(1)
          // 會被解析先獲取a[1],然后調用a[1].slice(1)。
          // 由于逗號位于[]內,且不被解析為數組字面量,而被解析為運算符,而逗號運算符會先執
          行左側表達式,然后執行右側表達式并且以右側表達式的計算結果作為返回值
          var a = ['a1', 'a2']
          var b = a[0,1].slice(1)

          3. 新行以 / 開始

          var a = 1
          var b = a
          /test/.test(b)
          // /會被解析為整除運算符,而不是正則表達式字面量的起始符號。瀏覽器中會報test前多了個.號
          var a = 1
          var b = a / test / .test(b)
          

          4. 新行以 + 、 - 、 % 和 * 開始

          var a = 2
          var b = a
          +a
          // 會解析如下格式
          var a = 2
          var b = a + a

          5. 新行以 , 或 . 開始

          var a = 2
          var b = a
          .toString()
          console.log(typeof b)
          // 會解析為
          var a = 2
          var b = a.toString()
          console.log(typeof b)

          到這里我們已經對ASI的規則有一定的了解了,另外還有一樣有趣的事情,就是“空語句”。

          // 三個空語句
          ;;;
          
          // 只有if條件語句,語句塊為空語句。
          // 可實現unless條件語句的效果
          if(1>2);else
            console.log('2 is greater than 1 always!');
          
          // 只有while條件語句,循環體為空語句。
          var a = 1
          while(++a < 100);
          5.4.4 結論

          建議絕對不要省略分號,同時也提倡將花括號和相應的表達式放在一行, 對于只有一行代碼的 if 或者 else 表達式,也不應該省略花括號。 這些良好的編程習慣不僅可以提到代碼的一致性,而且可以防止解析器改變代碼行為的錯誤處理。
          ?關于JavaScript 語句后應該加分號么?(點我查看)我們可以看看知乎上大牛們對著個問題的看法。

          你可能不知道的前端知識點:原來 JavaScript 還有位操作以及分號的使用細則

          6、將 argruments 對象(類數組)轉換成數組

          {0:1,1:2,2:3,length:3}這種形式的就屬于類數組,就是按照數組下標排序的對象,還有一個 length屬性,有時候我們需要這種對象能調用數組下的一個方法,這時候就需要把把類數組轉化成真正的數組。

          6.1 普通版

          var makeArray = function(array) {
            var ret = []
            if (array != null) {
              var i = array.length
              if (i == null || typeof array === "string") ret[0] = array
              else while (i) ret[--i] = array[i];
            }
            return ret
          }
          makeArray({0:1,1:2,2:3,length:3}) //[1,2,3]

          優點:通用版本,沒有任何兼容性問題
          缺點:太普通

          6.2 進階版

          var arr = Array.prototype.slice.call(arguments);

          這種應該是大家用過最常用的方法,至于為什么可以這么用,很多人估計也是一知半解,反正我看見大家這么用我也這么用,要搞清為什么里面的原因,我們還是從規范和源碼說起。

          照著規范的流程,自己看看推演一下就明白了:
          英文版15.4.4.10 Array.prototype.slice (start, end) 
          中文版15.4.4.10 Array.prototype.slice (start, end) 
          如果你想知道 JavaScript 的 sort 排序的機制,到底是哪種排序好,用的哪種,也可以從規范看出端倪。

          在官方的解釋中,如[mdn]

          The slice() method returns a shallow copy of a portion of an array into a new array object.

          簡單的說就是根據參數,返回數組的一部分的 copy。所以了解其內部實現才能確定它是如何工作的。所以查看 V8 源碼中的 Array.js 可以看到如下的代碼:

          方法 ArraySlice,源碼地址,第 660 行,直接添加到 Array.prototype 上的“入口”,內部經過參數、類型等等的判斷處理,分支為 SparseSlice 和 SimpleSlice 處理。

          slice.call 的作用原理就是,利用 call,將 slice 的方法作用于 arrayLike,slice 的兩個參數為空,slice 內部解析使得 arguments.lengt 等于0的時候 相當于處理 slice(0) : 即選擇整個數組,slice 方法內部沒有強制判斷必須是 Array 類型,slice 返回的是新建的數組(使用循環取值)”,所以這樣就實現了類數組到數組的轉化,call 這個神奇的方法、slice 的處理缺一不可。

          直接看 slice 怎么實現的吧。其實就是將 array-like 對象通過下標操作放進了新的 Array 里面:

          // This will work for genuine arrays, array-like objects, 
              // NamedNodeMap (attributes, entities, notations),
              // NodeList (e.g., getElementsByTagName), HTMLCollection (e.g., childNodes),
              // and will not fail on other DOM objects (as do DOM elements in IE < 9)
              Array.prototype.slice = function(begin, end) {
                // IE < 9 gets unhappy with an undefined end argument
                end = (typeof end !== 'undefined') ? end : this.length;
          
                // For native Array objects, we use the native slice function
                if (Object.prototype.toString.call(this) === '[object Array]'){
                  return _slice.call(this, begin, end); 
                }
          
                // For array like object we handle it ourselves.
                var i, cloned = [],
                  size, len = this.length;
          
                // Handle negative value for "begin"
                var start = begin || 0;
                start = (start >= 0) ? start : Math.max(0, len + start);
          
                // Handle negative value for "end"
                var upTo = (typeof end == 'number') ? Math.min(end, len) : len;
                if (end < 0) {
                  upTo = len + end;
                }
          
                // Actual expected size of the slice
                size = upTo - start;
          
                if (size > 0) {
                  cloned = new Array(size);
                  if (this.charAt) {
                    for (i = 0; i < size; i++) {
                      cloned[i] = this.charAt(start + i);
                    }
                  } else {
                    for (i = 0; i < size; i++) {
                      cloned[i] = this[start + i];
                    }
                  }
                }
          
                return cloned;
              };

          優點:最常用的版本,兼容性較強
          缺點:ie 低版本,無法處理 dom 集合的 slice call 轉數組。(雖然具有數值鍵值、length 符合ArrayLike 的定義,卻報錯)搜索資料得到 :因為 ie 下的 dom 對象是以 com 對象的形式實現的,js 對象與com對象不能進行轉換 。

          6.3 ES6 版本

          使用 Array.from, 值需要對象有 length 屬性, 就可以轉換成數組

          var arr = Array.from(arguments);

          擴展運算符

          var args = [...arguments];

          ES6 中的擴展運算符...也能將某些數據結構轉換成數組,這種數據結構必須有便利器接口。
          優點:直接使用內置 API,簡單易維護
          缺點:兼容性,使用 babel 的 profill 轉化可能使代碼變多,文件包變大

          你可能不知道的前端知識點:slice 方法的具體原理

          7、數字取整 2.33333 => 2

          7.1 普通版

          const a = parseInt(2.33333)

          parseInt() 函數解析一個字符串參數,并返回一個指定基數的整數 (數學系統的基礎)。這個估計是直接取整最常用的方法了。
          更多關于 parseInt() 函數可以查看 MDN 文檔

          7.2 進階版

          const a = Math.trunc(2.33333)

          Math.trunc() 方法會將數字的小數部分去掉,只保留整數部分。
          特別要注意的是:Internet Explorer 不支持這個方法,不過寫個 Polyfill 也很簡單:

          Math.trunc = Math.trunc || function(x) {
            if (isNaN(x)) {
              return NaN;
            }
            if (x > 0) {
              return Math.floor(x);
            }
            return Math.ceil(x);
          };

          數學的事情還是用數學方法來處理比較好。

          7.3 黑科技版

          7.3.1 ~~number

          雙波浪線 ~~ 操作符也被稱為“雙按位非”操作符。你通常可以使用它作為代替 Math.trunc() 的更快的方法。

          console.log(~~47.11)  // -> 47
          console.log(~~1.9999) // -> 1
          console.log(~~3)      // -> 3
          console.log(~~[])     // -> 0
          console.log(~~NaN)    // -> 0
          console.log(~~null)   // -> 0

          失敗時返回0,這可能在解決 Math.trunc() 轉換錯誤返回 NaN 時是一個很好的替代。
          但是當數字范圍超出 ±2^31?1 即:2147483647 時,異常就出現了:

          // 異常情況
          console.log(~~2147493647.123) // -> -2147473649 
          站長推薦

          1.阿里云: 本站目前使用的是阿里云主機,安全/可靠/穩定。點擊領取2000元代金券、了解最新阿里云產品的各種優惠活動點擊進入

          2.騰訊云: 提供云服務器、云數據庫、云存儲、視頻與CDN、域名等服務。騰訊云各類產品的最新活動,優惠券領取點擊進入

          3.廣告聯盟: 整理了目前主流的廣告聯盟平臺,如果你有流量,可以作為參考選擇適合你的平臺點擊進入

          鏈接: http://www.modern-decoration.com.cn/article/detial/279

          JavaScript的聲明提升

          在JavaScript中,當出現var聲明的變量或者function聲明的函數時,會將該聲明提到當前作用域的前面執行,這便是聲明提升。值得注意的是,只是提升了聲明操作,賦值還是在原來的位置進行。聲明提升包括變量聲明提升和函數聲明提升。

          base91 for javascript

          原理和 base64 是一樣的,ASCII 共有94個可打印字符,base64 使用了其中 64 個,base91 使用了 91 個。

          javascript中什么是句柄?

          一個句柄是指使用的一個唯一的整數值,即一個4字節(64位程序中為8字節)長的數值,來標識應用程序中的不同對象和同類中的不同的實例。

          javascript不可變性是什么?

          不可變性即某個變量在進行了某個操作之后,其本身沒有發生變化,比如對于字符串而言,對字符串的任何操作都會改變字符串本身的值,而是在字符串的基礎上復制出來一個然后再改變,這樣我們就說是不可變的

          為什么javascript不起作用?

          JavaScript是一種屬于網絡的腳本語言,已經被廣泛用于Web應用開發,常用來為網頁添加各式各樣的動態功能,為用戶提供更流暢美觀的瀏覽效果。通常JavaScript腳本是通過嵌入在HTML中來實現自身的功能的。

          javascript怎么判斷按鈕被點擊?

          JavaScript可以通過Event對象的target事件或srcElement(IE瀏覽器支持)來判斷按鈕是否被點擊。Event對象代表事件的狀態,比如事件在其中發生的元素、鍵盤按鍵的狀態、鼠標的位置、鼠標按鈕的狀態。

          javascript由幾部分組成?

          JavaScript有三部分組成。分別為核心(ECMAScript) 、文檔對象模型(DOM)、瀏覽器對象模型(BOM)。這三部分分別描述了該語言的語法和基本對象、處理網頁內容的方法和接口、與瀏覽器進行交互的方法和接口。

          Js輸出方式有哪些?

          在編寫JavaScript代碼的時候, 一定要記住每一句代碼后面都需要添加一個分號, 并且這個分號必須是英文的分號,我們會發現有時候不寫分號程序也能夠運行, 這里并不是因為不需要分號, 而是瀏覽器自動幫助我們添加了分號

          7個常見的 JavaScript 測驗及解答

          我相信學習新事物并評估我們所知的東西對自己的進步非常有用,可以避免了我們覺得自己的知識過時的情況。在本文中,我將介紹一些常見的 JavaScript 知識。請享用!

          JavaScript中“javascript:void(0) ”是什么意思

          expression 是一個要計算的 Javascript 標準的表達式。表達式外側的圓括號是可選的,鑒于規范化,以及養成好習慣,建議寫上去。當我們使用 void 操作符指定超級鏈接時,表達式會被計算但是不會在當前文檔處裝入任何內容。

          內容以共享、參考、研究為目的,不存在任何商業目的。其版權屬原作者所有,如有侵權或違規,請與小編聯系!情況屬實本人將予以刪除!

          文章投稿關于web前端網站點搜索站長推薦網站地圖站長QQ:522607023

          小程序專欄: 土味情話心理測試腦筋急轉彎幽默笑話段子句子語錄成語大全運營推廣

          国产精品高清视频免费 - 视频 - 在线观看 - 影视资讯 - 唯爱网