- 源码中使用了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
}
}