/* @flow */ /* globals MutationObserver */ import { noop } from 'shared/util' // can we use __proto__? 有些浏览器不能让你明目张胆的使用 __proto__ export const hasProto = '__proto__' in {} // Browser environment sniffing 这里作者不太严谨, 直接用 navigator.userAget 判断浏览器
//利用 window 来检测浏览器环境 export const inBrowser = typeof window !== 'undefined'
export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
//IE的内核是trident export const isIE = UA && /msie|trident/.test(UA) export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 export const isEdge = UA && UA.indexOf('edge/') > 0
//还可以这样来判断 android ios export const isAndroid = UA && UA.indexOf('android') > 0 export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
//判断chrome export const isChrome = UA && /chrome/d+/.test(UA) && !isEdge // this needs to be lazy-evaled because vue may be required before // vue-server-renderer can set VUE_ENV
// 这个需求需要延迟加载, 因为在 vue服务器渲染设置VUE_ENV环境之前, 需要先加载vue let _isServer export const isServerRendering = () => { if (_isServer === undefined) { /* istanbul ignore if */ if (!inBrowser && typeof global !== 'undefined') { // detect presence of vue-server-renderer and avoid // Webpack shimming the process
//检测 vue的服务器渲染是否存在, 而且避免webpack去填充process _isServer = global['process'].env.VUE_ENV === 'server' } else { _isServer = false } } return _isServer } // detect devtools 输出vue的工具方法的全局钩子 export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__ /* istanbul ignore next */
//这里判断 函数是否是系统函数, 比如 Function Object ExpReg window document 等等, 这些函数应该使用c/c++实现的
//这样可以区分 Symbol是系统函数, 还是用户自定义了一个Symbol, 下面这个函数可以看出来 export function isNative (Ctor: Function): boolean { return /native code/.test(Ctor.toString()) }
//这里使用了ES6的Reflect方法, 使用这个对象的目的是, 为了保证访问的是系统的原型方法,
// ownKeys 保证key的输出顺序, 先数组 后字符串 export const hasSymbol = typeof Symbol !== 'undefined' && isNative(Symbol) && typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys) /** * Defer a task to execute it asynchronously.
延迟一个任务, 异步执行; 在node.js中, next会在setTimeout之前执行, 也就是在当前执行栈之后, 事件队列之前执行
比如在同一个事件循环中, 反复设置一个vm的值, 最后只会执行 一次对应UI的更新.
JS 的 event loop 执行时会区分 task 和 microtask,引擎在每个 task 执行完毕,
从队列中取下一个 task 来执行之前,会先执行完所有 microtask 队列中的事件。
setTimeout 回调会被分配到一个新的 task 中执行,而 Promise 的 resolver、MutationObserver 的回调都会被安排到一个新的 microtask 中执行,
用 microtask?根据 HTML Standard,在每个 task 运行完以后,UI 都会重渲染,
那么在 microtask 中就完成数据更新,当前 task 结束就可以得到最新的 UI 了。
反之如果新建一个 task 来做数据更新,那么渲染就会进行两次。所以优先不使用task
*/ export const nextTick = (function () { const callbacks = [] let pending = false let timerFunc
//在适当的时机调用 nextTickHnadleer
function nextTickHandler () { pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() } }
/* var a = [1,2,3];
var b = a.slice(0)
b[0] = 22 ; a[0] => 1
这里完成了数组的浅复制, 注意这种slice不能完成数组的深度复制
*/
//举例来说,如果在文档中连续插入1000个段落(p元素),会连续触发1000个插入事件,执行每个事件的回调函数,这很可能造成浏览器的卡顿;
// 而Mutation Observer完全不同,只在1000个段落都插入结束后才会触发,而且只触发一次。
// the nextTick behavior leverages the microtask queue, which can be accessed // via either native Promise.then or MutationObserver. // MutationObserver has wider support, however it is seriously bugged in // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It // completely stops working after triggering a few times... so, if native // Promise is available, we will use it: /* istanbul ignore if */
//这里的nextTick是利用了事件队列,
// MutationsObserver 在 IOS的底层方法UIWebView 会有几个bug, 比如touch事件, 或者在一个事件触发几次以后, 它就懒的工作了
//所以我们优先使用 Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) {
//这种写法是一个语法糖 var p = Promise.resolve()
var logError = err => { console.error(err) }
timerFunc = () => {
//不知道then底层是怎么实现de, 如果模拟then也可用 setTimeout(fn,0)方法 p.then(nextTickHandler).catch(logError)
// 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.
//在有问题的IOS中, promise的then方法不能完全断开? 不能异步?
// 当回调函数进入到队列后, 它会卡在一个奇怪的状态, 不会刷新, 知道浏览器需要处理其他任务, 比如timeer
// 需要利用timeq 强制刷新任务队列, 并执行 if (isIOS) setTimeout(noop)
}
} else if (typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || // PhantomJS and iOS 7.x MutationObserver.toString() === '[object MutationObserverConstructor]'
)) { // use MutationObserver where native Promise is not available, // e.g. PhantomJS IE11, iOS7, Android 4.4
var counter = 1 var observer = new MutationObserver(nextTickHandler) var textNode = document.createTextNode(String(counter)) observer.observe(textNode, { characterData: true })
//重新设置 textNode的data属性, 让Mutaiont检查到变化后, 执行异步调用 timerFunc = () => { counter = (counter + 1) % 2 textNode.data = String(counter) } } else { // fallback to setTimeout /* istanbul ignore next */ timerFunc = () => { setTimeout(nextTickHandler, 0) } }
//最终返回的这个函数, 其实会执行 nextTickHandler方法, 从而执行各类的回调函数 return function queueNextTick (cb?: Function, ctx?: Object) { let _resolve
callbacks.push(() => {
if (cb) cb.call(ctx) if (_resolve) _resolve(ctx)
})
if (!pending) {
pending = true timerFunc() }
if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } } })()
let _Set
/* istanbul ignore if */ if (typeof Set !== 'undefined' && isNative(Set)) { // use native Set when available. _Set = Set } else { // a non-standard Set polyfill that only works with primitive keys. 设置一个简单的Set, 只支持 _Set = class Set { set: Object; constructor () { this.set = Object.create(null) } has (key: string | number) { return this.set[key] === true } add (key: string | number) { this.set[key] = true } clear () { this.set = Object.create(null) } } }
export { _Set }