亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

深入探究Vue中$nextTick的實現(xiàn)原理

 更新時間:2023年06月21日 10:51:13   作者:雅伊  
這篇文章主要為大家詳細介紹Vue中$nextTick的實現(xiàn)原理,文中的示例代碼講解詳細,對我們深入了解Vue有一定的幫助,需要的小伙伴可以參考一下

前言

Vue 實現(xiàn)響應式并不是數(shù)據(jù)發(fā)生變化之后 DOM 立即變化,而是按一定的策略進行 DOM 的更新。

Vue在修改數(shù)據(jù)后,視圖不會立刻更新,而是等同一事件循環(huán)中的所有數(shù)據(jù)變化完成之后,再統(tǒng)一進行視圖更新。

所以如果你用一個for循環(huán)來動態(tài)改變數(shù)據(jù)100次,實際上它只會應用最后一次改變,如果沒有這種機制,DOM就進行 100 次的重繪,這固然是一個很大的開銷。

而這里就涉及 Vue 一個重要的概念:異步更新隊列。

如果想要在修改數(shù)據(jù)之后立即獲取更新后的 DOM 可以使用 $nextTick。

今天就來看看 $nextTick 是如何實現(xiàn)的吧?。?!

JS 運行機制 (Event Loop)

JS 執(zhí)行是單線程的,它是基于事件循環(huán)的。事件循環(huán)大致分為以下幾個步驟:

  • 所有同步任務都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。
  • 主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
  • 一旦"執(zhí)行棧"中的所有同步任務執(zhí)行完畢,系統(tǒng)就會讀取"任務隊列",看看里面有哪些事件。那些對應的異步任務,于是結束等待狀態(tài),進入執(zhí)行棧,開始執(zhí)行。
  • 主線程不斷重復上面的第三步。

這里主線程的執(zhí)行過程就是一個tick,而所有的異步結果都是通過任務隊列來調度。

Loop 分為宏任務和微任務,無論是執(zhí)行宏任務還是微任務,完成后都會進入到一下tick,并在兩個tick之間進行UI渲染。

由于Vue DOM更新是異步執(zhí)行的,即修改數(shù)據(jù)時,視圖不會立即更新,而是會監(jiān)聽數(shù)據(jù)變化,并緩存在同一事件循環(huán)中,等同一數(shù)據(jù)循環(huán)中的所有數(shù)據(jù)變化完成之后,再統(tǒng)一進行視圖更新。為了確保得到更新后的DOM,所以設置了 Vue.nextTick()方法。

異步執(zhí)行的任務又分為宏任務、微任務

$nextTick

是Vue的核心方法之一,官方文檔解釋如下:

在下次DOM更新循環(huán)結束之后執(zhí)行延遲回調。在修改數(shù)據(jù)之后立即使用這個方法,獲取更新后的DOM。

nextTick 原理

Vue是異步執(zhí)行dom更新的,一旦觀察到數(shù)據(jù)變化,Vue就會開啟一個隊列,然后把在同一個事件循環(huán) (event loop) 當中觀察到數(shù)據(jù)變化的 watcher 推送進這個隊列。如果這個watcher被觸發(fā)多次,只會被推送到隊列一次。這種緩沖行為可以有效的去掉重復數(shù)據(jù)造成的不必要的計算和Dom操作。而在下一個事件循環(huán)時,Vue會清空隊列,并進行必要的DOM更新。

當你設置 vm.someData = ‘new value’,DOM 并不會馬上更新,而是在異步隊列被清除,也就是下一個事件循環(huán)開始時執(zhí)行更新時才會進行必要的DOM更新。如果此時你想要根據(jù)更新的 DOM 狀態(tài)去做某些事情,就會出現(xiàn)問題。為了在數(shù)據(jù)變化之后等待 Vue 完成更新 DOM ,可以在數(shù)據(jù)變化之后立即使用 Vue.nextTick(callback) 。這樣回調函數(shù)在 DOM 更新完成后就會調用。

源碼淺析

nextTick 的實現(xiàn)單獨有一個JS文件來維護它,在src/core/util/next-tick.js中。

nextTick 源碼主要分為兩塊:能力檢測和根據(jù)能力檢測以不同方式執(zhí)行回調隊列。

watcher 觸發(fā) nextTick

當數(shù)據(jù)發(fā)生變化時 dep 通知 watcher 執(zhí)行 update 更新

/**
 * 根據(jù) watcher 配置項,決定接下來怎么走,一般是 queueWatcher
 */
