Vue.js 升級踩坑小記

本文並非什麼高深的技術文章,只是記錄我最近遇到一個由於 Vue 升級致使個人一個項目踩坑以及我解決問題的過程。文章雖長但不水,寫下來的目的是想和你們分享一下我遇到問題時候一個思考的方法和態度。html

背景:去年我在慕課網推出了一門 Vue.js 的入門實戰課程——Vue.js 高仿餓了麼外賣 App ,這門課程收到了很是不錯的反響,因而今年又在慕課網上繼續推出了 Vue.js 的高級進階實戰課程——Vue.js 音樂 App,一樣反饋不錯。天天晚上下班回家,我會去問答區看一下學生們的問題,發現近期有很多同窗反饋了一樣的問題,iOS 微信裏點擊不能播放歌曲了,PC 能夠。一般遇到這種問題我會讓學生先去訪問個人項目的線上地址,看看個人代碼會不會有問題,獲得的結論是個人線上代碼沒問題,但他們本身寫就不行,而且說已經徹底和個人代碼作了對比,這就讓我以爲十分詭異。沒過多久,有些學生就想出了一個辦法,在全局 document 綁定一個同步的 click 事件,在 click 事件的回調函數中同步觸發一次 audio 的 play 方法,彷佛解決了問題,也獲得了一些同窗的採納,可是我看到之後的第一反應是不能用這種太 hack 的方式去解決問題,必須找到問題的本質,因而乎我開始了一段頗有意思的找問題的過程。vue

定位問題

先看現象:同窗們寫的代碼在 iOS 微信瀏覽器下不能播放,PC 是能夠的;我線上的代碼是均可以。瞭解現象後我開始排查問題:html5

  • 同窗們的代碼寫的有問題?
    雖然會有這種可能性,但從 2 個維度被我否決了:1. 同窗們也都對比過個人源碼的,並且出問題的同窗也不是個別現象;2. 若是是代碼問題,那麼大多可能性是 PC 和移動端都不能播放。webpack

  • 找不一樣?
    這個問題是最新纔出現的,同窗們開始學習編寫課程代碼都也是經過 vue-cli 腳手架先初始化代碼。接着我大概看了一下新版的腳手架初始化的代碼,果真是大不一樣,webpack 升級到 3+,配置發生了很大的變化。不過依據個人經驗,構建工具的升級是不會影響業務代碼的,必定還有別的緣由。ios

  • Vue.js 升級?
    除了 webpack 配置的不一樣,最新腳手架初始化的代碼用的 Vue.js 版本是 2.5+,而我線上代碼的 Vue.js 版本是 2.3+,難道是 Vue.js 致使的問題嗎?帶着這個疑問我去翻閱了 Vue.js 的 release log,發現 Vue.js 大大小小版本發佈了十幾回。若是每一個都仔細查看也會很耗時,因而我採用了一個經典的 2 分法的思路去定位,我先把 Vue.js 升級到 2.4.0,發現居然安裝不了(這是 Vue.js 剛升到 2.4 npm 發佈的 bug),因而又升級到 2.4.1,而後拿個人手機試了一下,仍是能夠播放的。接着我把 Vue.js 升級到 2.5.0,手機一試果真不能播放了,(擦。。)我內心默唸一句,總算找到問題所在了。git

問題的本質

以上定位到問題大概花了我半小時時間,可是我並無找到問題的根本緣由,因而我翻閱了 Vue.js 2.5 的 release log,因爲很長就不列了。Vue.js 每次升級主要分紅 2 大類,Features & Improvements 和 Bug Fixes。我從上往下依次掃了一遍,把一些關於它核心的改動都點進去看了一下代碼的修改,最終鎖定了這一條:github

use MessageChannel for nextTick 6e41679, closes #6566 #6690web

接着我點進去看了一下改動,我滴天,改動很大呀,nextTick 的核心實現變了,MutationObserver 不見了,改爲了 MessageChannel 的實現。等等,有些同窗看到這裏,可能會懵,這都是些啥呀。不急,我先簡單解釋一下 Vue 的 nextTick。vuex

nextTick

介紹 Vue 的 nextTick 以前,我先簡單介紹一下 JS 的運行機制:JS 執行是單線程的,它是基於事件循環的。對於事件循環的理解,阮老師有一篇文章寫的很清楚,大體分爲如下幾個步驟:vue-cli

(1)全部同步任務都在主線程上執行,造成一個執行棧(execution context stack)。

(2)主線程以外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。

