/* @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 中执行,

    会比 setTimeout 产生的 task 先执行    

    用 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 }