update () {
  /* istanbul ignore else */
  if (this.lazy) {
    // 懶執(zhí)行時走這里,比如 computed
    // 將 dirty 置為 true,可以讓 computedGetter 執(zhí)行時重新計算 computed 回調函數(shù)的執(zhí)行結果
    this.dirty = true
  } else if (this.sync) {
    // 同步執(zhí)行,在使用 vm.$watch 或者 watch 選項時可以傳一個 sync 選項,
    // 當為 true 時在數(shù)據(jù)更新時該 watcher 就不走異步更新隊列,直接執(zhí)行 this.run 
    // 方法進行更新
    // 這個屬性在官方文檔中沒有出現(xiàn)
    this.run()
  } else {
    // 更新時一般都這里,將 watcher 放入 watcher 隊列
    queueWatcher(this)
  }
}

queueWatcher

/**
 * 將 watcher 放入 watcher 隊列 
 */
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  // 如果 watcher 已經(jīng)存在,則跳過,不會重復入隊
  if (has[id] == null) {
    // 緩存 watcher.id,用于判斷 watcher 是否已經(jīng)入隊
    has[id] = true
    if (!flushing) {
      // 當前沒有處于刷新隊列狀態(tài),watcher 直接入隊
      queue.push(watcher)
    } else {
      // 已經(jīng)在刷新隊列了
      // 從隊列末尾開始倒序遍歷,根據(jù)當前 watcher.id 找到它大于的 watcher.id 的位置,然后將自己插入到該位置之后的下一個位置
      // 即將當前 watcher 放入已排序的隊列中,且隊列仍是有序的
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1, 0, watcher)
    }
    // queue the flush
    if (!waiting) {
      // waiting = false 表示當前瀏覽器的異步任務隊列沒有 flushSchedulerQueue 
      waiting = true
      if (process.env.NODE_ENV !== 'production' && !config.async) {
        // 直接去刷新 watcher 調度隊列 
        // 一般不會走這兒,Vue 默認是異步執(zhí)行,如果改為同步執(zhí)行,性能會大打折扣
        flushSchedulerQueue()
        return
      }
      /**
       *   熟悉的 nextTick => vm.$nextTick、Vue.nextTick
       *   1、將 回調函數(shù)(flushSchedulerQueue) 放入 callbacks 數(shù)組
       *   2、通過 pending 控制向瀏覽器任務隊列中添加 flushCallbacks 函數(shù)
       */
      nextTick(flushSchedulerQueue)
    }
  }
}

watcher 更新時 watcher 依次入隊,避免 watcher 重復入隊

$nextTick 本質是 promise

export function nextTick(cb? Function, ctx: Object) {
    let _resolve
    // cb 即為傳入的 flushSchedulerQueue函數(shù),會統(tǒng)一處理壓入callbacks數(shù)組
    // callbacks也就是異步操作隊列
    callbacks.push(() => {
        if(cb) {
            try {
                cb.call(ctx)  // 調用 flushSchedulerQueue 函數(shù),將其放入 callbacks異步隊列
            } catch(e) {
                handleError(e, ctx, 'nextTick')
            }
        } else if (_resolve) {
            _resolve(ctx)
        }
    })
    // pending 為false 說明本輪事件循環(huán)中沒有執(zhí)行過timerFunc()
    if(!pending) {
        pending = true
        timerFunc()
    }
    // 當不傳入 cb 參數(shù)時,提供一個promise化的調用 
    if(!cb && typeof Promise !== 'undefined') {
      // 如nextTick().then(() => {})
      // 當_resolve執(zhí)行時,就會跳轉到then邏輯中
        return new Promise(resolve => {
            _resolve = resolve
        })
    }
}

next-tick.js 對外暴露了 nextTick 這個參數(shù),所以每次調用 Vue.nextTick 時:

會把傳入的回調函數(shù) cb 壓入 callbacks 數(shù)組執(zhí)行 timerFunc 函數(shù),延遲調用 flushCallbacks 函數(shù)遍歷執(zhí)行 callbacks 數(shù)組中的所有函數(shù)

這里的 callbacks 沒有直接在 nextTick 中執(zhí)行回調函數(shù)的原因是保證在同一個 tick 內多次執(zhí)行nextTick,不會開啟多個異步任務,而是把這些異步任務都壓成一個同步任務,在下一個 tick 執(zhí)行完畢。

MutationObserver

先簡單介紹下 MutationObserver:MO 是 HTML5 中的 API,用于監(jiān)視 DOM 變動的接口,它可以用于監(jiān)控任何 DOM 節(jié)點的變更如子節(jié)點的刪除、屬性的修改、文本內容修改等。