(3)一旦"執行棧"中的全部同步任務執行完畢,系統就會讀取"任務隊列",看看裏面有哪些事件。那些對應的異步任務,因而結束等待狀態,進入執行棧,開始執行。

(4)主線程不斷重複上面的第三步。

主線程的執行過程就是一個 tick,而全部的異步結果都是經過 「任務隊列」 來調度被調度。 消息隊列中存放的是一個個的任務(task)。 規範中規定 task 分爲兩大類,分別是 macro task 和 micro task,而且每一個 macro task 結束後,都要清空全部的 micro task。

關於 macro task 和 micro task 的概念,這裏不會細講,簡單經過一段代碼演示他們的執行順序:

for (macroTask of macroTaskQueue) {
    // 1. Handle current MACRO-TASK
    handleMacroTask();

    // 2. Handle all MICRO-TASK
    for (microTask of microTaskQueue) {
        handleMicroTask(microTask);
    }
}複製代碼

在瀏覽器環境中,常見的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate;常見的 micro task 有 MutationObsever 和 Promise.then。對於它們更多的瞭解,感興趣的同窗能夠看這篇文章

回到 Vue 的 nextTick,nextTick 顧名思義,就是下一個 tick,Vue 內部實現了 nextTick,並把它做爲一個全局 API 暴露出來,它支持傳入一個回調函數,保證回調函數的執行時機是在下一個 tick。官網文檔介紹了 Vue.nextTick 的使用場景:

Usage: Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update.
使用:在下次 DOM 更新循環結束以後執行延遲迴調,在修改數據以後當即使用這個方法,獲取更新後的 DOM。

在 Vue.js 裏是數據驅動視圖變化,因爲 JS 執行是單線程的,在一個 tick 的過程當中,它可能會屢次修改數據,但 Vue.js 並不會傻到每修改一次數據就去驅動一次視圖變化,它會把這些數據的修改所有 push 到一個隊列裏,而後內部調用 一次 nextTick 去更新視圖,因此數據到 DOM 視圖的變化是須要在下一個 tick 才能完成。

接下來,咱們來看一下 Vue 的 nextTick 的實現,在 Vue.js 2.5+ 的版本,抽出來一個單獨的 next-tick.js 文件去實現它。

/* @flow */
/* globals MessageChannel */

import { noop } from 'shared/util'
import { handleError } from './error'
import { isIOS, isNative } from './env'

const callbacks = []
let pending = false

function flushCallbacks () {
  pending = false
  const copies = callbacks.slice(0)
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

// Here we have async deferring wrappers using both micro and macro tasks.
// In < 2.4 we used micro tasks everywhere, but there are some scenarios where
// micro tasks have too high a priority and fires in between supposedly
// sequential events (e.g. #4521, #6690) or even between bubbling of the same
// event (#6566). However, using macro tasks everywhere also has subtle problems
// when state is changed right before repaint (e.g. #6813, out-in transitions).
// Here we use micro task by default, but expose a way to force macro task when
// needed (e.g. in event handlers attached by v-on).
let microTimerFunc
let macroTimerFunc
let useMacroTask = false

// Determine (macro) Task defer implementation.
// Technically setImmediate should be the ideal choice, but it's only available
// in IE. The only polyfill that consistently queues the callback after all DOM
// events triggered in the same loop is by using MessageChannel.
/* istanbul ignore if */
if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
  macroTimerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else if (typeof MessageChannel !== 'undefined' && (
  isNative(MessageChannel) ||
  // PhantomJS
  MessageChannel.toString() === '[object MessageChannelConstructor]'
)) {
  const channel = new MessageChannel()
  const port = channel.port2
  channel.port1.onmessage = flushCallbacks
  macroTimerFunc = () => {
    port.postMessage(1)
  }
} else {
  /* istanbul ignore next */
  macroTimerFunc = () => {
    setTimeout(flushCallbacks, 0)
  }
}

// Determine MicroTask defer implementation.
/* istanbul ignore next, $flow-disable-line */
if (typeof Promise !== 'undefined' && isNative(Promise)) {
  const p = Promise.resolve()
  microTimerFunc = () => {
    p.then(flushCallbacks)
    // in problematic UIWebViews, Promise.then doesn't completely break, but
    // it can get stuck in a weird state where callbacks are pushed into the
    // microtask queue but the queue isn't being flushed, until the browser
    // needs to do some other work, e.g. handle a timer. Therefore we can
    // "force" the microtask queue to be flushed by adding an empty timer.
    if (isIOS) setTimeout(noop)
  }
} else {
  // fallback to macro
  microTimerFunc = macroTimerFunc
}

/** * Wrap a function so that if any code inside triggers state change, * the changes are queued using a Task instead of a MicroTask. */
export function withMacroTask (fn: Function): Function {
  return fn._withTask || (fn._withTask = function () {
    useMacroTask = true
    const res = fn.apply(null, arguments)
    useMacroTask = false
    return res
  })
}

export function nextTick (cb?: Function, ctx?: Object) {
  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')
      }
    } else if (_resolve) {
      _resolve(ctx)
    }
  })
  if (!pending) {
    pending = true
    if (useMacroTask) {
      macroTimerFunc()
    } else {
      microTimerFunc()
    }
  }
  // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') {
    return new Promise(resolve => {
      _resolve = resolve
    })
  }
}複製代碼

