源码共读 | vue2中的工具函数

978 阅读3分钟

我正在参与掘金会员专属活动-源码共读第一期,点击参与

前言

今天来学习vue2中的工具函数,目标主要就两个,学习+复习;学习大佬的实现方式,巩固复习js基础; 仓库链接:vue2打包好的

源代码在sharde路径下,因为是ts的我们直接研究打包好的就可以,当然有兴趣的也可以研究ts源码,这块的实现大同小异;

image.png

函数

函数比较多,这里简单分下类:

类型/值判断

//判断值是undefinen或null
  function isUndef (v) {
    return v === undefined || v === null
  }
  
  
 //判断值是否不是undefinen并且null
  function isDef (v) {
    return v !== undefined && v !== null
  }
  
  
//判断值是否为true
  function isTrue (v) {
    return v === true
  }


//判断值是否为false
  function isFalse (v) {
    return v === false
  }
  
  
//判断一个值是否为(基本类型)string、number、symbol、boolean
function isPrimitive (value) {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
    typeof value === 'symbol' ||
    typeof value === 'boolean'
  )
}


//判断一个值是否为对象,先排除null
function isObject (obj) {
  return obj !== null && typeof obj === 'object'
}


//判断值是否是纯对象
function isPlainObject (obj) {
 return _toString.call(obj) === '[object Object]'
 }


//判断值是否为正则表达式
function isRegExp (v) {
  return _toString.call(v) === '[object RegExp]'
}


//判断值是否为可用的数组索引
//基本流程是这样:
function isValidArrayIndex (val) {
  //转字符串转数字(浮点型,保留小数点后的值)
  var n = parseFloat(String(val));
  //判断值是否大于0,向下取整是否与原值相等,是否有限;
  return n >= 0 && Math.floor(n) === n && isFinite(val)
 }


//判断值是否为Promise对象
function isPromise (val) {
  return (
  //判断值是undefinen或null
   isDef(val) &&
   //判断是then和catch是否为函数
   typeof val.then === 'function' &&
   typeof val.catch === 'function'
    )
  }
  
  
 //判断对象是否包含某个属性。hasOwnProperty为原生方法返回一个布尔值,判断对象自身属性中是否具有指定的键
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  function hasOwn (obj, key) {
    return hasOwnProperty.call(obj, key)
  }  

类型/值转换

//方法返回一个表示该对象的字符串。
var _toString = Object.prototype.toString;


//把值转换为基本类型
function toRawType (value) {
    截取返回值中的字符串,表示其类型
    return _toString.call(value).slice(8, -1)
  }
  
  
//将值转换为字符串
function toString (val) {
//null返回空字符串
  return val == null
    ? ''
    //数组或对象且没有使用_toString方法,则使用JSON.stringify转为字符串,如果都不是,转为字符串;
    : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
      ? JSON.stringify(val, null, 2)
      : String(val)
}


//将值转换为数组,失败返回该值
  function toNumber (val) {
    var n = parseFloat(val);
    return isNaN(n) ? val : n
  }


//传入字符串,生成一个Map,返回一个函数检测Map是否包含key
  function makeMap (
    str,
    expectsLowerCase
  ) {
  //空对象
    var map = Object.create(null);
    var list = str.split(',');
    for (var i = 0; i < list.length; i++) {
      map[list[i]] = true;
    }
    return expectsLowerCase
    //转为小写
      ? function (val) { return map[val.toLowerCase()]; }
      : function (val) { return map[val]; }
  }

其他

  //调用原生方法冻结一个对象,无法增、删、改
  var emptyObject = Object.freeze({});
  
  
  //移除数组中的元素
    function remove (arr, item) {
    if (arr.length) {
      var index = arr.indexOf(item);
      if (index > -1) {
        return arr.splice(index, 1)
      }
    }
  }
  
  
//缓存数据
  function cached (fn) {
  //声明空对象
    var cache = Object.create(null);
    //返回一个函数
    return (function cachedFn (str) {
    //判断字符串是否在缓存中,不在则将fn(str)的值赋给cache[str]并返回
      var hit = cache[str];
      return hit || (cache[str] = fn(str))
    })
  }


//使用正则,连字符转小驼峰,正则匹配,replace替换,同时使用上面的缓存方法缓存起来
  var camelizeRE = /-(\w)/g;
  var camelize = cached(function (str) {
    return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; })
  });


