<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>

          帶你理解 JS 容易出錯的坑和細節

          時間:?2017-11-13閱讀:?995標簽:?js技巧

          執行環境(Execution context)

          var 和 let 的正確解釋

          當執行 JS 代碼時,會生成執行環境,只要代碼不是寫在函數中的,就是在全局執行環境中,函數中的代碼會產生函數執行環境,只此兩種執行環境。

          接下來讓我們看一個老生常談的例子,var

          b() // call b
          console.log(a) // undefined
          
          var a = 'Hello world'
          
          function b() {
              console.log('call b')
          }

          想必以上的輸出大家肯定都已經明白了,這是因為函數和變量提升的原因。通常提升的解釋是說將聲明的代碼移動到了頂部,這其實沒有什么錯誤,便于大家理解。但是更準確的解釋應該是:在生成執行環境時,會有兩個階段。第一個階段是創建的階段,JS 解釋器會找出需要提升的變量和函數,并且給他們提前在內存中開辟好空間,函數的話會將整個函數存入內存中,變量只聲明并且賦值為 undefined,所以在第二個階段,也就是代碼執行階段,我們可以直接提前使用。

          在提升的過程中,相同的函數會覆蓋上一個函數,并且函數優先于變量提升

          b() // call b second
          
          function b() {
              console.log('call b fist')
          }
          function b() {
              console.log('call b second')
          }
          var b = 'Hello world'

          var 會產生很多錯誤,所以在 ES6中引入了 let。let 不能在聲明前使用,但是這并不是常說的 let 不會提升,let 提升了,在第一階段內存也已經為他開辟好了空間,但是因為這個聲明的特性導致了并不能在聲明前使用。

          作用域

          function b() {
              console.log(value)
          }
          
          function a() {
              var value = 2
              b()
          }
          
          var value = 1
          a()

          可以考慮下 b 函數中輸出什么。你是否會認為 b 函數是在 a 函數中調用的,相應的 b 函數中沒有聲明 value 那么應該去 a 函數中尋找。其實答案應該是 1。

          當在產生執行環境的第一階段時,會生成 [[Scope]] 屬性,這個屬性是一個指針,對應的有一個作用域鏈表,JS 會通過這個鏈表來尋找變量直到全局環境。這個指針指向的上一個節點就是該函數聲明的位置,因為 b 是在全局環境中聲明的,所以 value 的聲明會在全局環境下尋找。如果 b 是在 a 中聲明的,那么 log 出來的值就是 2 了。

          異步

          JS 是門同步的語言,你是否疑惑過那么為什么 JS 還有異步的寫法。其實 JS 的異步和其他語言的異步是不相同的,本質上還是同步。因為瀏覽器會有一個 Event Queue 存放異步通知,JS 在執行代碼時會產生一個執行棧,同步的代碼在執行棧中,異步的在 Event Queue 中。只有當執行棧為空時,JS 才會去 Event Queue 中查看是否有需要處理的通知,有的話拿到執行棧中去執行。

          function sleep() {
            var ms = 2000 + new Date().getTime()
            while( new Date() < ms) {}
            console.log('sleep finish')
          }
          
          document.addEventListener('click', function() {
            console.log('click')
          })
          
          sleep()
          console.log('finish')

          以上代碼如果你在 sleep 被調用期間點擊,只有當 sleep 執行結束并且 log finish 后才會響應點擊事件。所以要注意 setTimeout 并不是你設定多久 JS 就會準時的響應,并且 setTimeout 也有個小細節,第二個參數設置為 0 也許會有人認為這樣就不是異步了,其實還是異步。這是因為 HTML5 標準規定這個函數第二個參數不得小于 4 毫秒,不足會自動增加。

          類型

          原始值

          JS 共有 6 個原始值,分別為 Boolean, Null, Undefined, Number, String, Symbol,這些類型都是值不可變的。

          有一個易錯的點是:雖然 typeof null 是 object 類型,但是 Null 不是對象,這是 JS 語言的一個很久遠的 Bug 了。

          深淺拷貝

          對于對象來說,直接將一個對象賦值給另外一個對象就是淺拷貝,兩個對象指向同一個地址,其中任何一個對象改變,另一個對象也會被改變

          var a = [1, 2]
          var b = a
          b.push(3)
          console.log(a, b) // -> 都是 [1, 2, 3]

          有些情況下我們可能不希望有這種問題,那么深拷貝可以解決這個問題。深拷貝不僅將原對象的各個屬性逐個復制出去,而且將原對象各個屬性所包含的對象也依次采用深復制的方法遞歸復制到新對象上。深拷貝有多種寫法,有興趣的可以看這篇文章

          函數和對象

          this

          this 是很多人會混淆的概念,但是其實他一點都不難,你只需要記住幾個規則就可以了。

          function foo() {
            console.log(this.a)
          }
          var a = 2
          foo() 
          
          var obj = {
            a: 2,
            foo: foo
          }
          obj.foo() 
          
          // 以上兩者情況 this 只依賴于調用函數前的對象,優先級是第二個情況大于第一個情況
          
          // 以下情況是優先級最高的,this 只會綁定在 c 上
          var c = new foo()
          c.a = 3
          console.log(c.a)
          
          // 還有種就是利用 call,apply,bind 改變 this,這個優先級僅次于 new

          以上幾種情況明白了,很多代碼中的 this 應該就沒什么問題了,下面讓我們看看箭頭函數中的 this

          function a() {
              return () => {
                  return () => {
                      console.log(this)
                  }
              }
          }
          console.log(a()()())

          箭頭函數其實是沒有 this 的,這個函數中的 this 只取決于他外面的第一個不是箭頭函數的函數的 this。在這個例子中,因為調用 a 符合前面代碼中的第一個情況,所以 this 是 window。并且 this 一旦綁定了上下文,就不會被任何代碼改變。

          下面我們再來看一個例子,很多人認為他是一個 JS 的問題

          var a = {
              name: 'js',
              log: function() {
                  console.log(this)
                  function setName() {
                      this.name = 'javaScript'
                      console.log(this)
                  }
                  setName()
              }
          }
          a.log()

          setName 中的 this 指向了 window,很多人認為他應該是指向 a 的。這里其實我們不需要去管函數是寫在什么地方的,我們只需要考慮函數是怎么調用的,這里符合上述第一個情況,所以應該是指向 window。

          閉包和立即執行函數

          閉包被很多人認為是一個很難理解的概念。其實閉包很簡單,就是一個能夠訪問父函數局部變量的函數,父函數在執行完后,內部的變量還存在內存上讓閉包使用。

          function a(name) {
              // 這就是閉包,因為他使用了父函數的參數
              return function() {
                  console.log(name)
              }
          }
          var b = a('js')
          b() // -> js

          現在來看一個面試題

          function a() {
              var array = []
          
              for(var i = 0; i < 3; i++) {
                  array.push(
                      function() {
                          console.log(i)
                      }
                  )
              }
          
              return array
          }
          
          var b = a()
          b[0]()
          b[1]()
          b[2]()

          這個題目因為 i 被提升了,所以 i = 3,當 a 函數執行完成后,內存中保留了 a 函數中的變量 i。數組中 push 進去的只是聲明,并沒有執行函數。所以在執行函數時,輸出了 3 個 3。

          如果我們想輸出 0 ,1,2 的話,有兩種簡單的辦法。第一個是在 for 循環中,使用 let 聲明一個變量,保存每次的 i 值,這樣在 a 函數執行完成后,內存中就保存了 3 個不同 let 聲明的變量,這樣就解決了問題。

          還有個辦法就是使用立即執行函數,創建函數即執行,這樣就可以保存下當前的 i 的值。

          function a() {
              var array = []
          
              for(var i = 0; i < 3; i++) {
                  array.push(
                      (function(j) {
                          return function() {
                              console.log(j)
                          }
                      }(i))
                  )
              }
          
              return array
          }

          立即執行函數其實就是直接調用匿名函數

          function() {} ()

          但是以上寫法會報錯,因為解釋器認為這是一個函數聲明,不能直接調用,所以我們加上了一個括號來讓解釋器認為這是一個函數表達式,這樣就可以直接調用了。

          所以我們其實只需要讓解釋器認為我們寫了個函數表達式就行了,其實還有很多種立即執行函數寫法

          true && function() {} ()
          new && function() {} ()

          立即執行函數最大的作用就是模塊化,其次就是解決上述閉包的問題了。

          原型,原型鏈和 instanceof 原理

          原型可能很多人覺得很復雜,本章節也不打算重復復述很多文章都講過的概念,你只需要看懂我畫的圖并且自己實驗下即可

          function P() {
              console.log('object')
          }
          
          var p = new P()


          原型鏈就是按照 __proto__ 尋找,直到 Object。instanceof 原理也是根據原型鏈判斷的

          p instanceof P // true
          p instanceof Object // true
          站長推薦

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

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

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

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

          三個實用的javascript小技巧

          如果你想從后向前獲取一個數組的元素,可以這樣寫:如果你想在某個條件邏輯值為true時,執行某個函數,就像這樣:如果你必須給一個變量賦默認值,可以簡單的這樣寫:

          JS技巧技法總結

          閉包原理、數組展平、前端語音(在項目中需要對ajax請求返回的消息進行語音播報,str 為返回的data)、Proxy 數據綁定和監聽、計數器

          JS禁止打開控制臺

          主要為了通過禁止打開控制臺,防止別人進行代碼調試。禁止右鍵查看源碼和F12;通過頁面寬度變化監測控制臺;利用控制臺特性改寫對象toString;利用控制臺特性進行監聽dom屬性

          javascript如何判斷值是否為整數?

          javascript如何判斷一個值是否為整數?下面本篇文章就來給大家介紹一下使用javascript判斷一個值是否為整數的方法。

          (a ==1 && a== 2 && a==3) 有可能是 true 嗎?

          1. 利用松散相等運算符 == 的原理,自定義 toString 和 valueOf 返回對應值2. 利用半寬度韓文等特殊字符,玩“障眼法”,本質上其實并沒有做到題設3. 劫持 JS 對象的 getter,不過這種方式對于嚴格相等 === 同樣有效

          js中~~和 | 的妙用

          ~~它代表雙非按位取反運算符,如果你想使用比Math.floor()更快的方法,那就是它了。需要注意,對于正數,它向下取整;對于負數,向上取整;非數字取值為0,它具體的表現形式為:

          js技巧_js中一些常見的陷阱

          這里我們針對JavaScript初學者給出一些技巧和列出一些陷阱。如果你已經是一個磚家,也可以讀一讀。你是否嘗試過對數組元素進行排序?

          5個小技巧讓你寫出更好的 JavaScript 條件語句

          使用 Array.includes 來處理多重條件,少寫嵌套,盡早返回,使用函數默認參數和解構,相較于 switch,Map / Object 也許是更好的選擇,使用 Array.every 和 Array.some 來處理全部/部分滿足條件,讓我們一起寫出可讀性更高的代碼吧

          js語言中常見錯誤總匯

          事實證明很多這些 null 或 undefined 的錯誤是普遍存在的。 一個類似于 Typescript 這樣的好的靜態類型檢查系統,當設置為嚴格的編譯選項時,能夠幫助開發者避免這些錯誤。

          js求數組的最大值--奇技淫巧和笨方法

          js中有很多“奇技淫巧”,有時我會常常刻意去用這些“奇技淫巧”(注意,我不是在反對用它,只是有時其實沒必要用)。比如,求數組中的最大值,js中Array沒有原生的求最大值的方法,但是Math有呀

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

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

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

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