咱們在有以前的知識背景,再理解 nextTick 的實現就不難了,這裏有一段很關鍵的註釋:在 Vue 2.4 以前的版本,nextTick 幾乎都是基於 micro task 實現的,但因爲 micro task 的執行優先級很是高,在某些場景下它甚至要比事件冒泡還要快,就會致使一些詭異的問題,如 issue #4521#6690#6566;可是若是所有都改爲 macro task,對一些有重繪和動畫的場景也會有性能影響,如 issue #6813。因此最終 nextTick 採起的策略是默認走 micro task,對於一些 DOM 交互事件,如 v-on 綁定的事件回調函數的處理,會強制走 macro task。

這個強制是怎麼作的呢,原來在 Vue.js 在綁定 DOM 事件的時候,默認會給回調的 handler 函數調用 withMacroTask 方法作一層包裝,它保證整個回調函數執行過程當中,遇到數據狀態的改變,這些改變都會被推到 macro task 中。

對於 macro task 的執行,Vue.js 優先檢測是否支持原生 setImmediate,這是一個高版本 IE 和 Edge 才支持的特性,不支持的話再去檢測是否支持原生的 MessageChannel,若是也不支持的話就會降級爲 setTimeout 0

nextTick 對 audio 播放的影響

回到咱們的問題,iOS 微信瀏覽器不能播放歌曲和 nextTick 有什麼關係呢?先來看一下咱們的歌曲播放這個功能的實現方法。

咱們的代碼會有一個播放器組件 player.vue,在這個組件中咱們會持有一個 html5 的 audio 標籤。因爲可調用播放的地方不少,好比在歌曲列表組件、榜單組件、搜索結果組件等等,所以咱們用 vuex 對播放相關的數據進行管理。咱們把正在播放的列表 playlist 和當前播放索引 currentIndex 用 state 維護,當前播放的歌曲 currentSong 經過它們計算而來:

// state.js
const state = {
  playlist: [],
  currentIndex:0
}
// getters.js
export const currentSong = (state) => {
  return state.playlist[state.currentIndex] || {}
}複製代碼

而後咱們在 player.vue 組件裏 watch currentSong 的變化去播放歌曲:

// player.vue
watch : {
   currentSong(newSong,oldSong) {
      if (!newSong.id || !newSong.url || newSong.id === oldSong.id) {
          return
       }
       this.$refs.audio.src = newSong.url
       this.$refs.audio.play()
   }
}複製代碼

這樣咱們就能夠在任何組件中提交對 playlistcurrentIndex 的修改來達到播放不一樣歌曲的目的。那麼這麼寫和 nextTick 有什麼關係呢?

由於在 Vue.js 中,watcher 的回調函數執行默認是異步的,當咱們提交對 playlist 或者 currenIndex 的修改,都會觸發 currentSong 的變化,可是因爲是異步,並不會馬上執行 watcher 的回調函數,而會在 nextTick 後執行。因此當咱們點擊歌曲列表中的歌曲後,在 click 的事件回調函數中會提交對 playlistcurrentIndex 的修改, 通過一系列同步的邏輯執行,最終是在 nextTick 後纔會執行 wathcer 的回調,也就是調用 audio 的 play。

因此本質上,就是用戶點擊到 audio 的 play 並非在一個 tick 中完成,而且前面提到 Vue.js 中對 v-on 綁定事件執行的 nextTick 過程會強制使用 macro task。那麼究竟是不是因爲 nextTick 影響了 audio 在 iOS 微信瀏覽器中的播放呢,
咱們就來把化繁爲簡,寫一個簡單 demo 來驗證這個問題,用的 Vue.js 版本是 2.5+ 的。