//首字母转大写
  var capitalize = cached(function (str) {
   //获取到第一个字母,进行转换
    return str.charAt(0).toUpperCase() + str.slice(1)
  });


//使用正则,小驼峰转连字符
  var hyphenateRE = /\B([A-Z])/g;
  var hyphenate = cached(function (str) {
    return str.replace(hyphenateRE, '-$1').toLowerCase()
  });
 
 
 //兼容bind方法,这里我们只要清楚apply和call的区别就很好理解(apply的第二个参数必须是数组或类数组),根据传参个数调用apply或call
  function polyfillBind (fn, ctx) {
    function boundFn (a) {
      var l = arguments.length;
      return l
        ? l > 1
          ? fn.apply(ctx, arguments)
          : fn.call(ctx, a)
        : fn.call(ctx)
    }

    boundFn._length = fn.length;
    return boundFn
  }

  function nativeBind (fn, ctx) {
    return fn.bind(ctx)
  }

  var bind = Function.prototype.bind
    ? nativeBind
    : polyfillBind;
  
  
 //类数组转数组,遍历依次添加,最后返回新数组
 function toArray (list, start) {
 start = start || 0;
 var i = list.length - start;
 var ret = new Array(i);
 while (i--) {
    ret[i] = list[i + start];
  }
 return ret
 }
 
 
 //属性合并
 function extend (to, _from) {
  for (var 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
  }  
  
  
//空函数
  function noop (a, b, c) {}


//返回false的函数
  var no = function (a, b, c) { return false; };
  
  
//返回当前参数
var identity = function (_) { return _; };


//传入一个由对象构成的数组,将每个对象的staticKeys属性合并为字符串,逗号隔开
  function genStaticKeys (modules) {
    return modules.reduce(function (keys, m) {
      return keys.concat(m.staticKeys || [])
    }, []).join(',')
  }
 
 
//判断两个值是否全等。这个函数比较长,一步步解析;
  function looseEqual (a, b) {
  //直接判断,相等则返回ture
    if (a === b) { return true }
    var isObjectA = isObject(a);
    var isObjectB = isObject(b);
    
    
    //如果两个值都是数组,判断它们的长度是否相等,相等则再判断元素,相等的话递归调用looseEqual
    if (isObjectA && isObjectB) {
      try {
        var isArrayA = Array.isArray(a);
        var isArrayB = Array.isArray(b);
        if (isArrayA && isArrayB) {
          return a.length === b.length && a.every(function (e, i) {
            return looseEqual(e, b[i])
          })    
     //如果两个值都是Date,则比较它们的时间戳是否相同
        } else if (a instanceof Date && b instanceof Date) {
          return a.getTime() === b.getTime()
      //如果两个值是对象则判断它们的键个数是否相等,相等继续递归调用looseEqual比较值
        } else if (!isArrayA && !isArrayB) {
          var keysA = Object.keys(a);
          var keysB = Object.keys(b);
          return keysA.length === keysB.length && keysA.every(function (key) {
            return looseEqual(a[key], b[key])
          })
        } else {
          /* istanbul ignore next */
          return false
        }
      } catch (e) {
        /* istanbul ignore next */
        return false
      }
    } else if (!isObjectA && !isObjectB) {
      return String(a) === String(b)
    } else {
      return false
    }
  }
  
  
//查找数组中某个值的索引,调用looseEqual方法进行比较
  function looseIndexOf (arr, val) {
    for (var i = 0; i < arr.length; i++) {
      if (looseEqual(arr[i], val)) { return i }
    }
    return -1
  }
  
  
 //传入一个函数,限制该函数额执行次数,只执行一次
   function once (fn) {
   //声明一个called变量,利用闭包提升其作用域
    var called = false;
    return function () {
      if (!called) {
      //执行一次就置为true,如果为true则不执行
        called = true;
        fn.apply(this, arguments);
      }
    }
  }

总结

本期的源码阅读到这里就结束了,读完vue2的工具函数我学到了这些:

  • 函数命名都非常规范
  • 函数各司其职,一个函数干一件事
  • 对原生方法的灵活应用,很多时候原生方法并不能满足我们的需求,巧妙的应用能帮我们轻松解决问题

工具函数的代码量并不是很大,也不是非常难以理解,读懂它们并不难,重要的是我们学到的编程思想。