前言
每个人都能做前端工程师,但达到山顶,却不是所有人。有些人一直在山底,有些人在山腰就没再往上爬,比如我,我一直在山腰那儿,做前端开发快有4年了,4年的应该是很强的,可惜我不是,还不懂Promise是怎么实现,还不会去读源代码。一看就没然后了,因为一般源代码是比较庞大且很多模块化,就要在脑子里画好连线。而且读的时间比较长,像我这种,有时会比较忙,过了几天再看就这么忘了,弃~就这么让焦虑伴随时间过去,然后偶然看到若川大佬发动的源码共读活动,有易到难的学习顺序,而且有大佬自己写的文章,还可以参考,觉得很不错的,就这么尝试的~再次感谢若川大佬组织的活动~~
这篇文章本来应该是2021年末完成的,因为有其他的重要的事要做,就这么拖到2022年,也算是希望2022年的开始,并且突破向上爬~加油咯~~
参考网址:
1.变量的解释:
__DEV__是指开发环境
2.工具函数的实现:
2.1.EMPTY_OBJ 空对象
export const EMPTY_OBJ = __DEV__ ? Object.freeze({}) : {}
这是在说,在开发环境下时,赋值给空对象时,为了防止开发人员增加属性,就用Object.freeze来冻结,注意这只能冻结最外层的属性(即使增加属性也不会报错),不影响内层的属性,比如:
const obj1 = Object.freeze({})
obj1.name = 'fency'
console.log(obj1) //{}, 没变化的,只是不会报错的
const obj2 = Object.freeze({info:{name:'fency', age:20, sex:'femal'}})
obj2.age = 18
console.log(obj2) //{info:{name:'fency', age:18, sex:'femal'}} 内层的age被变化
知识点:
看起来,Object.freeze()和const有点像,其实是不一样的,const不能再重新赋值,但里面的属性还能改。而Object.freeze能不能重新赋值,取决于声明的方式,它只是主要在于改变属性的操作符。
2.2.EMPRY_ARR 空数组
export const EMPTY_ARR = __DEV__ ? Object.freeze([]) : []
和上面的 EMPTY_OBJ差不多,但它有个神奇的地方,比如说:
const arr1 = Object.freeze([])
arr1.push(2) //会报错,因为被冻结了,属性就不能用
arr1[0] = 1 //不会报错的,只是无效
arr[0] //undefined
2.3.NOOP 空函数
export const NOOP = () => {}
2.4.NO 一直返回false
export const NO = () => false
2.5.isOn 判断字符串是不是以on开头,并且on后面的首字母不能是小写字母
export onRE = /^on[^a-z]/
export const isOn = key => onRE.test(key)
知识点:
- ^符号在
开头,则表示必须以什么开头,比如o;- ^在
其中,则是指非,比如说[^a-z],这是表示不能小写字母
举🌰:
isOn('onChange') //true
isOn('onchange') //false
isOn('on') //false
isOn('on3change') //true
2.6.isModelListener 监听器
export const isModelListener = (key: string) => key.startsWith('onUpdate:')
知识点:
startsWith是es6新增的方法,用来判断当前字符串的开头和给定的另一个字符串是不是一样,返回Boolean
举🌰:
// 语法
str.startsWith(srarchString, position)
//searchString: 给定的字符串,注意区分大小
//position: 可选,开始位置,默认0
const str = "Hello Fency"
str.startsWith('Hel') //true
str.startsWith('hel') //false
str.startsWith('Hel',2) //false, 从下标2开始,也就是l开始,llo和Hel不一样,所以返回false
2.7.extend 继承
export const extend = Object.assign
Object.assign是es6对象的新增方法,准确来说是合并,用于对象的合并
- 第一个参数是目标对象,如果该参数不是对象类型,会自动转换为Object(只支持
String,Number,Boolean),undefined和null会报错- 第二个以及后面的参数都是源对象
Tips:目标对象和源对象如果有相同的属性,源对象会覆盖目标对象,这个是浅拷贝
举🌰:
const obj1 = {name:'fency', info:{sex:'femal'}}
const obj2 = {name:'new fency', age:20}
const obj3 = extend(obj1, obj2)
console.log(obj3) //{name:'new fency',info:{sex:'femal'}, age: 20}
console.log(obj1===obj3) //true
常见用途:
- 为对象添加属性
- 为对象添加方法
- 克隆对象
- 合并多个对象
- 为属性指定默认值
2.8.remove 移除某一项
export const remove = (arr, el)=>{
const i = arr.indexOf(el)
if(i > -1){
arr.splice(i,1)
}
}
2.9.hasOwn 判断是不是自身的属性
const hasOwnProperty = Object.prototype.hasOwnProperty
export const hasOwn = (val, key) => hasOwnProperty.call(val, key)
知识点:
hasOwnProperty,是一个Object的原型方法,它只有在自己本身的属性匹配到了才能返回
true,其他的或者它的原型属性返回false
举🌰:
hasOwn({name:'fency'}, 'name'); //true
hasOwn({}, 'name'}; //false
hasOwn({}.__proto__, 'toString'); //true,因为每个对象的原型都有一个toString()方法,才会返回true
2.10.isArray 判断是否是数组
export const isArray = Array.isArray
//举例子
isArray([]) //true
isArray({}) //false
isArray(12) //false
知识点:
typeof判断不出是否是数组,不管是Object或Array都会返回
object;然后Object.prototype.toString.call也可以判断出的,但isArray是最简洁的。
2.11.objectToString 对象转换为字符串
export const objectToString = Object.prototype.toString
2.12.toTypeString
export const toTypeString = val => objectToString.call(val)
这往往用来判断类型,相比type更强大,有多强大呢?咱们看看下面的~
2.13.isMap 判断是否是Map
export const isMap = (val) => toTypeString(val) === '[object Map]'
还能判断是否是Map, Map是es6新增的属性
知识点:
es6为什么要新增Map?因为Object的键并不是都可以用的,比如说获取DOM节点作为一个键,但在Object的键中,会变成
[object HTMLDivElement],这样就没法用。而在Map,能够正常获取DOM节点。Map的键值不限于字符串的。可以说Map是Object上更完善的一种数据结构。
2.14.isSet 判断是否是Set
export const isSet = (val) => toTypeString(val) === '[object Set']
Set是es6新增的属性,常用于去重,使每一个值都是唯一的。
举🌰:
const arr = [1,2,2,3,'a','a','b']
[...new Set(arr)] //[ 1, 2, 3, 'a', 'b' ]
2.15.isDate 判断是否是Date
export const isDate = (val) => val instanceof Date
知识点:
instanceof用来判断一个实例对象的原型链是否在构造函数的原型属性上。一般都是用来判断实例对象和构造函数的原型关系问题。
举🌰:
const date = new Date()
isDate(date) // true
function Person(){}
function Animal(){}
const p = new Person()
p instanceof Person //true
p instanceof Animal //false,因为p在Animal构造函数上并没有实例化
2.16.isFunction 判断是否是function
export const isFunction = (val) => typeof val === 'function'
知识点:
判断是否是
function的方法有多种,Object.prototype.toString.call也是可以用的。但typeof是最好最简单最稳定的方法。
2.17.isString 判断是否是字符串
export const isString = (val) => typeof val === 'string'
知识点:
typeof是最简单的判断方法,但它比较适用于判断基本类型,比如String,Number,Boolean,Symbol,Undefined,还有特殊的function。如果要判断是否是Array,或者Object,就用其他的方法
2.18.isSymbol 判断是否是symbol
export const isSymbol = (val) => typeof val === 'symbol'
和上面一样的道理
2.19.isObject 判断是否是对象
export const isObject = (val) => val !== null && typeof val === 'object'
因为typeof null也会返回object, 所以要加上 val !== null😅
举🌰:
isObject({}) //true
isObject([]) //true
isObject(new Map()) //true
2.20.isPromise 判断是否是Promise
export const isPromise = (val) => {
return isObject(val) && isFunction(val.then) && isFunction(val.catch)
}
Promise是es6新增的属性,解决之前最可怕的回调函数,现在变成最常见的异步执行解决方法(async/await也是如此),然后Promise必须是要有then和catch
举🌰:
const p=new Promise((resolve,reject)=>{
resolve('执行成功')
})
isPromise(p) //true
2.21.toRawType 对象转字符串,然后截取
export const toRawType = (value) => {
return toTypeString(value).slice(8,-1)
}
截取到后就是类型,比typeof判断还要准确的。
举🌰:
toRawType('a') //'String'
toRawType({a:1}) //'Object'
toRawType({null}) //'Null'
toRawType(function(){}) //'Function'
toRawType(undefined) //'Undefined'
toRawType(true) //'Boolean'
2.22.isPlainObject 判断是不是纯粹的对象
export const isPlainObject = (val) => toTypeString(val) === '[object Object]'
isPlainObject和上面的isObject类似的,但还是有几点不同的,看下面的🌰:
isObject([]) //true
isPlainObject([]) //false
isObject(new Map()) //true
isPlainObject(new Map()) //false
fucntion Person(){
this.name = "fency"
}
const p = new Person() //实例化后就变成object
isPlainObject(p) //true
2.23.isIntegerKey 判断key是不是数字类型
export cosnt isIntegerKey = (key) => isString(key) && key !== 'NaN' && key[0] !== '-' && '' + parseInt(key, 10) === key
判断key是不是数字类型,要满足以下所有的条件:
- isString(key) 必须是字符串类型;
- key !== 'NaN'
- key[0] !== '-'
- ' ' + parseInt(key, 10) === key
' ' +为了保证和key是一样的类型,===包括比较类型- parseInt(key, 10) 第二个参数是进制,这要求十进制,也就是说必须是整数
举🌰:
isIntegerKey('a') //false, 因为parseInt(a,10)会返回NaN
isIntegerKey('011') //false, 因为parseInt('011',10)会返回11,和'011'不一样
isIntegerKey(11) //false, 因为isString(11)会返回false
isIntegerKey('11') //true
2.24.makeMap && isReservedProp
//做一个map 键值对
export function makeMap(str, expectsLowerCase){
const map = Object.create(null)
//字符串根据逗号来分隔成为一个数组
const list = str.split(',')
//存到map,并且赋给true,表示存在的。
for(let i = 0; i < list.length; i++){
map[list[i]] = true
}
//返回一个函数的,需要检查时就根据key来判断是否在里面的
return expectsLowerCase ? val => !!map[val.toLowerCase()] : val => !!map[val]
}
export const isReservedProp = makeMap(
',key,ref,' +
'onVnodeBeforeMount,onVnodeMounted,' +
'onVnodeBeforeUpdate,onVnodeUpdated,' +
'onVnodeBeforeUnmount,onVnodeUnmounted'
)
isReservedProp先保留需要的属性,然后返回一个函数的,然后需要检查某个key是否在里面的。
isReservedProp('key') //true
isReservedProp('ref') //true
2.25.cacheStringFunction 缓存
const cacheStringFunction = fn => {
//创建对象,作为数据字典
const cache = Object.create(null)
//利用闭包的思想,以至于别的函数能访问到这个变量
return str => {
const hit = cache[str]
return hit || (cache[str] = fn(str))
}
}
2.26. camelize 连字符转驼峰
// /- 匹配连字符-;\w [0-9a-zA-Z_];/g 全局的意思
// 圆括号()是为了提取字符串,比如说`on-click`,那么提取的就是c
const camelizeRE = /-(\w)/g
export const camelize = cacheStringFunction(str => {
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))
})
第一次见正则表达式还有函数作为第二个参数的,然后我就查询有关它的使用方法~
知识点:
- replace是替换匹配的字符串
- 第一个参数可以是字符串,或者RegExp
- 第二个参数可以是新的字符串,或者函数(返回的值),用来替换掉第一个参数。
- 一个函数作为第二个参数时,前提是要匹配成功后才能执行函数的。
- 它的实现方法是这样:比如说
on-click根据RegExp获取到c- 这个是利用高阶函数,如果接触这个太少了,可能会一时没搞懂,比如说我。建议认真研究的~
- cacheStringFunction 利用
str => {return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''))}作为函数。先替换字符串后,再存到缓存列表的。如果之前存过的,则直接返回的。这么高级的写法,一定要get下~
3.27.hyphenate 驼峰转连字符
// \B是\b的相反,\b是单词的边界,\B是非单词的边界
// 然后后面[A-Z],是说根据大写字母来划分边界的。
const hyphenateRE = /\B([A-Z])/g
// 其实就是把 onClick 处理成为 on-click
export const hyphenate = cacheStringFunction(str => {
return str.replace(hyphenateRE, '-$1').toLowerCase()
})
3.28.capitalize 首字母转化为大写
export const capitalize = cacheStringFunction(str => str.charAt(0).toUpperCase() + str.slice(1))
charAt就是根据位置返回指定的字符串,这里面用0,也就是取首字母,然后转化为大写字母,slice(1),从位置1开始到最后的字符串,然后组成一个新的字符串并返回去。
3.29.toHandlerKey click -> onClick
export const toHandlerKey = cacheStringFunction(str => str ? `on${capitalize(str)}` : ``)
就字符串前面添加on
3.30.hasChanged 判断是否有变化
export const hasChanged = (value, oldValue) => !Object.is(value, oldValue)
知识点:
Object.is 是es6新增的属性,和之前的比较两个值是否相等:
===和==。一般来说推荐用===,因为具有严格的特性,但有个奇怪的问题,比如NaN===NaN居然返回false,所以出现Object.is()
NaN === NaN //false
Object.is(NaN, NaN) //true
+0 === -0 //true
Object.is(+0, -0) //false
3.31.invokeArrayFns 执行数组里的函数
export const invokeArrayFns = (fns, arg) => {
for(let i=0; i<fns.length; i++){
fns[i](arg)
}
}
就是如果需要执行多个函数的话,可以用这个来执行。高明哇~之前做的业务任务遇到过的,但我却一个一个执行的,多笨哪~😂
3.32.def
export const def = (obj,key, value) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
value
})
}
Object.defineProperty是vue2.x版本主要实现方法之一,用于添加或者修改对象的属性,configurable是可修改,enumeable不可枚举,value就是值。也就是表示对一个对象赋值后,这个对象的属性可以改变,但不可枚举的。
3.33. toNumber 转数字
export const toNumber = val => {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
用parseFloat为了保持小数点正常输出,然后判断是否是数值。一般来说isNaN返回true的就是数值类型的。
3.34.getGlobalThis 获取全局对象
let _globalThis;
export const getGlobalThis = () => {
return (
_globalThis ||
(_globalThis =
typeof globalThis !== 'undefined'
? globalThis
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: typeof global !== 'undefined'
? global
: {})
)
}
顺序:_globalThis(这个有值的话,会直接返回) > self > window > global > {}
总结:
- 了解了工具函数的写法;
- 认识了不常用的API,希望后面能多运用下;
- 了解了正则表达式还有另外函数作为参数的;
- 了解了高阶函数的使用方法。