<template>
    <div id="app">
        <audio ref="audio"></audio>
        <button @click="changeUrl">click me</button>
    </div>
</template>

<script>
    const musicList = [
    'http://ws.stream.qqmusic.qq.com/108756223.m4a?fromtag=46',
    'http://ws.stream.qqmusic.qq.com/101787871.m4a?fromtag=46',
    'http://ws.stream.qqmusic.qq.com/718475.m4a?fromtag=46'
  ]

  export default {
    name: 'app',
    data() {
      return {
        index: 0,
        url: ''
      }
    },
    methods: {
      changeUrl() {
        this.index = (this.index + 1) % musicList.length
        this.url = musicList[this.index]
      }
    },
    watch: {
      url(newUrl) {
        this.$refs.audio.src = newUrl
        this.$refs.audio.play()
      }
    }
  }
</script>複製代碼

這段代碼的邏輯很是簡單,咱們會添加一個 watcher 監聽 url 變化,當點擊按鈕的時候,會調用 changeUrl 方法,修改 url,而後 watcher 的回調函數執行,並調用 audio 的 play 方法。這段代碼在 PC 瀏覽器是能夠正常播放歌曲的,可是在 iOS 微信瀏覽器裏卻不能播放,這就證明了咱們以前的猜測——在用戶點擊事件的回調函數到 audio 的播放若是經歷了 nextTick 在 iOS 微信瀏覽器下不能播放。

macro task 的鍋?

有些同窗可能會認爲,當用戶點擊了按鈕到播放的過程在 iOS 微信瀏覽器或者是 iOS safari 瀏覽器應該須要在同一個 tick 才能執行,果然須要這樣嗎?咱們把上述代碼作一個簡單的修改:

changeUrl() {
  this.index = (this.index + 1) % musicList.length
  this.url = musicList[this.index]

  setTimeout(()=>{
    this.$refs.audio.src = this.url
    this.$refs.audio.play()
  }, 0)
}複製代碼

咱們如今不利用 Vue.js 的 nextTick 了,直接來模擬 nextTick 的過程,發現使用 setTimeout 0 是能夠在 iOS 微信瀏覽器器、包括 iOS safari 下播放的,然而實際上咱們只要在 1000ms 內的延時時間播放都是能夠的,可是超過 1000ms,好比 setTimeout 1001 又不能播放了,感興趣的同窗能夠試試,這個現象的理論依據我還沒找到,若是知道理論的同窗也很是歡迎留言告訴我。

因此經過上述的實驗,咱們發現並不必定要在同一個 tick 執行播放,那麼爲啥 Vue.js 的 nextTick 是不能夠的呢?回到 nextTick 的 macro task 的實現,它優先 setImmediate、而後 MessageChannel,最後纔是 setTimeout 0。咱們知道,除了高版本 IE 和 Edge,setImmediate 是沒有原生支持的,除非一些工具對它進行了從新改寫。而 MessageChannel 的瀏覽器支持程度仍是很是高的,那麼我把這段 demo 的異步過程改爲用 MessageChannel 實現。

changeUrl() {
  this.index = (this.index + 1) % musicList.length
  this.url = musicList[this.index]

  let channel = new MessageChannel()
  let port = channel.port2
  channel.port1.onmessage = () => {
    this.$refs.audio.src = this.url
    this.$refs.audio.play()
  }
  port.postMessage(1)
}複製代碼

這段代碼在 PC 瀏覽器是能夠播放的,而在 iOS 微信瀏覽器又不能播放了,調試後發現 this.$refs.audio.play() 的邏輯也是能夠執行到的,可是歌曲並不能播放,應該是瀏覽器對 audio 播放在使用 MessageChannel 作異步的一種限制。

前面提到實現 macro task 還有一種方法是利用 postMessage,它的瀏覽器支持程度也很好,咱們來把 demo 改爲利用它來實現。

changeUrl() {
  this.index = (this.index + 1) % musicList.length
  this.url = musicList[this.index]

  addEventListener('message', () => {
    this.$refs.audio.src = this.url
    this.$refs.audio.play()
  }, false);
  postMessage(1, '*')
}複製代碼

這段代碼在 PC 瀏覽器和 iOS 微信瀏覽器以及 iOS safari 均可以播放的,說明並非 macro task 的鍋,而是 MessageChannel 的鍋。其實 macro task 還有不少實現方式,感興趣的同窗能夠看看 core-js 中對於 macro task 的幾種實現方式

