Open webfansplz opened 3 years ago
Repository (仓库地址):https://github.com/vuejs/vue-next/blob/master/packages/shared/src/patchFlags.ts Gain (收获) : 运算符 + 我也不知道有啥....
Vue3核心的ts、proxy、composition之类的,在vue3虚拟dom中,比如 静态标记update性能可以说是提升了1.3~2倍,ssr提升了2~3倍。
普通编译模板的静态标记
<div id="app">
<h1>test</h1>
<p>哈哈哈</p>
<div>{{name}}</div>
</div>
在vue2中会解析成
function render() {
with(this) {
return _c('div', {
attrs: {
"id": "app"
}
}, [_c('h1', [_v("test")]), _c('p', [_v("哈哈哈")]), _c('div', [_v(
_s(name))])])
}
}
前面两个标签是静态的, 后续的渲染就不会产生别的变化,vue2里面依旧使用_c新建vdom,在diff的时候对比,这样会有一些额外的性能损耗。
vue3中年是如何解析的
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", { id: "app" }, [
_createVNode("h1", null, "test"),
_createVNode("p", null, "哈哈哈"),
_createVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */)
]))
}
// Check the console for the AST
这里最后一行也就是最后一个_createVNode的第四个参数是一个1,只有当这个参数有带上的时候才会去追踪,静态节点不需要去遍历操作之类的,这样性能就可以大大提升,这就是vue3性能比vue2好的原因之一
复杂点来看
<div id="app">
<h1>test</h1>
<p>哈哈哈</p>
<div>{{name}}</div>
<div :class="{red:isRed}">red</div>
<button @click="handleClick">btn</button>
</div>
会解析成这样
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", { id: "app" }, [
_createVNode("h1", null, "test"),
_createVNode("p", null, "哈哈哈"),
_createVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */),
_createVNode("div", {
class: {red:_ctx.isRed}
}, "red", 2 /* CLASS */),
_createVNode("button", { onClick: _ctx.handleClick }, "btn", 8 /* PROPS */, ["onClick"])
]))
}
根据_createVNode中的第四个参数的值其实可以很容易的才出来他明显就是一个标示,可以理解为一个flag,根据他是text,props等等之类的标示来判定他再diff的时候要对比哪些,不用再去做没有用的props遍历,贼牛逼!
export const enum PatchFlags {
TEXT = 1,// 表示具有动态textContent的元素
CLASS = 1 << 1, // 表示有动态Class的元素
STYLE = 1 << 2, // 表示动态样式(静态如style="color: red",也会提升至动态)
PROPS = 1 << 3, // 表示具有非类/样式动态道具的元素。
FULL_PROPS = 1 << 4, // 表示带有动态键的道具的元素,与上面三种相斥
HYDRATE_EVENTS = 1 << 5, // 表示带有事件监听器的元素
STABLE_FRAGMENT = 1 << 6, // 表示其子顺序不变的片段(没懂)。
KEYED_FRAGMENT = 1 << 7, // 表示带有键控或部分键控子元素的片段。
UNKEYED_FRAGMENT = 1 << 8, // 表示带有无key绑定的片段
NEED_PATCH = 1 << 9, // 表示只需要非属性补丁的元素,例如ref或hooks
DYNAMIC_SLOTS = 1 << 10, // 表示具有动态插槽的元素
}
以上是vue3的pathFlags,说白了就是通过枚举的方式来定义,嗯他是什么标示。就是通过运算符来flag,仅此而已。
那么问题来了,如果同时有props和text的绑定的,那么咋办呢?
<div id="app">
<h1>test</h1>
<p>哈哈哈</p>
<div :id="userId">{{name}}</div>
</div>
很简单,把位运算组合即可
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache) {
return (_openBlock(), _createBlock("div", { id: "app" }, [
_createVNode("h1", null, "test"),
_createVNode("p", null, "哈哈哈"),
_createVNode("div", {
id: _ctx.userid,
"\"": ""
}, _toDisplayString(_ctx.name), 9 /* TEXT, PROPS */, ["id"])
]))
}
text是1,props是8,组合起来就是9?咋算呢????通过位运算来判定需要做text和props的判断,按位与不久好了,通俗说,不是0就需要比较!打个比方
类型 二进制
text 000000001
props 000001000
merge 000001001
上面就是按位与 1和8的二进制位就是1
测试一下(放到控制台)
let TEXT = 1,
PROPS = 8,
CLASS = 2,
VAL = 9
!!(TEXT & VAL) // true
!!(PROPS & VAL) // true
!!(CLASS & VAL) // false
如果你不会运算符....那么 百度一下哈哈哈
综上所述,其实这种方式呢还可以运用在很多种地方,比如权限控制???把最高级设置成1000然后二级0100三级0010...这样的方式来做-。-这样不就是简单明了吗哈哈哈哈哈哈。
Repository (仓库地址):https://github.com/ustbhuangyi/better-scroll/tree/dev/packages Gain (收获) : 插件化架构简单了解
BS插件化架构包管理:
Core 通常提供系统运行所需的最小功能集,不会因为业务功能扩展而不断修改,而插件模块是可以根据实际业务功能的需要不断地调整或扩展。BS的core实现了基础的列表滚动效果:
import { BScroll } from './BScroll'
export { BScrollInstance } from './Instance'
export { Options, CustomOptions } from './Options'
export { TranslaterPoint } from './translater'
export { MountedBScrollHTMLElement } from './BScroll'
export { Behavior, Boundary } from './scroller/Behavior'
export { createBScroll, CustomAPI } from './BScroll'
export default BScroll
使用
import BScroll from '@better-scroll/core'
const bs = new BScroll('.wrapper', {/* ... */})
插件模块是独立的模块,包含特定的处理、额外的功能和自定义代码,来向核心系统增强或扩展额外的业务能力。「通常插件模块之间也是独立的,也有一些插件是依赖于若干其它插件的。重要的是,尽量减少插件之间的通信以避免依赖的问题。」 BS的插件模块包括了mouse-wheel、observe-dom、pull-down、pull-up、scroll-bar等等
插件运行时需要的基础接口
浏览器就是一个典型的插件化架构,浏览器是内核,页面是插件,这样通过不同的URL地址加载不同的页面,来提供非常丰富的功能。而且,我们开发网页时候,浏览器会提供很多API和能力,这些接口通过 window来挂载, 比如,DOM、BOM、Event、Location等等(百度到的)
另外jQuery、vue-cli、bable、webpack等都是使用插件系统。
Repository (仓库地址): es5-shim js猴子补丁,旨在低版本浏览器同样可以使用一些好用的js原生方法。 Gain (收获) : 手动实现原生函数bind 虽然我们很少用低版本浏览器了,这些polyfill显得有些多余,但是里面的一些思想,很值得我们学习。
先说说bind是什么: 1)bind是用于绑定this指向的 2)使用方法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
Function.prototype.bind = function (context) {
var me = this;
var argsArray = Array.prototype.slice.call(arguments);
return function () {
return me.apply(context, argsArray.slice(1))
}
}
基本原理就是缓存原函数,并返回一个新的函数,然后新的函数运行时,通过apply来手动指定函数运行上下文。
arguments
是一个类数组,通过Array.prototype.slice.call
将其转为真正的数组。
bind接收多个参数,从第二个参数开始的参数,将会作为函数执行的预置参数:
function list() {
return Array.prototype.slice.call(arguments);
}
// 创建一个函数,它拥有预设参数列表。
var leadingThirtysevenList = list.bind(null, 37);
var list2 = leadingThirtysevenList();
// [37]
var list3 = leadingThirtysevenList(1, 2, 3);
// [37, 1, 2, 3]
上面的代码就不太满足需求了,修改成为以下代码:
Function.prototype.bind = Function.prototype.bind || function (context) {
var me = this;
var args = Array.prototype.slice.call(arguments, 1);
return function () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return me.apply(contenxt, finalArgs);
}
}
bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们的绑定this就需要“被忽略”
Function.prototype.bind = Function.prototype.bind || function (context) {
var me = this;
var args = Array.prototype.slice.call(arguments, 1);
var F = function () {};
F.prototype = this.prototype;
var bound = function () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.contact(innerArgs);
return me.apply(this instanceof F ? this : context || this, finalArgs);
}
bound.prototype = new F;
return bound;
}
var Empty = function Empty() {};
// ...
bind: function bind(that) {
var target = this;
if (!isCallable(target)) {
throw new TypeError('Function.prototype.bind called on incompatible ' + target);
}
var args = array_slice.call(arguments, 1);
var bound;
var binder = function () {
if (this instanceof bound) {
var result = target.apply(
this,
array_concat.call(args, array_slice.call(arguments))
);
if ($Object(result) === result) {
return result;
}
return this;
} else {
return target.apply(
that,
array_concat.call(args, array_slice.call(arguments))
);
}
};
var boundLength = max(0, target.length - args.length);
var boundArgs = [];
for (var i = 0; i < boundLength; i++) {
array_push.call(boundArgs, '$' + i);
}
bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
if (target.prototype) {
Empty.prototype = target.prototype;
bound.prototype = new Empty();
Empty.prototype = null;
}
return bound;
}
其实我们的实现已经基本和它差不多了,我们来看看区别:
bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this, arguments); }')(binder);
bound使用了系统自己的构造函数Function来声明,第一个参数是binder,函数体内又binder.apply(this, arguments) 我们知道这种动态创建函数的方式,类似eval。最好不要使用它,因为用它定义函数比用传统方式要慢得多。 那么ES5-shim为什么使用它,是抽风了吗。
你可能不知道,每个函数都有length属性。对,就像数组和字符串那样。函数的length属性,用于表示函数的形参个数。更重要的是函数的length属性值是不可重写的。
function test (){}
test.length // 输出0
test.hasOwnProperty('length') // 输出true
Object.getOwnPropertyDescriptor('test', 'length')
// 输出:
// configurable: false,
// enumerable: false,
// value: 4,
// writable: false
现在我们明白了: ES5-shim是为了最大限度的进行兼容,包括对返回函数length属性的还原。如果按照我们之前实现的那种方式,length值始终为零。 所以:既然不能修改length的属性值,那么在初始化时赋值总可以吧! 于是我们可通过eval和new Function的方式动态定义函数来。 在源码里有一段很有趣的注释
// XXX Build a dynamic function with desired amount of arguments is the only
// way to set the length property of a function.
// In environments where Content Security Policies enabled (Chrome extensions,
// for ex.) all use of eval or Function costructor throws an exception.
// However in all of these environments Function.prototype.bind exists
// and so this code will never be executed.
他解释了为什么要使用动态函数,就如同我们上边所讲的那样,是为了保证length属性的合理值。但是在一些浏览器中出于安全考虑,使用eval或者Function构造器都会被抛出异常。但是,巧合也就是这些浏览器基本上都实现了bind函数,这些异常又不会被触发。
So, What a coincidence!
Repository (仓库地址):https://github.com/ElemeFE/element/blob/dev/packages/progress/src/progress.vue Gain (收获) : SVG的基础知识和使用方式。。。
<template>
<div
class="el-progress"
:class="[
'el-progress--' + type,
status ? 'is-' + status : '',
{
'el-progress--without-text': !showText,
'el-progress--text-inside': textInside,
}
]"
role="progressbar"
:aria-valuenow="percentage"
aria-valuemin="0"
aria-valuemax="100"
>
<!-- 直线型进度条 -->
<div v-if="type === 'line'" class="el-progress-bar">
<div class="el-progress-bar__outer" :style="{height: strokeWidth + 'px'}">
<div class="el-progress-bar__inner" :style="barStyle">
<div v-if="showText && textInside" class="el-progress-bar__innerText">{{ content }}</div>
</div>
</div>
</div>
<!-- 环形进度条 -->
<div v-else class="el-progress-circle" :style="{height: width + 'px', width: width + 'px'}">
<svg viewBox="0 0 100 100">
<path
class="el-progress-circle__track"
:d="trackPath"
stroke="#e5e9f2"
:stroke-width="relativeStrokeWidth"
fill="none"
:style="trailPathStyle"
/>
<path
class="el-progress-circle__path"
:d="trackPath"
:stroke="stroke"
fill="none"
:stroke-linecap="strokeLinecap"
:stroke-width="percentage ? relativeStrokeWidth : 0"
:style="circlePathStyle"
/>
</svg>
</div>
<div
v-if="showText && !textInside"
class="el-progress__text"
:style="{fontSize: progressTextSize + 'px'}"
>
<template v-if="!status">{{ content }}</template>
<i v-else :class="iconClass" />
</div>
</div>
</template>
<script>
export default {
name: 'ElProgress',
props: {
/** 进度条类型 */
type: {
type: String,
default: 'line',
validator: val => ['line', 'circle', 'dashboard'].indexOf(val) > -1
},
/** 百分比 */
percentage: {
type: Number,
default: 0,
required: true,
validator: val => val >= 0 && val <= 100
},
/** 进度条当前状态 */
status: {
type: String,
validator: val => ['success', 'exception', 'warning'].indexOf(val) > -1
},
/** 进度条的宽度 */
strokeWidth: {
type: Number,
default: 6
},
/** circle/dashboard 类型路径两端的形状 */
strokeLinecap: {
type: String,
default: 'round'
},
/** 进度条显示文字内置在进度条内(只在 type=line 时可用) */
textInside: {
type: Boolean,
default: false
},
/** 环形进度条画布宽度(只在 type 为 circle 或 dashboard 时可用) */
width: {
type: Number,
default: 126
},
/** 是否显示进度条文字内容 */
showText: {
type: Boolean,
default: true
},
/** 进度条背景色(会覆盖 status 状态颜色) */
color: {
type: [String, Array, Function],
default: ''
},
format: Function
},
computed: {
/**
* 直线进度条的样式
* 根据百分比给出已完成进度部分的宽度和颜色
*/
barStyle() {
const style = {}
style.width = this.percentage + '%'
style.backgroundColor = this.getCurrentColor(this.percentage)
return style
},
/** 圆环线的宽 */
relativeStrokeWidth() {
return (this.strokeWidth / this.width * 100).toFixed(1)
},
/** 半径 */
radius() {
if (this.type === 'circle' || this.type === 'dashboard') {
return parseInt(50 - parseFloat(this.relativeStrokeWidth) / 2, 10)
} else {
return 0
}
},
/**
* 绘制环形进度条图形
* M = moveto(M X,Y) :将画笔移动到指定的坐标位置
* L = lineto(L X,Y) :画直线到指定的坐标位置
* H = horizontal lineto(H X):画水平线到指定的X坐标位置
* V = vertical lineto(V Y):画垂直线到指定的Y坐标位置
* C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次贝赛曲线
* S = smooth curveto(S X2,Y2,ENDX,ENDY)
* Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次贝赛曲线
* T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射
* A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧线
* Z = closepath():关闭路径
*
* m 同M,但使用的是相对坐标,参数x, y
*
* a 的命令参数:a rx ry x-axis-rotation large-arc-flag sweep-flag dx dy
* 弧形命令A的前两个参数分别是x轴半径和y轴半径,弧形命令A的第三个参数表示弧形的旋转情况,
* large-arc-flag(角度大小) 和sweep-flag(弧线方向),large-arc-flag决定弧线是大于还是小于180度,0表示小角度弧,1表示大角度弧。
* sweep-flag表示弧线的方向,0表示从起点到终点沿逆时针画弧,1表示从起点到终点沿顺时针画弧。
*/
trackPath() {
const radius = this.radius
const isDashboard = this.type === 'dashboard'
return `
M 50 50
m 0 ${isDashboard ? '' : '-'}${radius}
a ${radius} ${radius} 0 1 1 0 ${isDashboard ? '-' : ''}${radius * 2}
a ${radius} ${radius} 0 1 1 0 ${isDashboard ? '' : '-'}${radius * 2}
`
},
/** 圆的周长 */
perimeter() {
return 2 * Math.PI * this.radius
},
/** 仪表板比圆环少0.25的长度 */
rate() {
return this.type === 'dashboard' ? 0.75 : 1
},
/**
* 偏移量计算
* 由于仪表盘和环形的0.25缺口,所以偏移量不同
*/
strokeDashoffset() {
const offset = -1 * this.perimeter * (1 - this.rate) / 2
return `${offset}px`
},
/**
* 绘制图形
* strokeDasharray: 属性控制用于描边路径的破折号和间隙的模式。
* strokeDashoffset: 属性指定进入短划线模式的距离以开始短划线。
*/
trailPathStyle() {
return {
strokeDasharray: `${(this.perimeter * this.rate)}px, ${this.perimeter}px`,
strokeDashoffset: this.strokeDashoffset
}
},
circlePathStyle() {
return {
strokeDasharray: `${this.perimeter * this.rate * (this.percentage / 100)}px, ${this.perimeter}px`,
strokeDashoffset: this.strokeDashoffset,
transition: 'stroke-dasharray 0.6s ease 0s, stroke 0.6s ease'
}
},
/**
* 进度条颜色
* 根据用户传递的颜色来提供进度条颜色
* 或者根据当前状态来提供颜色
*/
stroke() {
let ret
if (this.color) {
ret = this.getCurrentColor(this.percentage)
} else {
switch (this.status) {
case 'success':
ret = '#13ce66'
break
case 'exception':
ret = '#ff4949'
break
case 'warning':
ret = '#e6a23c'
break
default:
ret = '#20a0ff'
}
}
return ret
},
/** 根据状态给圆环进度条中心位置更换icon */
iconClass() {
if (this.status === 'warning') {
return 'el-icon-warning'
}
if (this.type === 'line') {
return this.status === 'success' ? 'el-icon-circle-check' : 'el-icon-circle-close'
} else {
return this.status === 'success' ? 'el-icon-check' : 'el-icon-close'
}
},
/** 根据进度条类型调整文字说明的宽度 */
progressTextSize() {
return this.type === 'line'
? 12 + this.strokeWidth * 0.4
: this.width * 0.111111 + 2
},
/** 进度条文字 */
content() {
if (typeof this.format === 'function') {
return this.format(this.percentage) || ''
} else {
return `${this.percentage}%`
}
}
},
methods: {
/** 根据百分比获取颜色 */
getCurrentColor(percentage) {
if (typeof this.color === 'function') {
return this.color(percentage)
} else if (typeof this.color === 'string') {
return this.color
} else {
return this.getLevelColor(percentage)
}
},
/** 给颜色数组排序 */
getLevelColor(percentage) {
const colorArray = this.getColorArray().sort((a, b) => a.percentage - b.percentage)
for (let i = 0; i < colorArray.length; i++) {
if (colorArray[i].percentage > percentage) {
return colorArray[i].color
}
}
return colorArray[colorArray.length - 1].color
},
/**
* 获取颜色数组
* 让进度条可以根据不同的百分比显示不同的颜色
*/
getColorArray() {
const color = this.color
const span = 100 / color.length
return color.map((seriesColor, index) => {
if (typeof seriesColor === 'string') {
return {
color: seriesColor,
progress: (index + 1) * span
}
}
return seriesColor
})
}
}
}
</script>
qs 一个字符串序列化的库 收获: 如何通过预置方法初始化数据来提高传入需要处理数据的容错率、代码功能拆分组合理解
入口 lib/index.js stringify: 方法接受传入两个参数,第一个为需要格式的数据,第二个为输出的格式(第二参数接受自定义函数所以需要预先判定是否为 function )
if (typeof options.filter === 'function') {
filter = options.filter;
obj = filter('', obj);
} else if (isArray(options.filter)) {
filter = options.filter;
// 如果第二参数为数组,则根据传入的格式来进行 key 获取
objKeys = filter;
}
如果传入的数据格式是对象,则获取他的key 放入 objKeys
if (!objKeys) {
objKeys = Object.keys(obj);
}
如果是函数的话会挂载方法过去(normalizeStringifyOptions 内做了方法预处理)
// 使用参数预置初始化值
var defaults = {
addQueryPrefix: false,
allowDots: false,
charset: 'utf-8',
charsetSentinel: false,
delimiter: '&',
encode: true,
encoder: utils.encode,
encodeValuesOnly: false,
format: defaultFormat,
formatter: formats.formatters[defaultFormat],
// deprecated
indices: false,
serializeDate: function serializeDate(date) {
return toISO.call(date);
},
skipNulls: false,
strictNullHandling: false
};
var filter = defaults.filter;
if (typeof opts.filter === 'function' || isArray(opts.filter)) {
filter = opts.filter;
}
// 验证值是否存在 (undefined 值会被删掉就是因为这个, undefined 不被 boolean 识别)
skipNulls: typeof opts.skipNulls === 'boolean' ? opts.skipNulls : defaults.skipNulls,
识别处理格式并赋值备用
var arrayFormat;
if (opts && opts.arrayFormat in arrayPrefixGenerators) {
arrayFormat = opts.arrayFormat;
} else if (opts && 'indices' in opts) {
arrayFormat = opts.indices ? 'indices' : 'repeat';
} else {
arrayFormat = 'indices';
}
var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
根据获取到的 key 来进行具体数据获取拼接的数组
for (var i = 0; i < objKeys.length; ++i) {
var key = objKeys[i];
if (options.skipNulls && obj[key] === null) {
continue;
}
pushToArray(keys, stringify(
obj[key],
key,
generateArrayPrefix,
options.strictNullHandling,
options.skipNulls,
options.encode ? options.encoder : null,
options.filter,
options.sort,
options.allowDots,
options.serializeDate,
options.formatter,
options.encodeValuesOnly,
options.charset
));
}
// 把数据拼接成数组
var pushToArray = function (arr, valueOrArray) {
push.apply(arr, isArray(valueOrArray) ? valueOrArray : [valueOrArray]);
};
组装数据
var joined = keys.join(options.delimiter);
var prefix = options.addQueryPrefix === true ? '?' : '';
if (options.charsetSentinel) {
if (options.charset === 'iso-8859-1') {
// encodeURIComponent('✓'), the "numeric entity" representation of a checkmark
prefix += 'utf8=%26%2310003%3B&';
} else {
// encodeURIComponent('✓')
prefix += 'utf8=%E2%9C%93&';
}
}
return joined.length > 0 ? prefix + joined : '';
ms -Tiny milisecond conversion utility
/**
* Helpers.
*/
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var w = d * 7;
var y = d * 365.25; // why ? 见以下解析第1点
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} [options]
* @throws {Error} throw an error if val is not a non-empty string or a number
* @return {String|Number}
* @api public
*/
module.exports = function (val, options) {
options = options || {};
var type = typeof val;
// 字符串类型且不等于'',走parse方法
if (type === "string" && val.length > 0) {
return parse(val);
}
// number类型 且 为一个有限数值
else if (type === "number" && isFinite(val)) {
// long选项? fmtLong: fmtShort
return options.long ? fmtLong(val) : fmtShort(val);
}
// 类型错误报错
throw new Error(
"val is not a non-empty string or a valid number. val=" +
JSON.stringify(val)
);
};
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
// 字符串解析成数字
function parse(str) {
str = String(str);
// why? why 100? 见以下解析2,3点
if (str.length > 100) {
return;
}
// 基操正则..
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
str
);
if (!match) {
return;
}
var n = parseFloat(match[1]);
var type = (match[2] || "ms").toLowerCase();
switch (type) {
case "years":
case "year":
case "yrs":
case "yr":
case "y":
return n * y;
case "weeks":
case "week":
case "w":
return n * w;
case "days":
case "day":
case "d":
return n * d;
case "hours":
case "hour":
case "hrs":
case "hr":
case "h":
return n * h;
case "minutes":
case "minute":
case "mins":
case "min":
case "m":
return n * m;
case "seconds":
case "second":
case "secs":
case "sec":
case "s":
return n * s;
case "milliseconds":
case "millisecond":
case "msecs":
case "msec":
case "ms":
return n;
default:
return undefined;
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
// 直接相除四舍五入取整
function fmtShort(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return Math.round(ms / d) + "d";
}
if (msAbs >= h) {
return Math.round(ms / h) + "h";
}
if (msAbs >= m) {
return Math.round(ms / m) + "m";
}
if (msAbs >= s) {
return Math.round(ms / s) + "s";
}
return ms + "ms";
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
// long选项其实就是单位描述更加详细完整
function fmtLong(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return plural(ms, msAbs, d, "day");
}
if (msAbs >= h) {
return plural(ms, msAbs, h, "hour");
}
if (msAbs >= m) {
return plural(ms, msAbs, m, "minute");
}
if (msAbs >= s) {
return plural(ms, msAbs, s, "second");
}
return ms + " ms";
}
/**
* Pluralization helper.
*/
// 复数判断
function plural(ms, msAbs, n, name) {
// 复数判断,因为Math.round是四舍五入取整,所以此处判断使用n*1.5
var isPlural = msAbs >= n * 1.5;
// ms(89999, { long: true }) 1 minute
// ms(90000, { long: true }) 2 minutes
return Math.round(ms / n) + " " + name + (isPlural ? "s" : "");
}
其实 ms 的源码非常精简,也非常易读,但是我却从它的 History Commits 榨出了一些知识点:
第一版的 ms 代码其实是更简单的,当然了也存在一些问题。后面随着迭代和一些大佬(我在这里也看到了 tj 大佬,tj 大佬真是无处不在啊...)的 pr 贡献,发生了以下变化:
看源码,真的也能从它的 History Commits 学到很多~
100 多行,2kb 多的库,却有 3.2k 的 star,足已见得它的实用与易用。
Share your knowledge and repository sources from Github . ♥️
2020/10/19 - 2020/10/23 ~