/* @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 }