如何解決?

如今咱們定位到問題的本質是由於 Vue.js 的 nextTick 中優先使用了 MessageChannel,它會影響 iOS 微信瀏覽器的播放,那麼咱們如何用最小成原本解決這個問題呢?

Vue.js 的版本降級

若是是真實運行在生產環境中的項目,毫無疑問這確定是優先解決問題的首選,由於確實也是由於 Vue.js 的升級才形成這個 bug 的。在咱們的實際項目中,咱們都是鎖死某個 Vue.js 的版本的,除非咱們想使用某個 Vue.js 新版的 feature 或者是當前版本遇到了一個嚴重 bug 而新版已經修復的狀況,咱們纔會考慮升級 Vue.js,而且每次升級都須要通過完整的功能測試。

爲什麼把 Vue.js 降級到 2.4+ 就沒問題呢,由於 Vue.js 2.5 以前的 nextTick 都是優先使用 microtask 的,那麼 audio 播放的時機實際上仍是在當前 tick,因此固然不會有問題。

說到版本問題,其實這也是 Vue.js 的一點瑕疵吧,升版本的時候有時候改動過於激進了,好比此次關於 nextTick 的升級,它實際上是 Vue.js 一個很是核心的功能,可是它只有單元測試,並無大量的功能測試 case 覆蓋,也只能經過社區幫助反饋問題作改進了。

同步的 watcher

Vue.js 的 watcher 默認是異步的,固然它也提供了同步的 watcher,這樣 watcher 的回調函數執行就不須要經歷了 nextTick,這樣確實能夠修復這個 bug,但又會引發別的問題。由於咱們的音樂播放器有一個 feature 是能夠在播放的過程當中切換播放模式,咱們支持順序播放、隨機播放、單曲循環三種播放模式,當咱們從順序播放切到隨機播放模式的時候,其實是對播放列表 playlist 作了修改,同時也修改了 currentIndex,這樣能夠保證咱們在切換模式的時候並不會修改當前歌曲。那麼問題來了,因爲 currentSong 是由 playlistcurrentIndex 計算而來的,對它們任何一個修改,都會觸發 currentSong 的變化,因爲咱們如今改爲同步的 watcher,那麼 currentSong 的回調會執行 2 次,這樣第一次的修改致使計算出來的歌曲就變成了另一首了,這個顯然也不是咱們指望的。因此同步 watcher 也是不可行的。

其它方式

其實還有不少方式都能「修復」這個問題,好比咱們不經過 watcher,改爲每次點擊經過 event bus 去通知;好比仍然使用同步 watcher,但 currentSong 不經過計算,直接用 state 保留;好比每次點擊事件不經過 v-on 綁定,咱們直接在 mounted 的鉤子函數裏利用原生的 addEventListener 去綁定 click 事件。

固然,上述幾個方式都是可行的,可是我並不推薦這麼去改,由於這樣對業務代碼的改動實在太大了,若是咱們自己的寫法若是是合理的,卻要強行改爲這些方式,就好像是:我知道了框架的某一個坑,我用一些奇技淫巧繞過了這些坑,這樣作也是不合理的。

框架產生的意義是什麼:制定一種友好的開發規範,提高開發效率,讓開發人員更加專一業務邏輯的開發。因此優秀的框架不該該限制開發人員對於一些場景下功能的實現方式,僅僅是由於這種實現方式雖然自己合理但可能會觸發框架某個坑。

臨時的 hack 方法

因爲不想動業務代碼,因此我就想了一些比較 hack 的辦法,由於是 MessageChannel 的鍋,因此我就在 Vue.js 的初始化前,引入了一段 hack.js

// hack for global nextTick
function noop() {
}

window.MessageChannel = noop
window.setImmediate = noop複製代碼

這樣的話 Vue.js 在初始化 nextTick 的時候,發現全局的 setImmediateMessageChannel 被改寫了,就自動降級爲 setTimeout 0 的實現,這樣就能夠規避掉咱們的問題了。固然,這種 hack 方式算是沒有辦法的辦法了,我並不推薦。

給 Vue.js 提 issue