調用過程是要先給它綁定回調,得到 MO 實例,這個回調會在 MO 實例監(jiān)聽到變動時觸發(fā)。 這里 MO 的回調是放在microtask中執(zhí)行的。

// 創(chuàng)建MO實例
const observer = new MutationObserver(callback)
const textNode = '想要監(jiān)聽的Don節(jié)點'
observer.observe(textNode, {
  characterData: true // 說明監(jiān)聽文本內容的修改
})

timerFunc

由于宏任務耗費的時間是大于微任務的,所以在瀏覽器支持的情況下,優(yōu)先使用微任務。如果瀏覽器不支持微任務,再使用宏任務。

源碼路徑位于:src\core\util\next-tick.js

/**
 * flushCallbacks 做了三件事:
 *   1、將 pending 置為 false  表示下一個 flushCallbacks() 可以進入瀏覽器異步任務隊列
 *   2、清空 callbacks 數(shù)組
 *   3、執(zhí)行 callbacks 數(shù)組中的所有函數(shù)(比如 flushSchedulerQueue、用戶調用 nextTick 傳遞的回調函數(shù))
 */
// 對callbacks進行遍歷,然后執(zhí)行相應的回調函數(shù)
function flushCallbacks () {
    pending = false
    // 這里拷貝的原因是:
    // 有的cb 執(zhí)行過程中又會往 callbacks 中加入內容
    // 比如 $nextTick的回調函數(shù)里還有$nextTick
    // 后者的應該放到下一輪的nextTick 中執(zhí)行
    // 所以拷貝一份當前的,遍歷執(zhí)行完當前的即可,避免無休止的執(zhí)行下去
    const copies = callbcks.slice(0)
    callbacks.length = 0 // callbacks里面的函數(shù)
    for(let i = 0; i < copies.length; i++) {
        copies[i]() // 執(zhí)行 callbacks 數(shù)組中的每一個函數(shù)
    }
}
let timerFunc // 異步執(zhí)行函數(shù) 用于異步延遲調用 flushCallbacks 函數(shù)
// 優(yōu)先使用 Promise  首選 Promise.resolve().then()
if(typeof Promise !== 'undefined' && isNative(Promise)) {
    const p = Promise.resolve()
   // 首選 Promise.resolve().then()
    timerFunc = () => {
        // 在 微任務隊列 中放入 flushCallbacks 函數(shù) 
        p.then(flushCallbacks)
        // IOS 的UIWebView, Promise.then 回調被推入 microTask 隊列,但是隊列可能不會如期執(zhí)行
        // 因此,添加一個空計時器強制執(zhí)行 microTask
        if(isIOS) setTimeout(noop)
    }
    isUsingMicroTask = true
} else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) || MutationObserver.toString === '[object MutationObserverConstructor]')) {
    // 當 原生 Promise 不可用時,使用 原生 MutationObserver
    let counter = 1
    // 創(chuàng)建MO實例,監(jiān)聽到DOM變動后會執(zhí)行回調flushCallbacks
    const observer = new MutationObserver(flushCallbacks)
    const textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
        characterData: true // 設置true 表示觀察目標的改變
    })
    // 每次執(zhí)行timerFunc 都會讓文本節(jié)點的內容在 0/1之間切換
    // 切換之后將新值復制到 MO 觀測的文本節(jié)點上
    // 節(jié)點內容變化會觸發(fā)回調
    timerFunc = () => {
        counter = (counter + 1) % 2
        textNode.data = String(counter) // 觸發(fā)回調
    }
    isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
   // setImmediate 宏任務隊列,  setImmediate 性能優(yōu)于 setTimeout 將flushCallbacks 放入瀏覽器的異步任務隊列
    timerFunc = () => {
        setImmediate(flushCallbacks)
    }
} else {
    timerFunc = () => {
        setTimeout(flushCallbacks, 0)
    }
}

關鍵在于timeFunc(),該函數(shù)起到延遲執(zhí)行的作用。

nextTick 的目的就是產(chǎn)生一個回調函數(shù)加入 task 或者 microtask 中,當前棧執(zhí)行完以后(可能中間還有別的排在前面的函數(shù))調用該回調函數(shù),起到了異步觸發(fā)(即下一個tick時觸發(fā))的目的。

timeFunc() 有四種實現(xiàn):

1.promise:

如果瀏覽器支持Promise,那么就用 Promise.then 的方式來延遲函數(shù)調用,Promise.then() 可以將函數(shù)延遲到當前函數(shù)調用棧最末端,也就是函數(shù)調用棧最后調用該函數(shù)。從而做到延遲

2.MutationObserver

