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

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

前言

今天来学习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的工具函数我学到了这些:
  • 函数命名都非常规范
  • 函数各司其职,一个函数干一件事
  • 对原生方法的灵活应用,很多时候原生方法并不能满足我们的需求,巧妙的应用能帮我们轻松解决问题
工具函数的代码量并不是很大,也不是非常难以理解,读懂它们并不难,重要的是我们学到的编程思想。
------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容