iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 29
0
Modern Web

VueJS 從前端到後端系列 第 29

Vanilla JS 與 Vue 的生命週期 Day 28

部落格同步刊登 [IT 鐵人賽] Vanilla JS 與 Vue 的生命週期 Day 28

最後幾天來聊一下關於生命週期的事情,這裡提到的生命週期並不限於 Vue 所屬的區域,也包含了一些原生 JavaScript 那種無關 Bug 而是一種 Feature 的那些 生命週期 的事情。

沒有被 JS 婊過就不算寫過 JS 你說是吧( 並沒有這種說法好嗎 )


關於封裝與執行

我之前犯過幾個低級錯誤,舉個例子給大家看個笑話:

<template>
    <section class="hello">
        <h1 v-if="window.location.href === '/'">Homepage</h1>
        <h1 v-else>Other pages</h1>
    </section>
</template>

以上是錯誤的範例,至於看不出來哪裡有錯的人 左轉不送謝謝。就字面上來說每個字都對,寫法也沒有錯誤,不過,就 Vue 而言,你會獲得一個 windowundefined 的結果。至於原因嘛,在 Vue 樣版作用域當中,裡面所有的變數是 明確指向該物件 的作用域裡面,也就是說,最終都會指回該物件的 this 本身。

所以,那上面的 window 並不會是你以為的 window 物件。

另外一個關於封裝的低級錯誤:

let myOptions = {}

export default {
  name: 'component',
  data () {
    return {
      options: myOptions
    }
  }
}

以上的寫法也沒有任何錯誤、或是會讓人覺得奇怪的地方。但,錯誤會發生在 這個物件被重用( Reuse )的時候 。我在 第 4 天 有提到會被污染的事情,另外在 第 12 天 也提到了一些生命週期的奇妙狀況。

對於 Webpack 最終封裝的結果來說,你只要是放在 export default {} 外面的事情,最好都可以當他是一個 小規模的全域變數 來看待。如果還是不知道我在講什麼,為什麼這樣會有錯的人,請複習一下 Kuro 的文章:

JavaScript 是「傳值」或「傳址」

歐,你不要以為剛剛的 let myOptions = {} 改成 const myOptions = {} 就會沒事,如果認為會沒事的人 一樣左轉不送謝謝 ,你可以參考一下 Object.freeze() 的相關文件。

犯蠢的事情很多,不過就一些小地方來說,還是得自己留意一下才行。通常不會是因為你 寫法 上面的錯誤,多數狀況是 那些東西寫在哪裡 或是說被 應用在哪裡 的問題。終究你還是得摸清楚你所使用的框架,是不是有什麼需要留意的地方。


那些 XX 週期

終究你可能會學到一件事情,就是那些讓人煩躁的生命週期,最終都是用來浪費生命的(欸)。我舉一個有趣的例子,是關於生命週期與原生 JS 混合的案例:

<template>
    <section>
        <img class="previewer" :src="previewSrc" />
        <input type="file" @change.prevent="previewFile($event)" />
    </section>
</template>
export default {
  name: 'component',
  data () {
    return {
      previewSrc: ''
    }
  },
  methods: {
    previewFile (evt) {
      let fileReader = new window.FileReader()
      fileReader.readAsDataURL(evt.target.files[0])
      fileReader.onload = event => {
        const preview = this.$el.querySelector('.previewer')
        preview.addEventListener('load', () => {
          // 中間就不贅述了。
        }, false)
        
        this.previewSrc = event.target.result
      }
    }
  }
}

上述的例子混合了 Vue 的方法與在原生 JavaScript 操作的 onload 方法。但為何會說這樣的方式會出問題呢?原因在於 前端渲染 這件事,倘若你的操作並不會 重繪 所有的 DOM 結構樹的話,那麼,對於瀏覽器與 JavaScript 來說,那些 DOM 物件與其被綁定的事件,就依舊會在同樣的 位置 上。

這裡的 位置 也包含了記憶體位址。但是,請注意上面例子,同樣都是使用 讀取 的事件,有一個是使用 onload 來指定,而另外一個則使用 addEventListener 來指定,這會造成一些意想不到的錯誤。

那麼,當你重複的操作剛剛的檔案上傳動作時,會有以下這樣的結果:

https://ithelp.ithome.com.tw/upload/images/20190930/20001433bS44O6O8cS.png

操作 3 次選擇檔的動作,然後你會發現 Preview 的事件被累加了,而 FileReader 的卻維持正常。主要的差異在於,雖然都是屬於匿名函式,但是 addEventListener 所接受的匿名函式,在每次運作的時候,都會是 新的位置 ,而對於 onload 來說,他只是原有物件屬性,被做了一個 賦值 的動作,所以,兩者所參考的記憶體位置是不同的。

所以,不是每個地方都雞婆的用上 addEventListener 就自以為很會寫 JS 好像很潮。

上述的狀況,如果再加上 updated 這個生命週期來看,那麼,他理所當然的會被夾在 FileReader 與 Preview 中間。不過,倘若你的圖片 src 是用 preview.src 來賦予值的話,那麼你的 updated 不會被呼叫是很合理的,對於 Vue 來說,你自己操作 DOM 跟他無關,所以只要是虛擬節點沒有任何改變,自然就不會呼叫 updated


小結

其實 JavaScript 寫到最後都會懷疑人生,這是一個階段不用太灰心(欸)。


上一篇
大型資料載入實例與狀況 Deep watch Day 27
下一篇
下一個 VueJS 3.0 Day 29
系列文
VueJS 從前端到後端31

尚未有邦友留言

立即登入留言