在回顾Vue源码的时候,经常看到Vue中一些辅助函数,总结一些日常中会用到的,和一些js代码片段
Vue 源码中的一些辅助函数
判断对象的某个属性或者是否定义或者是否有值
function isDef (v){
return v !== undefined && v !== null
}
已经定义的值判断是否是原始类型
function isPrimitive (value){
return (
typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}
是否是 promise 类型
function isPromise (val){
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}
转换为数字
// 将输入值转换为数字。如果转换失败,则返回原始字符串。
function toNumber (val){
const n = parseFloat(val)
return isNaN(n) ? val : n
}
创建一个 map,返回一个函数去检测一个 key 值是否存在与这个 map
function makeMap (
str,
expectsLowerCase
){
const map = Object.create(null)
const list = str.split(',')
for (let i = 0; i < list.length; i++) {
map[list[i]] = true
}
return expectsLowerCase
? val => map[val.toLowerCase()]
: val => map[val]
}
vue 源码中的使用
var 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'
);
var 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
);
var isReservedTag = function (tag) {
return isHTMLTag(tag) || isSVG(tag)
};
var isBuiltInTag = makeMap('slot,component', true)
function validateComponentName (name) {
if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicoderegexp.source) + "]\*\$")).test(name)) {
warn(
'Invalid component name: "' + name + '". Component names ' +
'should conform to valid custom element name in html5 specification.'
);
}
if (isBuiltInTag(name) || config.isReservedTag(name)) {
warn(
'Do not use built-in or reserved HTML elements as component ' +
'id: ' + name
);
}
}
从数组中移除一项
function remove (arr, item){
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
vue 源码中的使用
Dep.prototype.removeSub = function removeSub (sub) {
remove(this.subs, sub);
};
转换一个类数组为真实数组
function toArray (list, start){
start = start || 0
let i = list.length - start
const ret = new Array(i)
while (i--) {
ret[i] = list[i + start]
}
return ret
}
vue 源码中的使用
Vue.prototype.$emit = function (event) {
const vm = this;
...
let cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
const args = toArray(arguments, 1);
...
}
return vm
};
将属性混合到目标对象中
function extend (to, _from): Object {
for (const key in _from) {
to[key] = _from[key]
}
return to
}
将对象数组合并为单个对象
function toObject (arr) {
var res = {};
for (var i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i]);
}
}
return res
}
vue 源码中的使用
function updateStyle (oldVnode: VNodeWithData, vnode: VNodeWithData) {
...
if (Array.isArray(style)) {
style = vnode.data.style = toObject(style)
}
}
确保一个函数仅被调用一次
function once (fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
}
}
vue 源码中的使用
const reject = once(reason => {
warn(
`Failed to resolve async component: ${String(factory)}` +
(reason ? `\nReason: ${reason}` : '')
);
if (isDef(factory.errorComp)) {
factory.error = true;
forceRender(true);
}
});
定义一个属性
function def (obj, key, val, enumerable) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
});
}
通过使用__proto__的原型链拦截来增强目标对象或数组
function protoAugment (target, src) {
target.__proto__ = src;
}
通过定义来增强目标对象或数组
隐藏的属性。
function copyAugment (target, src, keys) {
for (var i = 0, l = keys.length; i < l; i++) {
var key = keys[i];
def(target, key, src[key]);
}
}
vue 源码中的使用
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
} else {
this.walk(value)
}
}
...
}
日常使用的js代码片段
取数组相同项(不同项同理取不包含)
选择一个数组创建 Set,然后使用 Array.filter() 过滤 Set 中包含的另一个数组值
const intersection = (ary1, ary2) => { const s = new Set(ary2); return ary1.filter(item => s.has(item)) };
//如果确定数组中不会有重复部分
const intersection = (ary1, ary2) => ary1.filter(v => ary2.includes(v));
// intersection([32,43,543], [32,12,55,43]) -> [32,43]
统计数组中出现指定值的次数
用 reduce 查询数组,符合条件时+1
const countOccurrences = (ary, value) => ary.reduce((res, val) => val === value ? ++res : res + 0, 0);
//countOccurrences([2,4,11,'a','a','b',32], 'a') -> 2
扁平化数组(扁平所有)
递归数组,reduce 展开数组并合并
const deepFlatten = arr => arr.reduce((a, v) => a.concat(Array.isArray(v) ? deepFlatten(v) : v), []);
// deepFlatten(['a',['b'],[['c'],'d']]) -> ['a','b','c','d']
//如果所要展开的只有一层 可以直接使用 es6 的 Array.flat(),且只能展开一层
['a',['b'],['c'],'d'].flat() -> ['a','b','c','d']'a',['b'],[['c'],'d']].flat() -> ["a", "b", ['c'], "d"]
扁平化指定深度的数组
在上一个的基础上改造一下,增加一个深度参数
const flattenDepth = (ary, depth = 1) =>
depth != 1 ? ary.reduce((a, v) => a.concat(Array.isArray(v) ? flattenDepth(v, depth - 1) : v), [])
: ary.flat();
// flattenDepth(['a','b',['c'],[['d','e',['f'] ]]], 2) -> ["a", "b", "c", "d", "e", ['f']]
滚动到顶部
const scrollToTop = _ => {
const c = document.documentElement.scrollTop || document.body.scrollTop; //获取到顶端的距离
if (c > 0) {
window.requestAnimationFrame(scrollToTop);//滚动动画效果
window.scrollTo(0, c - c / 8);
}
};
取两个日期之间相差的天数
const getDaysDiffBetweenDates = (beginDate, endDate) => ( endDate - beginDate) / (1000 _ 3600 _ 24);
// getDaysDiffBetweenDates(new Date("2020-09-22"), new Date("2020-10-01")) -> 9
本文使用 mdnice 排版