本文不許備解析Vue源碼的運行原理,僅單純探尋vue中工具函數中那些值得學習的騷操做php
終極目標:從工具函數中擴展知識點html
inBrowser
: 檢測當前宿主環境是不是瀏覽器// 經過判斷 `window` 對象是否存在便可 export const inBrowser = typeof window !== 'undefined' 複製代碼
hasProto
:檢查當前環境是否可使用對象的 __proto__
屬性// 一個對象的 __proto__ 屬性指向了其構造函數的原型 // 從一個空的對象字面量開始沿着原型鏈逐級檢查。 export const hasProto = '__proto__' in {} 複製代碼
user Agent
常量的一系列操做user Agent
// toLowerCase目的是 爲了後續的各類環境檢測 export const UA = inBrowser && window.navigator.userAgent.toLowerCase() 複製代碼
export const isIE = UA && /msie|trident/.test(UA)
前端
解析:使用正則去匹配 UA 中是否包含'msie'
或者'trident'
這兩個字符串便可判斷是否爲 IE 瀏覽器vue
多關鍵詞高亮插件:Multi-highlight面試
IE9
| Edge
| Chrome
判斷export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 export const isEdge = UA && UA.indexOf('edge/') > 0 export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge 複製代碼
isReserved
:檢測字符串是否以 $ 或者 _ 開頭// charCodeAt() 方法可返回指定位置的字符的 Unicode 編碼 export function isReserved (str: string): boolean { const c = (str + '').charCodeAt(0) return c === 0x24 || c === 0x5F } 複製代碼
解析: 得到該字符串第一個字符的unicode
,而後與 0x24
和 0x5F
做比較。正則表達式
若做爲一個想進階中高級的前端,charCodeAt
方法的各類妙用仍是須要知道的(面試算法題各類考)。算法
Javascript
中級算法之charCodeAt
從傳遞進來的字母序列中找到缺失的字母並返回它。 如:fearNotLetter("abce") 應該返回 "d"。chrome
function fearNotLetter(str) { //將字符串轉爲ASCII碼,並存入數組 let arr=[]; for(let i=0; i<str.length; i++){ arr.push(str.charCodeAt(i)); } for(let j=1; j<arr.length; j++){ let num=arr[j]-arr[j-1]; //判斷後一項減前一項是否爲1,若不爲1,則缺失該字符的前一項 if(num!=1){ //將缺失字符ASCII轉爲字符並返回 return String.fromCharCode(arr[j]-1); } } return undefined; } fearNotLetter("abce") // "d" 複製代碼
camelize
: 連字符轉駝峯const camelizeRE = /-(\w)/g export const camelize = cached((str: string): string => { return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '') }) 複製代碼
解析: 定義正則表達式:/-(\w)/g
,用來全局匹配字符串中 中橫線及連字符後的一個字符。若捕獲到,則將字符以toUpperCase
大寫替換,不然以''
替換。 如:camelize('aa-bb') // aaBb
vue-cli
toString
: 將給定變量的值轉換爲 string 類型並返回export function toString (val: any): string { return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val) } 複製代碼
解析: 在Vue
中充斥着不少這類加強型的封裝,大大減小了咱們代碼的複雜性。但這裏,咱們要學習的是這種多重三元運算符
的用法
export function toString (val: any): string { return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val) } 複製代碼
解析:
export function toString (val: any): string { return 當變量值爲 null 時 ? 返回空字符串 : 不然,判斷當變量類型爲 object時 ? 返回 JSON.stringify(val, null, 2) : 不然 String(val) } 複製代碼
相似的操做在vue源碼裏不少。好比mergeHook
mergeHook
: 合併生命週期選項function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ): ?Array<Function> { return childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal } 複製代碼
這裏咱們不關心mergeHook
在源碼中是作什麼的(實際上是判斷父子組件有無對應名字的生命週期鉤子函數,而後將其經過 concat
合併
capitalize
:首字符大寫// 忽略cached export const capitalize = cached((str: string): string => { return str.charAt(0).toUpperCase() + str.slice(1) }) 複製代碼
解析: str.charAt(0)
獲取str
的第一項,利用toUpperCase()
轉換爲大寫字母,str.slice(1)
截取除第一項的str
部分。
hyphenate
:駝峯轉連字符const hyphenateRE = /\B([A-Z])/g export const hyphenate = cached((str: string): string => { return str.replace(hyphenateRE, '-$1').toLowerCase() }) 複製代碼
解析: 與camelize
相反。實現方式一樣是使用正則,/\B([A-Z])/g
用來全局匹配字符串中的大寫字母, 而後替換掉。
isPrimitive
: 判斷變量是否爲原型類型export function isPrimitive (value: any): boolean %checks { return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } 複製代碼
解析: 這個很簡單,但咱們常常忽略掉symbol
這個類型(雖然徹底沒用過)。
isRegExp
: 判斷變量是否爲正則對象。// 使用 Object.prototype.toString 與 '[object RegExp]' 作全等對比。 export function isRegExp (v: any): boolean { return _toString.call(v) === '[object RegExp]' } 複製代碼
這也是最準確的類型判斷方法,在Vue
中其它類型也是同樣的判斷
isValidArrayIndex
: 判斷變量是否含有效的數組索引export function isValidArrayIndex (val: any): boolean { const n = parseFloat(String(val)) // n >= 0 && Math.floor(n) === n 保證了索引是一個大於等於 0 的整數 return n >= 0 && Math.floor(n) === n && isFinite(val) } 複製代碼
isFinite
方法檢測它參數的數值。若是參數是NaN
,正無窮大或者負無窮大,會返回false
,其餘返回true
。
isObject
: 區分對象和原始值export function isObject (obj: mixed): boolean %checks { return obj !== null && typeof obj === 'object' } 複製代碼
makeMap()
:判斷一個變量是否包含在傳入字符串裏export function makeMap ( str: string, expectsLowerCase?: boolean ): (key: string) => true | void { const map = Object.create(null) const list: Array<string> = str.split(',') for (let i = 0; i < list.length; i++) { map[list[i]] = true } return expectsLowerCase ? val => map[val.toLowerCase()] : val => map[val] } 複製代碼
map
str
分隔成數組並保存到 list
變量中list
,並以list
中的元素做爲 map
的 key
,將其設置爲 true
expectsLowerCase
爲true
的話,小寫map[key]
:咱們用一個例子來講明下:
let isMyName = makeMap('前端勸退師,帥比',true); //設定一個檢測是否爲個人名字的方法,第二個參數不區分大小寫 isMyName('前端勸退師') // true isMyName('帥比') // true isMyName('醜逼') // false 複製代碼
Vue
中相似的判斷很是多,也很實用。
isHTMLTag
| isSVG
| isReservedAttr
這三個函數是經過 makeMap
生成的,用來檢測一個屬性(標籤)是否爲保留屬性(標籤)
export const isHTMLTag = makeMap( 'html,body,base,head,link,meta,style,title,' + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' + 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + 'embed,object,param,source,canvas,script,noscript,del,ins,' + 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + 'output,progress,select,textarea,' + 'details,dialog,menu,menuitem,summary,' + 'content,element,shadow,template,blockquote,iframe,tfoot' ) export const isSVG = makeMap( 'svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + 'foreignObject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', true ) // web平臺的保留屬性有 style 和 class export const isReservedAttr = makeMap('style,class') 複製代碼
once
:只調用一次的函數export function once (fn: Function): Function { let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } } } 複製代碼
解析: 以called
做爲回調標識符。調用此函數時,called
標示符改變,下次調用就無效了。也是典型的閉包調用。
cache
:建立一個緩存函數/** * Create a cached version of a pure function. */ export function cached<F: Function> (fn: F): F { const cache = Object.create(null) return (function cachedFn (str: string) { const hit = cache[str] return hit || (cache[str] = fn(str)) }: any) } 複製代碼
解析: 這裏的註釋已把做用解釋了。
const cache = Object.create(null)
建立純函數是爲了防止變化(純函數的特性:輸入不變則輸出不變)
。
在Vue中,須要轉譯不少相同的字符串,若每次都從新執行轉譯,會形成不少沒必要要的開銷。
cache
這個函數能夠讀取緩存,若是緩存中沒有就存放到緩存中,最後再讀。
looseEqual
: 檢查兩個值是否相等export function looseEqual (a: any, b: any): boolean { // 當 a === b 時,返回true if (a === b) return true // 不然進入isObject判斷 const isObjectA = isObject(a) const isObjectB = isObject(b) // 判斷是否都爲Object類型 if (isObjectA && isObjectB) { try { // 調用 Array.isArray() 方法,再次進行判斷 // isObject 不能區分是真數組仍是對象(typeof) const isArrayA = Array.isArray(a) const isArrayB = Array.isArray(b) // 判斷是否都爲數組 if (isArrayA && isArrayB) { // 對比a、bs數組的長度 return a.length === b.length && a.every((e, i) => { // 調用 looseEqual 進入遞歸 return looseEqual(e, b[i]) }) } else if (!isArrayA && !isArrayB) { // 均不爲數組,獲取a、b對象的key集合 const keysA = Object.keys(a) const keysB = Object.keys(b) // 對比a、b對象的key集合長度 return keysA.length === keysB.length && keysA.every(key => { //長度相等,則調用 looseEqual 進入遞歸 return looseEqual(a[key], b[key]) }) } else { // 若是a、b中一個是數組,一個是對象,直接返回 false /* istanbul ignore next */ return false } } catch (e) { /* istanbul ignore next */ return false } } else if (!isObjectA && !isObjectB) { return String(a) === String(b) } else { return false } } 複製代碼
這個函數比較長,建議配合註釋食用。 總之,就是
各類類型判斷+遞歸