Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

22 实现消息队列 #24

Open
xwjie opened this issue Jan 24, 2018 · 0 comments
Open

22 实现消息队列 #24

xwjie opened this issue Jan 24, 2018 · 0 comments

Comments

@xwjie
Copy link
Owner

xwjie commented Jan 24, 2018

参考通过microtasks和macrotasks看JavaScript异步任务执行顺序【朴灵评注】JavaScript 运行机制详解:再谈Event Loop 和 vue的源码next-tick.js。

macrotasks和microtasks的划分:

macrotasks:

  • setTimeout
  • setInterval
  • setImmediate
  • requestAnimationFrame
  • I/O
  • UI rendering

microtasks:

  • process.nextTick
  • Promises
  • Object.observe
  • MutationObserver

处理机制

  1. 一个事件循环有一个或者多个任务队列;
  2. 每个事件循环都有一个microtask队列
  3. macrotask队列就是我们常说的任务队列,microtask队列不是任务队列
  4. 一个任务可以被放入到macrotask队列,也可以放入microtask队列
  5. 当一个任务被放入microtask或者macrotask队列后,准备工作就已经结束,这时候可以开始执行任务了。

通俗的解释一下,microtasks的作用是用来调度应在当前执行的脚本执行结束后立即执行的任务。 例如响应事件、或者异步操作,以避免付出额外的一个task的费用。

microtask会在两种情况下执行:

任务队列(macrotask = task queue)回调后执行,前提条件是当前没有其他执行中的代码。
每个task末尾执行。
另外在处理microtask期间,如果有新添加的microtasks,也会被添加到队列的末尾并执行。

也就是说执行顺序是:

开始 -> ** 取task queue第一个task执行 -> 取microtask全部任务依次执行 -> 取task queue下一个任务执行 -> 再次取出microtask全部任务执行 ->** … 这样循环往复

以上文本都引自上面的文章。

之前的测试代码问题

<div id="demo1">
{{message}} + {{message2}}  + {{message3}}
</div>

<script>
var vm = new Xiao({
  el: '#demo1',
  data: {
    message: '123',
    message2: '456',
    message3: '789'
  }
})

vm.message = 'new message1' // 更改数据
console.log(vm.$el.textContent)

setTimeout(function(){
	vm.message = 'new message2' // 更改数据
	vm.message2 = '89';
	vm.message3 = '222';
	console.log(vm.$el.textContent)
}, 1000);

</script>

之前的代码里面,更新了三次属性,那么就会render三次,使用了event loop的异步队列之后,只会更新一次。

实现

wathcer 类update的时候,放入队列。

export default class Watcher {
  update() {
    log(`[Watcher${this.id}] update`)

    // fixme
    // this.get();
    // 放队列里面执行
    queueWatcher(this)
  }
}

队列实现

import Watcher from './watcher'
import { log, logstart, logend, nextTick } from '../util'

// 当前已有的id队列
let has: { [key: number]: ?true } = {}

// 队列
const queue: Array<Watcher> = []

// 是否正在执行队列刷新
let flushing = false

// 是否等待刷新
let waiting = false

function flushSchedulerQueue() {
  logstart(`flushSchedulerQueue, queue size: ${queue.length}`)
  let watcher, id
  flushing = true

  queue.sort((a, b) => a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (let index = 0; index < queue.length; index++) {
    watcher = queue[index]
    id = watcher.id
    has[id] = null
    watcher.get()
  }

  // 清空
  queue.length = 0

  flushing = false
  waiting = false

  logend()
}

export function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  log('queueWatcher', id)

  // 队列里面没有
  if (has[id] == null) {
    has[id] = true
    queue.push(watcher)
  }

  // 防止重复提交
  if (!waiting) {
    waiting = true
    nextTick(flushSchedulerQueue)
  }
}

nextTick实现

代码来自 vue

使用的队列是 微任务队列 MicroTask,实现是用 Promise.resolve() 的回调函数会加入 microtask 来实现的。microtask的优先级会更加高(准确的说更加快的执行)。

setImmediate 是ie才有的。MessageChannel是h5有的,这2个的任务都只能放到 `macrotask‘ (大任务队列?),只有 Promise的才能放到 microtask 里面。

  const p = Promise.resolve()
  microTimerFunc = () => {
    p.then(flushCallbacks)
  }

如果不是h5,那只能用 setTimeout(flushCallbacks, 0) 了。默认有4毫秒延迟,而且是异步的。

import { isNative } from './typeutil'
import {log} from './debug'

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)
  }
} 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) {
  log('nextTick' , cb)

  let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        console.log('nextTick', e)
      }
    } 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
    })
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant