Vue 源码中的一些辅助函数

3,236 阅读4分钟

在回顾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 排版