<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          這些JavaScript 細節(jié),你未必知道

          共 14265字,需瀏覽 29分鐘

           ·

          2021-11-15 11:54

          大廠技術(shù)  高級前端  Node進階

          點擊上方 程序員成長指北,關(guān)注公眾號

          回復(fù)1,加入高級Node交流群

          前言

          本文主要給大家?guī)硪恍┪易x《你不知道的 JavaScript(中卷)》中遇到的一些有意思的內(nèi)容,可以說是打開新世界的大門的感覺。希望能在工作之余,給大家?guī)硪稽c樂趣。

          JavaScript 是一門優(yōu)秀的語言。只學(xué)其中一部分內(nèi)容很容易,但是要全面掌握則很難。開發(fā)人員遇到困難時往往將其歸咎于語言本身,而不反省他們自己對語言的理解有多匱乏。《你不知道的 JavaScript》旨在解決這個問題,使讀者能夠發(fā)自內(nèi)心地喜歡上這門語言。

          強制類型轉(zhuǎn)換

          值類型轉(zhuǎn)換

          var a = 42;
          var b = a + ""; // 隱式強制類型轉(zhuǎn)換
          var c = String(a); // 顯式強制類型轉(zhuǎn)換

          抽象值操作

          document.all 是假值對象。也就是 !!document.all 值為 false

          顯示強制類型轉(zhuǎn)換

          日期顯示轉(zhuǎn)換為數(shù)字:

          使用 Date.now() 來獲得當前的時間戳,使用 new Date(..).getTime() 來獲得指定時間的時間戳。

          奇特的 ~ 運算符:

          ~x 大致等同于 -(x+1)。很奇怪,但相對更容易說明問題:~42; // -(42+1) ==> -43

          JavaScript 中字符串的 indexOf(..) 方法也遵循這一慣例,該方法在字符串中搜索指定的子 字符串,如果找到就返回子字符串所在的位置(從 0 開始),否則返回 -1。

          ~indexOf() 一起可以將結(jié)果強制類型轉(zhuǎn)換(實際上僅僅是轉(zhuǎn)換)為真 / 假值:

          var a = "Hello World";
          ~a.indexOf("lo"); // -4 <-- 真值!

          if (~a.indexOf("lo")) { // true
            // 找到匹配!
          }

          解析非字符串:

          曾經(jīng)有人發(fā)帖吐槽過 parseInt(..) 的一個坑:

          parseInt( 1/0, 19 ); // 18

          parseInt(1/0, 19) 實際上是 parseInt("Infinity", 19)。第一個字符是 "I",以 19 為基數(shù) 時值為 18。

          此外還有一些看起來奇怪但實際上解釋得通的例子:

          parseInt(0.000008); // 0 ("0" 來自于 "0.000008")
          parseInt(0.0000008); // 8 ("8" 來自于 "8e-7")
          parseInt(false, 16); // 250 ("fa" 來自于 "false")
          parseInt(parseInt, 16); // 15 ("f" 來自于 "function..")
          parseInt("0x10"); // 16
          parseInt("103", 2); // 2

          隱式強制類型轉(zhuǎn)換

          字符串和數(shù)字之間的隱式強制類型轉(zhuǎn)換

          例如:

          var a = "42";
          var b = "0";
          var c = 42;
          var d = 0;
          a + b; // "420"
          c + d; // 42

          再例如:

          var a = [1,2];
          var b = [3,4];
          a + b; // "1,23,4"

          根據(jù) ES5 規(guī)范 11.6.1 節(jié),如果某個操作數(shù)是字符串或者能夠通過以下步驟轉(zhuǎn)換為字符串的話,+ 將進行拼接操作。如果其中一個操作數(shù)是對象(包括數(shù)組),則首先對其調(diào)用 ToPrimitive 抽象操作(規(guī)范 9.1 節(jié)),該抽象操作再調(diào)用 [[DefaultValue]](規(guī)范 8.12.8 節(jié)),以數(shù)字作為上下文。

          你或許注意到這與 ToNumber 抽象操作處理對象的方式一樣(參見 4.2.2 節(jié))。因為數(shù)組的 valueOf() 操作無法得到簡單基本類型值,于是它轉(zhuǎn)而調(diào)用 toString()。因此上例中的兩個數(shù)組變成了 "1,2" 和 "3,4" 。+ 將它們拼接后返回 "1,23,4" 。

          簡單來說就是,如果 + 的其中一個操作數(shù)是字符串(或者通過以上步驟可以得到字符串),則執(zhí)行字符串拼接;否則執(zhí)行數(shù)字加法。

          符號的強制類型轉(zhuǎn)換

          ES6 允許從符號到字符串的顯式強制類型轉(zhuǎn)換,然而隱式強制類型轉(zhuǎn)換會產(chǎn)生錯誤,具體的原因不在本書討論范圍之內(nèi)。

          例如:

          var s1 = Symbol("cool");
          String(s1); // "Symbol(cool)"
          var s2 = Symbol("not cool");
          s2 + ""; // TypeError

          符號不能夠被強制類型轉(zhuǎn)換為數(shù)字(顯式和隱式都會產(chǎn)生錯誤),但可以被強制類型轉(zhuǎn)換為布爾值(顯式和隱式結(jié)果都是 true)。

          由于規(guī)則缺乏一致性,我們要對 ES6 中符號的強制類型轉(zhuǎn)換多加小心。

          好在鑒于符號的特殊用途,我們不會經(jīng)常用到它的強制類型轉(zhuǎn)換。

          寬松相等和嚴格相等

          常見的誤區(qū)是“== 檢查值是否相等,=== 檢查值和類型是否相等”。聽起來蠻有道理,然而還不夠準確。很多 JavaScript 的書籍和博客也是這樣來解釋的,但是很遺憾他們都錯了。

          正確的解釋是:“== 允許在相等比較中進行強制類型轉(zhuǎn)換,而 === 不允許。”

          字符串和數(shù)字之間的相等比較:

          • 如果 Type(x) 是數(shù)字,Type(y) 是字符串,則返回 x == ToNumber(y) 的結(jié)果。
          • 如果 Type(x) 是字符串,Type(y) 是數(shù)字,則返回 ToNumber(x) == y 的結(jié)果。

          其他類型和布爾類型之間的相等比較:

          • 如果 Type(x) 是布爾類型,則返回 ToNumber(x) == y 的結(jié)果;
          • 如果 Type(y) 是布爾類型,則返回 x == ToNumber(y) 的結(jié)果。

          nullundefined 之間的相等比較:

          • 如果 x 為 null,y 為 undefined,則結(jié)果為 true。
          • 如果 x 為 undefined,y 為 null,則結(jié)果為 true。

          對象和非對象之間的相等比較:

          • 如果 Type(x) 是字符串或數(shù)字,Type(y) 是對象,則返回 x == ToPrimitive(y) 的結(jié)果;
          • 如果 Type(x) 是對象,Type(y) 是字符串或數(shù)字,則返回 ToPromitive(x) == y 的結(jié)果。

          語法

          錯誤

          提前使用變量

          ES6 規(guī)范定義了一個新概念,叫作 TDZ(Temporal Dead Zone,暫時性死區(qū))。

          TDZ 指的是由于代碼中的變量還沒有初始化而不能被引用的情況。

          對此,最直觀的例子是 ES6 規(guī)范中的 let 塊作用域:

          {
            a = 2; // ReferenceError!
            let a;
          }

          a = 2 試圖在 let a 初始化 a 之前使用該變量(其作用域在 { .. } 內(nèi)),這里就是 a 的 TDZ,會產(chǎn)生錯誤。

          有意思的是,對未聲明變量使用 typeof 不會產(chǎn)生錯誤(參見第 1 章),但在 TDZ 中卻會報錯:

          {
            typeof a; // undefined
            typeof b; // ReferenceError! (TDZ)
            let b;
          }

          回調(diào)

          省點回調(diào)

          構(gòu)造一個超時驗證工具:

          function timeoutify(fn, delay) {
            var intv = setTimeout(function() {
              intv = null
              fn(new Error('Timeout!'))
            }, delay)

            return function() {
              // 還沒有超時?
              if (intv) {
                clearTimeout(intv)
                fn.apply(this, arguments)
              }
            }
          }

          以下是使用方式:

          // 使用 ‘error-first 風(fēng)格’ 回調(diào)設(shè)計
          function foo(err, data) {
            if (err) {
              console.error(err)
            }
            else {
              console.log(data)
            }
          }

          ajax('http://some.url.1', timeoutify(foo, 500))

          如果你不確定關(guān)注的 API 會不會永遠異步執(zhí)行怎么辦呢?可以創(chuàng)建一個類似于這個“驗證概念”版本的 asyncify(..) 工具:

          function asyncify(fn) {
            var orig_fn = fn,
              intv = setTimeout(function() {
                intv = null
                if (fn) fn()
              }, 0)

            fn = null

            return function() {
              // 觸發(fā)太快,在定時器intv觸發(fā)指示異步轉(zhuǎn)換發(fā)生之前?
              if (intv) {
                fn = orig_fn.bind.apply(
                  orig_fn,
                  // 把封裝器的this添加到bind(..)調(diào)用的參數(shù)中,
                  // 以及克里化(currying)所有傳入?yún)?shù)
                  [this].concat([].slice.call(arguments))
                )
              }
              // 已經(jīng)是異步
              else {
                // 調(diào)用原來的函數(shù)
                orig_fn.apply(this, arguments)
              }
            }
          }

          可以像這樣使用 asyncify(..)

          function result(data) {
            console.log(a)
          }

          var a = 0

          ajax('..pre-cached-url..', asyncify(result))
          a++

          不管這個 Ajax 請求已經(jīng)在緩存中并試圖對回調(diào)立即調(diào)用,還是要從網(wǎng)絡(luò)上取得,進而在將來異步完成,這段代碼總是會輸出 1,而不是 0——result(..) 只能異步調(diào)用,這意味著 a++ 有機會在 result(..) 之前運行。

          關(guān)于回調(diào)地獄的可以看:JS中優(yōu)雅的使用async await

          Promise

          Promise 信任問題

          回調(diào)未調(diào)用

          提供一個超時處理的解決方案:

          // 用于超時一個Promise的工具
          function timeoutPromise(delay) {
            return new Promise(function(resolve, reject) {
              setTimeout(function() {
                reject('Timeout!')
              }, delay)
            })
          }

          // 設(shè)置foo()超時
          Promise.race([
            foo(),
            timeoutPromise(3000)
          ])
          .then(
            function() {
              // foo(..)及時完成!
            },
            function(err) {
              // 或者foo()被拒絕,或者只是沒能按時完成
            // 查看err來了解是哪種情況
            }
          )

          鏈式流

          為了進一步闡釋鏈接,讓我們把延遲 Promise 創(chuàng)建(沒有決議消息)過程一般化到一個工具中,以便在多個步驟中復(fù)用:

          function delay(time) {
            return new Promise(function(resolve, reject) {
              setTimeout(resolve, time)
            })
          }

          delay(100) // 步驟1
            .then(function STEP2() {
              console.log("step 2 (after 100ms)")
              return delay(200)
            })
            .then(function STEP3() {
              console.log("step 3 (after another 200ms)")
            })
            .then(function STEP4() {
              console.log("step 4 (next Job)")
              return delay(50)
            })
            .then(function STEP5() {
              console.log("step 5 (after another 50ms)")
            })

          調(diào)用 delay(200) 創(chuàng)建了一個將在 200ms 后完成的 promise,然后我們從第一個 then(..) 完成回調(diào)中返回這個 promise,這會導(dǎo)致第二個 then(..) 的 promise 等待這個 200ms 的 promise。

          Promise 局限性

          順序錯誤處理

          Promise 的設(shè)計局限性(鏈式調(diào)用)造成了一個讓人很容易中招的陷阱,即 Promise 鏈中的錯誤很容易被無意中默默忽略掉。

          關(guān)于 Promise 錯誤,還有其他需要考慮的地方。由于一個 Promise 鏈僅僅是連接到一起的成員 Promise,沒有把整個鏈標識為一個個體的實體,這意味著沒有外部方法可以用于觀察可能發(fā)生的錯誤。

          如果構(gòu)建了一個沒有錯誤處理函數(shù)的 Promise 鏈,鏈中任何地方的任何錯誤都會在鏈中一直傳播下去,直到在某個步驟注冊拒絕處理函數(shù)。在這個特定的例子中,只要有一個指向鏈中最后一個 promise 的引用就足夠了(下面代碼中的 p),因為你可以在那里注冊拒絕處理函數(shù),而且這個處理函數(shù)能夠得到所有傳播過來的錯誤的通知:

          // foo(..), STEP2(..)以及STEP3(..)都是支持promise的工具
          var p = foo(42)
            .then(STEP2)
            .then(STEP3);

          雖然這里可能令人迷惑,但是這里的 p 并不指向鏈中的第一個 promise(調(diào)用 foo(42) 產(chǎn)生的那一個),而是指向最后一個 promise,即來自調(diào)用 then(STEP3) 的那一個。

          還有,這個 Promise 鏈中的任何一個步驟都沒有顯式地處理自身錯誤。這意味著你可以在 p 上注冊一個拒絕錯誤處理函數(shù),對于鏈中任何位置出現(xiàn)的任何錯誤,這個處理函數(shù)都會得到通知:

          p.catch(handleErrors); 

          但是,如果鏈中的任何一個步驟事實上進行了自身的錯誤處理(可能以隱藏或抽象的不可見的方式),那你的 handleErrors(..) 就不會得到通知。這可能是你想要的——畢竟這是一個“已處理的拒絕”——但也可能并不是。不能清晰得到(對具體某一個“已經(jīng)處理”的拒絕的)錯誤通知也是一個缺陷,它限制了某些用例的功能。

          基本上,這等同于 try..catch 存在的局限:try..catch 可能捕獲一個異常并簡單地吞掉它。所以這并不是 Promise 獨有的局限性,但可能是我們希望繞過的陷阱。

          遺憾的是,很多時候并沒有為 Promise 鏈序列的中間步驟保留的引用。因此,沒有這樣的引用,你就無法關(guān)聯(lián)錯誤處理函數(shù)來可靠地檢查錯誤。

          關(guān)于Promise你還可以看這個:一道讓人失眠的 Promise 試題深入分析

          單一值

          根據(jù)定義,Promise 只能有一個完成值或一個拒絕理由。在簡單的例子中,這不是什么問題,但是在更復(fù)雜的場景中,你可能就會發(fā)現(xiàn)這是一種局限了。

          一般的建議是構(gòu)造一個值封裝(比如一個對象或數(shù)組)來保持這樣的多個信息。這個解決方案可以起作用,但要在 Promise 鏈中的每一步都進行封裝和解封,就十分丑陋和笨重了。

          1. 分裂值

          有時候,你可以把這一點,當作提示你應(yīng)該把問題分解為兩個或更多 Promise 的信號。

          設(shè)想你有一個工具 foo(..),它可以異步產(chǎn)生兩個值(x 和 y):

          function getY(x) { 
            return new Promise(function(resolve, reject){ 
              setTimeout(function(){ 
                resolve((3 * x) - 1); 
              }, 100); 
            });


          function foo(bar, baz) { 
            var x = bar * baz; 
            return getY(x).then(function(y){ 
              // 把兩個值封裝到容器中
              return [x, y]; 
            }); 


          foo(10, 20).then(function(msgs){ 
            var x = msgs[0]; 
            var y = msgs[1]; 
            console.log(x, y); // 200 599 
          }); 

          首先,我們重新組織一下 foo(..) 返回的內(nèi)容,這樣就不再需要把 xy 封裝到一個數(shù)組值中以通過 promise 傳輸。取而代之的是,我們可以把每個值封裝到它自己的 promise:

          function foo(bar, baz) { 
            var x = bar * baz; 
            
            // 返回兩個 promise
            return [ 
              Promise.resolve(x), 
              getY(x) 
            ]; 


          Promise.all( 
            foo(10, 20) 
          ).then(function(msgs){ 
            var x = msgs[0]; 
            var y = msgs[1]; 
            console.log(x, y); 
          }); 

          一個 promise 數(shù)組真的要優(yōu)于傳遞給單個 promise 的一個值數(shù)組嗎?從語法的角度來說,這算不上是一個改進。

          但是,這種方法更符合 Promise 的設(shè)計理念。如果以后需要重構(gòu)代碼把對 xy 的計算分開,這種方法就簡單得多。由調(diào)用代碼來決定如何安排這兩個 promise,而不是把這種細節(jié)放在 foo(..) 內(nèi)部抽象,這樣更整潔也更靈活。這里使用了 Promise.all([ .. ]),當然,這并不是唯一的選擇。

          1. 傳遞參數(shù)

          var x = ..var y = .. 賦值操作仍然是麻煩的開銷。我們可以在輔助工具中采用某種函數(shù)技巧:

          function spread(fn) { 
            return Function.apply.bind(fn, null); 


          Promise.all( 
            foo(10, 20) 
          ).then(spread(function(x, y){ 
            console.log(x, y); // 200 599 
          })) 

          這樣會好一點!當然,你可以把這個函數(shù)戲法在線化,以避免額外的輔助工具:

          Promise.all( 
            foo(10, 20) 
          ).then(Function.apply.bind( 
            function(x, y){ 
              console.log(x, y); // 200 599 
            },
            null
          )); 

          這些技巧可能很靈巧,但 ES6 給出了一個更好的答案:解構(gòu)。數(shù)組解構(gòu)賦值形式看起來是這樣的:

          Promise.all( 
            foo(10, 20) 
          ).then(function(msgs){ 
            var [x, y] = msgs; 
            console.log(x, y); // 200 599 
          }); 

          不過最好的是,ES6 提供了數(shù)組參數(shù)解構(gòu)形式:

          Promise.all( 
            foo(10, 20) 

          .then(function([x, y]){ 
            console.log(x, y); // 200 599 
          }); 

          現(xiàn)在,我們符合了“每個 Promise 一個值”的理念,并且又將重復(fù)樣板代碼量保持在了最小!

          單決議

          Promise 最本質(zhì)的一個特征是:Promise 只能被決議一次(完成或拒絕)。在許多異步情況中,你只會獲取一個值一次,所以這可以工作良好。

          但是,還有很多異步的情況適合另一種模式——一種類似于事件或數(shù)據(jù)流的模式。在表面上,目前還不清楚 Promise 能不能很好用于這樣的用例,如果不是完全不可用的話。如果不在 Promise 之上構(gòu)建顯著的抽象,Promise 肯定完全無法支持多值決議處理。

          設(shè)想這樣一個場景:你可能要啟動一系列異步步驟以響應(yīng)某種可能多次發(fā)生的激勵(就像是事件),比如按鈕點擊。

          這樣可能不會按照你的期望工作:

          // click(..) 把"click"事件綁定到一個 DOM 元素
          // request(..) 是前面定義的支持 Promise 的 Ajax 
          var p = new Promise(function(resolve, reject){ 
            click("#mybtn", resolve); 
          }); 

          p.then(function(evt){ 
            var btnID = evt.currentTarget.id; 
            return request("http://some.url.1/?id=" + btnID); 
          }).then(function(text){ 
            console.log(text); 
          }); 

          只有在你的應(yīng)用只需要響應(yīng)按鈕點擊一次的情況下,這種方式才能工作。如果這個按鈕被點擊了第二次的話,promise p 已經(jīng)決議,因此第二個 resolve(..) 調(diào)用就會被忽略。

          因此,你可能需要轉(zhuǎn)化這個范例,為每個事件的發(fā)生創(chuàng)建一整個新的 Promise 鏈:

          click("#mybtn"function(evt){ 
            var btnID = evt.currentTarget.id; 
            request("http://some.url.1/?id=" + btnID).then(function(text){ 
              console.log(text); 
            }); 
          }); 

          這種方法可以工作,因為針對這個按鈕上的每個 "click" 事件都會啟動一整個新的 Promise 序列。

          由于需要在事件處理函數(shù)中定義整個 Promise 鏈,這很丑陋。除此之外,這個設(shè)計在某種程度上破壞了關(guān)注點與功能分離(SoC)的思想。你很可能想要把事件處理函數(shù)的定義和對事件的響應(yīng)(那個 Promise 鏈)的定義放在代碼中的不同位置。如果沒有輔助機制的話,在這種模式下很難這樣實現(xiàn)。

          感謝

          如果本文對你有幫助,就點個贊支持下吧!感謝閱讀。

          關(guān)于本文

          作者:gyx_這個殺手不太冷靜
          https://juejin.cn/post/6859133591108976648

          - EOF -

          Node 社群


          我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。


             “分享、點贊在看” 支持一波??

          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色日本在线观看视频 | 亚洲日本欧美视频 | 无码一区三区 | 爱爱免费看片 | www.操操操.com |