MutationObserver 是H5 新加的一個功能,其功能是監(jiān)聽 DOM 節(jié)點的變動,在所有 DOM 變動完成后,執(zhí)行回調函數(shù)。

3.setImmediate、setTimeout

利用 setImmediate、setTimeout 的延遲原理,setTimeout(func, 0)會將func函數(shù)延遲到下一次函數(shù)調用棧的開始,也就是當前函數(shù)執(zhí)行完畢后再執(zhí)行該函數(shù),因此完成了延遲功能。

**而 setImmediate 性能優(yōu)于 setTimeout **

延遲調用優(yōu)先級如下:

Promise.then > MutationObserver > setImmediate > setTimeout

為什么優(yōu)先使用 microtack

JS 的 event loop 執(zhí)行時會區(qū)分 task 和 microtask,引擎在每個 task 執(zhí)行完畢,從隊列中取下一個 task 來執(zhí)行之前,會先執(zhí)行完所有 microtask 隊列中的 microtask。

setTimeout 回調會被分配到一個新的 task 中執(zhí)行,而 Promise 的 resolver、MutationObserver 的回調都會被安排到一個新的 microtask 中執(zhí)行,會比 setTimeout 產(chǎn)生的 task 先執(zhí)行。

要創(chuàng)建一個新的 microtask,優(yōu)先使用 Promise,如果瀏覽器不支持,再嘗試 MutationObserver。

實在不行,只能用 setTimeout 創(chuàng)建 task 了。

為啥要用 microtask? 根據(jù) HTML Standard,在每個 task 運行完以后,UI 都會重渲染,那么在 microtask 中就完成數(shù)據(jù)更新,當前 task 結束就可以得到最新的 UI 了。

反之如果新建一個 task 來做數(shù)據(jù)更新,那么渲染就會進行兩次。

flushCallbacks

依次執(zhí)行callbacks中的函數(shù)

/**
 * 做了三件事:
 *   1、將 pending 置為 false  表示下一個 flushCallbacks() 可以進入瀏覽器異步任務隊列
 *   2、清空 callbacks 數(shù)組
 *   3、執(zhí)行 callbacks 數(shù)組中的所有函數(shù)(比如 flushSchedulerQueue、用戶調用 nextTick 傳遞的回調函數(shù))
 */
function flushCallbacks () {
  // 微任務隊列中只能有一個 flushCallbacks()
  pending = false
  const copies = callbacks.slice(0) // 清空 callbacks 數(shù)組
  callbacks.length = 0
  for (let i = 0; i < copies.length; i++) {
    copies[i]()
  }
}

callbacks 異步隊列中存放了 flushSchedulerQueue 函數(shù),

在 flashcallbacks() 刷新隊列的過程中依次執(zhí)行 callbacks 數(shù)組中的 flushSchedulerQueue 函數(shù)

flushSchedulerQueue

刷新隊列,保證了 watcher 的父子關系順序,依次執(zhí)行 watcher 的 run 方法

function flushSchedulerQueue () {
  currentFlushTimestamp = getNow()
  //  flushing = true 表示 watcher 隊列正在被刷新
  flushing = true
  let watcher, id
  // 確保 watcher 的先后順序
  // watcher 隊列排序  watcher 的id 由小到大有序遞增  保證組件更新順序  
  // 父組件的 watcher 先于子組件的 watcher 更新
  // 用戶 watcher 先于 渲染 watcher 更新
  // 父組件的 watcher 在執(zhí)行時 子組件的 watcher 銷毀 可以跳過子組件的執(zhí)行 
  queue.sort((a, b) => a.id - b.id)
  //  for 循環(huán)遍歷 watcher 隊列  依次執(zhí)行 watcher 的 run 方法
  // queue.length 動態(tài)計算 確保是最新的隊列
  for (index = 0; index < queue.length; index++) {
    // 拿出當前索引的 watcher
    watcher = queue[index]
    // 首先執(zhí)行 brfore 鉤子  執(zhí)行beforeUpdate()
    if (watcher.before) {
      watcher.before() // watcher 實例化時 傳入的 before 方法
    }
    // 清空緩存 表示當前 watcher 已經(jīng)被執(zhí)行  當該 watcher 再次入隊時可以進入
    id = watcher.id
    has[id] = null
    // 執(zhí)行 watcher 的run() 
    watcher.run()
    if (process.env.NODE_ENV !== 'production' && has[id] != null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break
      }
    }
  }
  // keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()
  resetSchedulerState()
  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)
  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')
  }
}

flushSchedulerQueue() 主要負責依次執(zhí)行 queue 數(shù)組中每個 watcher 的 run 方法,從而進入更新階段。

