- 源碼中使用了typescript語法 export導出 本地練習時因爲沒有配置環境先註釋掉
// 判斷當前環境
// 1.1inBrowser: 檢測當前宿主環境是否是瀏覽器
// 通過判斷 `window` 對象是否存在即可
// export const inBrowser = typeof window !== 'undefined'
const inBrowser = typeof window !== 'undefined'
console.log(inBrowser); // true
console.log(typeof window); // object
// 1.2 hasProto:檢查當前環境是否可以使用對象的 __proto__ 屬性
// 一個對象的 __proto__ 屬性指向了其構造函數的原型
// 從一個空的對象字面量開始沿着原型鏈逐級檢查。
// export const hasProto = '__proto__' in {}
const hasProto = '__proto__' in {}
console.log(hasProto); // true
// 2.1 獲取當瀏覽器的user Agent
// toLowerCase目的是 爲了後續的各種環境檢測
// export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
const UA = inBrowser && window.navigator.userAgent.toLowerCase()
console.log(UA); // mozilla/5.0 (windows nt 10.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/68.0.3440.106 safari/537.36
// 2.2 IE瀏覽器判斷
// 解析:使用正則去匹配 UA 中是否包含'msie'或者'trident'這兩個字符串即可判斷是否爲 IE 瀏覽器
// export const isIE = UA && /msie|trident/.test(UA)
const isIE = UA && /msie|trident/.test(UA);
console.log(isIE); // false 用的chrome
// 2.3 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
// 字符串操作
// 3.1 isReserved:檢測字符串是否以 $ 或者 _ 開頭
// charCodeAt() 方法可返回指定位置的字符的 Unicode 編碼
// export function isReserved (str: string): boolean { // TS語法
// const c = (str + '').charCodeAt(0)
// return c === 0x24 || c === 0x5F
// }
// 解析: 獲得該字符串第一個字符的unicode,然後與 0x24 和 0x5F 作比較。
function isReserved (str) {
const c = (str + '').charCodeAt(0)
return c === 0x24 || c === 0x5F
}
console.log(isReserved('_abc')); // true
console.log(isReserved('$abc')); // true
console.log(isReserved('abc')); // false
// 3.2 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
const camelizeRE = /-(\w)/g
const camelize = cached((str) => {
return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : '')
})
console.log(camelize('abc-asd')); // cached暫未定義
// hyphenate:駝峯轉連字符
const hyphenateRE = /\B([A-Z])/g
export const hyphenate = cached((str: string): string => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
})
// 3.3 toString: 將給定變量的值轉換爲 string 類型並返回
// export function toString (val: any): string {
// return val == null
// ? ''
// : typeof val === 'object'
// ? JSON.stringify(val, null, 2) // 使用2個空格縮進
// : String(val)
// }
function toString (val) {
return val == null
? ''
: typeof val === 'object'
? JSON.stringify(val, null, 2) // 使用2個空格縮進
: String(val)
}
console.log(toString({})) // '{}'
console.log(toString(null)) // ''
console.log(toString(123)) // '123'
// 3.3.2 mergeHook: 合併生命週期選項
// 多元運算符的運用 換行對其 方便讀寫
function mergeHook (
parentVal: ?Array<Function>,
childVal: ?Function | ?Array<Function>
): ?Array<Function> {
return childVal
? parentVal
? parentVal.concat(childVal)
: Array.isArray(childVal)
? childVal
: [childVal]
: parentVal
}
// 3.4 capitalize:首字符大寫
// 忽略cached
export const capitalize = cached((str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1)
})
// 4. 類型判斷
// 4.1 isPrimitive: 判斷變量是否爲原型類型
export function isPrimitive (value: any): boolean %checks {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
// 4.2 isRegExp: 判斷變量是否爲正則對象。
// 使用 Object.prototype.toString 與 '[object RegExp]' 做全等對比。
export function isRegExp (v: any): boolean {
return _toString.call(v) === '[object RegExp]'
}
// 4.3 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)
}
// 4.4 isObject: 區分對象和原始值
export function isObject (obj: mixed): boolean %checks {
return obj !== null && typeof obj === 'object'
}
// 5.Vue中的閉包使用
// 5.1 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 isLaugh = makMap('嘻嘻,哈哈',true);
//設定一個檢測是否爲我的名字的方法,第二個參數不區分大小寫
isLaugh('嘻嘻') // true
isLaugh('哈哈') // true
isLaugh('呵呵') // false
// 5.2 once:只調用一次的函數
// 以called作爲回調標識符。調用此函數時,called標示符改變,下次調用就無效了
export function once (fn: Function): Function {
let called = false
return function () {
if (!called) {
called = true
fn.apply(this, arguments)
}
}
}
// 5.3 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)創建純函數是爲了防止變化(純函數的特性:輸入不變則輸出不變)。
// 多類型的全等判斷
// 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
}
}