因此這種狀況最合理的就是給 Vue.js 提 issue,我確實也是這麼作了,去 Github 上提了一個 issue,第一次給 Vue.js 提 issue,發現 Vue 官方這塊作的仍是蠻人性化的,直接給一個提 issue 的連接,經過填寫一些表單來描述這個 issue,而且推薦了一個很好的復現問題的工具 CodeSandbox 。這個 issue 當天就收到了尤大的回覆,他表示 Vue.js 的 nextTick 確實會形成這個問題,可是我應該在同一個 tick 完成歌曲的播放,而不該該使用 watcher,接着就 close 了 issue。由於我提 issue 爲了更直觀的演示核心問題,用的就是上面提到的很是簡單的 demo,因此在這種場景下,他說的也沒問題,確實沒有必要使用 watcher,因而我趕忙又回覆了 issue,說明了一下個人真實使用場景,並代表但願從 Vue.js 內核去修復這個問題。惋惜的是,尤大目前也並無再回復這個 issue。

總結

經過記錄我這一次發現問題——定位問題——解決問題的過程,我想給同窗帶來的思考不只僅是這個問題自己,還有咱們遇到問題後的一些態度。發現問題並不難,不少人在寫代碼中都會發現問題,那麼發現問題後你的第一反應是嘗試本身解決,仍是去求助,我相信前者確定更好。那麼在解決以前須要定位問題,這裏我要提到一個詞,叫「面向巧合編程」,不少人遇到問題後會不斷嘗試這種辦法,極可能某個辦法就從表象上「解決」了這個問題,殊不知道爲何,這種解決問題的方式是很不靠譜的,你可能並無根本上解決問題,又可能解決了這個問題卻又引起另外一個問題。因此定位問題的本質就很是關鍵了,其實這是一個能力,一個好的工程師不只會寫代碼,也要會查問題,能快速定位到問題的本質,是一個優秀的工程師的必要條件,這一點不容易,須要平時不斷地的積累。在定位到問題的本質後,就要解決問題了,一道題每每有多解,但每種解法是否合理,這也是一個須要思考的過程,多和一些比你厲害的人交流,多積攢一些這方面的經驗,這也是一個積累的過程。若是之後你再遇到問題,也用這樣的態度去面對的問題,那麼你也會很快的成長。

不少同窗學習個人音樂課程後,會問:「黃老師,你何時再出新視頻呀?」,其實我想說這門課程你真的學完了嗎?由於它的定位是一門 Vue.js 的進階課程,不只僅是由於課程的項目自己比較複雜,並且項目中不少知識點均可以作延伸的學習,另外項目不免會有一些小 bug 和一些因爲接口改動引起的功能不可用的狀況,遇到這些問題除了給我提 issue,嘗試本身去解決而後給我提 pull request 的方式是否是對本身的提高更大呢?因此這門課程仍是值得多去挖掘的,若是真正榨乾了這門課的價值再來問我也不遲,固然我也會給大家帶來更多幹貨的課程。

最後也來小小安利個人這門 Vue.js 進階課程吧(慕課網地址),感興趣的同窗能夠點進去看看課程介紹。課程的項目是託管在個人 Github 私服的,並不開源,因此外面的一切和這個課程相關的代碼都是盜版的。這個源碼我是一直維護的,包括最近 Vue.js 的腳手架的升級,以及依賴方接口的一些改造形成的功能不可用問題,都已經獲得瞭解決。簡單地截幾張截圖:

vue-music-issue
vue-music-issue

這一張是對 issue 的處理,咱們在課程推出來後解決了幾十個 issue,若是有同窗在學習過程當中遇到問題建議去翻閱 issue 尋找答案。有一些版本的升級的 issue 我不會關,爲了讓同窗們能夠更方便的找到。

vue-music-contribute
vue-music-contribute

這一張是代碼提交記錄,能夠看到除了我仍是有一些很不錯的同窗在一塊兒維護這個項目,這其中有一個同窗學習很是主動,自驅力很強,常與我探討技術問題,最近他也加入了滴滴,在咱們部門作了不少的產出。

更直觀的感覺這個項目,能夠掃描下方的二維碼,體驗一下接近原生 App 的感受:

二維碼
二維碼

咱們有一個官方的課程交流羣,若是購買了這門課程,歡迎與其它同窗一塊兒交流學習,也能夠加個人 qq 和微信,交流技術問問題均可以,不過我通常白天很忙,晚上纔有時間。

固然,想關注個人一些動態,也歡迎 follow 個人 Github

但願同窗們一塊兒來支持正版,抵制盜版,我會爲你們帶來更多優質的課程以及其它的一些形式的技術方向的分享。

本文參考的一些值得延伸學習的文章:

JavaScript 運行機制詳解:再談Event Loop

Tasks, microtasks, queues and schedules

相關文章
相關標籤/搜索