刷新隊列,由 flushCallbacks 函數(shù)負責調用,主要做了如下兩件事:

  • 更新 flushing 為 ture,表示正在刷新隊列,在此期間往隊列中 push 新的 watcher 時需要特殊處理(將其放在隊列的合適位置)
  • 按照隊列中的 watcher.id 從小到大排序,保證先創(chuàng)建的 watcher 先執(zhí)行,也配合 第一步
  • 遍歷 watcher 隊列,依次執(zhí)行 watcher.before、watcher.run,并清除緩存的 watcher

$nextTick 的應用場景

1.在Vue生命周期的created()鉤子函數(shù)進行的DOM操作一定要放在nextTick的回調函數(shù)中。

原因是created()鉤子函數(shù)執(zhí)行時DOM其實并未進行渲染。

2.在數(shù)據(jù)變化后要執(zhí)行的某個操作,而這個操作需要使用隨數(shù)據(jù)改變而改變的 DOM 結構的時候,這個操作應該放在nextTick()的回調函數(shù)中。

因為:Vue異步執(zhí)行DOM更新,只要觀察到數(shù)據(jù)變化,Vue將開啟一個隊列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)改變,如果同一個 watcher 被多次觸發(fā),只會被推入到隊列中一次。

總結

使用nextTick()是為了可以獲取更新后的DOM 。

觸發(fā)時機:在同一事件循環(huán)中的數(shù)據(jù)變化后,DOM完成更新,立即執(zhí)行nextTick()的回調。

同一事件循環(huán)中的代碼執(zhí)行完畢 -> DOM 更新 -> nextTick callback觸發(fā)

Vue 的 nextTick 是如何實現(xiàn)

  • 將傳遞的回調函數(shù)放入 callbacks 全局數(shù)組中
  • 調用 timerFunc 函數(shù),在瀏覽器的異步隊列中放入刷新callbacks(flashcallbacks ) 函數(shù) ,延遲執(zhí)行 (根據(jù)運行環(huán)境判斷將flashcallbacks() 放入宏任務或者是微任務隊列, 使得 flashcallbacks 被延遲調用)
  • 事件循環(huán)到了微任務或者宏任務,依次遍歷執(zhí)行 callbacks 數(shù)組中的所有函數(shù)

到此這篇關于深入探究Vue中$nextTick的實現(xiàn)原理的文章就介紹到這了,更多相關Vue $nextTick內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • vue params、query傳參使用詳解

    vue params、query傳參使用詳解

    本篇文章主要介紹了vue params、query傳參使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • 實例詳解vue.js淺度監(jiān)聽和深度監(jiān)聽及watch用法

    實例詳解vue.js淺度監(jiān)聽和深度監(jiān)聽及watch用法

    這篇文章主要介紹了vue.js淺度監(jiān)聽和深度監(jiān)聽及watch用法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友參考下吧
    2018-08-08
  • vue實現(xiàn)拖動左側導航欄變大變小

    vue實現(xiàn)拖動左側導航欄變大變小

    這篇文章主要為大家詳細介紹了vue實現(xiàn)拖動左側導航欄變大變小,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Vue項目中如何安裝element組件

    Vue項目中如何安裝element組件

    這篇文章主要介紹了Vue項目中如何安裝element組件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • vue使用原生swiper代碼實例

    vue使用原生swiper代碼實例

    這篇文章主要介紹了vue使用原生swiper代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-02-02
  • 在vue項目如何使用base64加密

    在vue項目如何使用base64加密

    這篇文章主要介紹了在vue項目如何使用base64加密,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • VUE2.0+Element-UI+Echarts封裝的組件實例

    VUE2.0+Element-UI+Echarts封裝的組件實例

    下面小編就為大家分享一篇VUE2.0+Element-UI+Echarts封裝的組件實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • vue3編寫帶提示的表格組件功能

    vue3編寫帶提示的表格組件功能

    本文介紹了如何使用Vue 3編寫一個帶提示的表格組件,并假設每行都有一個保存按鈕,如果需要全部保存,還會加上驗證,感興趣的朋友一起看看吧
    2025-02-02
  • vue+springboot實現(xiàn)登錄功能

    vue+springboot實現(xiàn)登錄功能

    這篇文章主要為大家詳細介紹了vue+springboot實現(xiàn)登錄功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • VUE 使用中踩過的坑

    VUE 使用中踩過的坑

    本篇是我對vue使用過程中以及對一些社區(qū)朋友提問我的問題中做的一些總結,感興趣的朋友跟隨腳本之家小編一起學習吧
    2018-02-02

最新評論