0%

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

前言

  1. 这是跟随@若川 源码活动的第二个打卡。
  2. 该期活动地址【若川视野 x 源码共读】第24期 | vue2工具函数

工具函数

1. emptyObject

1
2
3
4
5
6
7
/*!
* Vue.js v2.6.14
* (c) 2014-2021 Evan You
* Released under the MIT License.
*/
/* */
var emptyObject = Object.freeze({});

Object.freeze()

Object.freeze() 方法可以冻结一个对象,冻结指的是不能向这个对象添加新的属性,不能修改其已有属性的值,不能删除已有属性,以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说,这个对象永远是不可变的。该方法返回被冻结的对象。

1
2
3
4
5
6
7
8
9
10
var deadline = Object.freeze({date: 'yesterday'});
deadline.date = 'tomorrow';
deadline.excuse = 'lame';
deadline.date; // 'yesterday' 不可以修改已有属性的值
deadline.excuse; // undefined 不可以向这个对象中添加新的属性
Object.isSealed(deadline); // true;
Object.isFrozen(deadline); // true 判断对象是否冻结的方法
Object.getOwnPropertyDescriptor(deadline, 'date');
// {value: "yesterday", writable: false, enumerable: true, configurable: false} (不可配置,不可写)
Object.keys(deadline); // ['date'] (可枚举)

2. isUndef

判断是否是未定义

1
2
3
4
5
// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.
function isUndef (v) {
return v === undefined || v === null
}

3. isDef

判断是否已经定义

1
2
3
4
5
// These helpers produce better VM code in JS engines due to their
// explicitness and function inlining.
function isDef (v) {
return v !== undefined && v !== null
}

4. isTrue

1
2
3
function isTrue (v) {
return v === true
}

5. isFalse

1
2
3
function isFalse (v) {
return v === false
}

6. isPrimitive

判断值是否是原始值

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Check if value is primitive.
*/
function isPrimitive (value) {
return (
typeof value === 'string' ||
typeof value === 'number' ||
// $flow-disable-line
typeof value === 'symbol' ||
typeof value === 'boolean'
)
}

7. isObject

判断是否是对象,没有区分对象和数组

1
2
3
4
5
6
7
8
/**
* Quick object check - this is primarily used to tell
* Objects from primitive values when we know the value
* is a JSON-compliant type.
*/
function isObejct(obj) {
return obj !== null && type of === 'object'
}

8. toRowType

转换成原始类型

1
2
3
4
5
6
7
var _toString = Object.prototype.toString;
function toRowType(value) {
return _toString.call(value).slice(8, -1)
}

toRowType('') // 'String'
toRowType()// 'undefined'

为什么需要.slice(8, -1)

  1. _toString.call(value) 生成结果[object String] 。
  2. 二、vue-shared.md __toString.call(value).slice(8, -1) 去掉[object和]。

9. isPlainObject

判断是否是纯对象

1
2
3
4
5
6
function isPlainObject (obj) {
return _toString.call(obj) === '[object Object]'
}

isPlainObject({}) // true
isPlainObject([])

10. isRegExp

判断是否是正则表达式

1
2
3
function isRegExp(obj) {
return _toString.call(obj) === '[object RegExp]'
}

11. isValidArrayIndex

判断是否是可用的数组索引值

1
2
3
4
function isValidArrayIndex(val) {
var n = parseFloat(String(val));
return n >= 0 && Math.floor(n) === n && isFinite(val);、
}
  1. 为什么要使用parseFloat?

    使用parseFloat可解析其他进制字符串时,有可能由于计算精度问题无法得到一个确定的十进制证书。

  2. 为什么使用isFinite

    is Finite()函数判断被传入的参数值是否为一个有限数值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    isFinite(Infinity);  // false
    isFinite(NaN); // false
    isFinite(-Infinity); // false

    isFinite(0); // true
    isFinite(2e64); // true

    isFinite('0'); // true
    Number.isFinite('0') // false
    Number.isFinite(null) // false

12. isPromise

判断是否是promise

1
2
3
4
5
6
7
8
9
funciton isPromise(val) {
return (
isDef(val) &&
typeof val.then === 'function' &&
typeof val.catch === 'function'
)
}

isPromise(new Promise()) // true

13. toString转字符串

1
2
3
4
5
6
7
8
9
let _toString = Object.prototype.toString;

function toString(val) {
return val == null
? ''
: Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)
? JSON.stringify(val, null, 2)
: String(val)
}
  1. 数组或者对象并且对象的toString 方法是Object.prototype.toString,用JSON.stringify转换

  2. JSON.stringify(val, null, 2)

    1. JSON.stringify(value, replacer, space)

      • value 序列化为一个JSON字符串的值
      • replacer 如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
      • space 指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为10。该值若小于1,则意味着没有空格;如果该参数为字符串(当字符串长度超过10个字母,取其前10个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
    2. 例子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      function replacer(key, value) {
      if (typeof value === "string") {
      return undefined;
      }
      return value;
      }

      var foo = {
      foundation: "Mozilla",
      model: "box",
      week: 45,
      transport: "car",
      month: 7
      };
      var jsonString = JSON.stringify(foo, replacer);// {week: 45, month: 7}

14. toNumber

转数字

1
2
3
4
5
6
7
8
9
10
function toNumber(val) {
var n = parseFloat(val);
return isNaN(n) ? val : n;
}


toNumber('a') // 'a'
toNumber('1') // 1
toNumber('1a') // 1
toNumber('a1') // 'a1'

15. makeMap生成一个map

传入一个以逗号分隔的字符串,生成一个map,并且返回一个函数检测key值在不在这个map中。第二个参数是小写选项。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 传入一个以逗号分隔的字符串,生成一个 map(键值对) ,并且返回一个函数检测 key 值在不在这个 map 中。第二个参数是小写选项。
function makeMap(str, expectsLowerCase) {
const map = Object.create(null);
let list = str.split(',');
for (let i = 0; i < list.length; i++) {
map[list[i]] = true;
}
return expectsLowerCase
? function (val) { return map[val.toLowerCase()]; }
: function (val) { return map[val] };
}

let str = '123,456,789';
let makeMapStrFunc = makeMap(str, false);
makeMapStrFunc('123'); // true
makeMapStrFunc('145'); // undefined

16. isBuiltInTag

判断是否是内置的Tag

1
2
3
4
5
6
var isBuiltInTag = makeMap('slot, component', true);

isBuiltInTag('slot') // true
isBuiltInTag('component') // true
isBuiltInTag('Slot') // true
isBuiltInTag('Component') // true

17. isReservedAttribute

判断是否是保留的属性

1
2
3
4
5
6
7
8
let isReservedAttribute = makeMap('key,ref,slot,slot-scope,is');

isReservedAttribute('key') // true
isReservedAttribute('ref') // true
isReservedAttribute('slot') // true
isReservedAttribute('slot-scope') // true
isReservedAttribute('is') // true
isReservedAttribute('IS') // undefined

18. remove

移除数组中的某一项

1
2
3
4
5
6
7
8
function remove(arr, item) {
if (arr.length) {
var index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1)
}
}
}

splice 其实是一个很耗性能的方法。删除数组中的一项,其他元素都要移动位置。

引申axios InterceptorManager 拦截器源码 中,拦截器用数组存储的。但实际移除拦截器时,只是把拦截器置为 null 。而不是用splice移除。最后执行时为 null 的不执行,同样效果。axios 拦截器这个场景下,不得不说为性能做到了很好的考虑。因为拦截器是用户自定义的,理论上可以有无数个,所以做性能考虑是必要的

看如下 axios 拦截器代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 代码有删减
// 声明
this.handlers = [];

// 移除
if (this.handlers[id]) {
this.handlers[id] = null;
}

// 执行
if (h !== null) {
fn(h);
}

19. hasOwn

检测是否是自己的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key)
}

let hasOwnProperty = Object.prototype.hasOwnProperty;
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key);
}

let obj = {
a: 123
}
console.log(obj.hasOwnProperty('a')); // true
console.log(hasOwn(obj, 'a')); // true
console.log(hasOwn(obj, 'b')); // false

20. cached

缓存

1
2
3
4
5
6
7
function cached (fn) {
let cache = Object.create(null);
return (function cachedFn(str) {
var hit = cached[str];
return hit || (cache[str] = fn(str));
})
}

21. camelize

连字符转小驼峰 如 on-click => onClick

1
2
3
4
var camelizeRE = /-(\w)/g;
var camelize = cached(function (str) {
return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : '' });
})
  1. \w 表示 [0-9a-zA-Z_]。表示数字、大小写字母和下划线。

  2. ```javascript
    String.prototype.replace(regexp | substr, newSubStr|function)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    我们来重点看一下第二个参数中的`function` 中的参数

    [MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace#%E6%8C%87%E5%AE%9A%E4%B8%80%E4%B8%AA%E5%87%BD%E6%95%B0%E4%BD%9C%E4%B8%BA%E5%8F%82%E6%95%B0)

    - `match`

    - `p1,p2, ...` 假如replace()方法的第一个参数是一个[`RegExp`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp) 对象,则代表第n个括号匹配的字符串。如果是用 `/(\a+)(\b+)/` 这个来匹配,`p1` 就是匹配的 `\a+`,`p2` 就是匹配的 `\b+`。

    > 因此,在这里,_ 的值是 -c, c 的值 是 c

    - ...

    # 22. capitalize

    > 首字母转大写

    ```javascript
    var capitalize = cached(function (str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
    })

23. hyphenate

小驼峰转连字符 onClick => on-click

1
2
3
4
var hyphenateRE = /\B([A-Z])/g;
var hyphenate = cached(function(str) {
return str.replace(hyphenateRE, '-$1').toLowerCase();
})

24. polyfillBind

bind垫片 兼容了老版本浏览器不支持原生的bind函数。同时兼容写法,对参数的多少做出了判断,使用call和apply实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

25. toArray

把类数组转成真正的数组, 支持从指定位置开始转化,默认从0开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 fn() {
var args = toArray(arguments);
console.log(args); // 1, 2, 3, 4, 5
var args2 = toArray(arguments, 2);
console.log(args2); // 3, 4, 5
}
fn(1, 2, 3, 4, 5)

26. extend

合并对象

1
2
3
4
5
6
7
8
9
10
11
12
13
function extend(to, _from) {
for (let key in _from) {
to[key] = _from[key];
}
return to;
}

// 例子
const data = { name: 'lily' };
const data2 = extend(data, { age: '18', name: 'lily1' });
console.log(data); // { age: '18', name: 'lily1' }
console.log(data2); // { age: '18', name: 'lily1' }
console.log(data === data2); // true

27. toObject

转对象, 传入的是个数组,后面的元素会覆盖前面的元素

1
2
3
4
5
6
7
8
9
10
11
12
13
function toObject(arr) {
var res = {};
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i])
}
}

return res;
}

//例子
toObject(['笑嘻嘻','哈哈呵呵']); // 哈哈呵呵

28. noop

空函数

1
function noop(a, b, c) {}

29. no

一直返回false

1
var no = function(a, b, c) { return false; };

30. identity

返回参数本身

1
var identity = function(_) {  return _; };

31. genStaticKeys

生成静态属性

1
2
3
4
5
function genStaticKeys (modules) {
return modules.reduce(function (keys, m) {
return keys.concat(m.staticKeys || [])
}, []).join(',')
}

32. looseEqual

宽松相等

由于数组、对象等是引用类型,所以两个内容看起来相等,严格相等都是不相等。

1
2
3
4
5
var a = {};
var b = {};
a === b; // false
a == b; // false
复制代码

所以该函数是对数组、日期、对象进行递归比对。如果内容完全相等则宽松相等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function looseEqual(a, b) {
if (a === b) { return true };
var isObjectA = isObject(a);
var isObjectB = isObject(b);
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])
})
} else if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
} 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 {
return false;
}
} catch (e) {
return false;
}
} else if (!isObjectA && !isObjectB) {
return String(a) === String(b);
} else {
return false;
}
}

33. looseIndexOf

宽松的in de x O f

1
2
3
4
5
6
function looseIndexOf(arr, val) {
for (var i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val) { return i };
}
return -1;
}

34. once

利用闭包特性,存储状态,实现函数只执行一次

1
2
3
4
5
6
7
8
9
function once(fn) {
var called = false;
return function () {
if (!called) {
called = true;
fn.apply(called, arguments);
}
}
}

一、提交的代码规范

husky + lint-staged + pre-commit

husky: 操作git钩子的工具

lint-staged: 本地暂存代码检查工具

pre-commit: 通过钩子函数,判断提交的代码是否符合规范

阅读全文 »

一、代码规范

工程没有具体统一的规范,包括有代码格式 , 命名规范 , 文档注释 。现在每个人都有自己的代码规范造成项目存在几种不同的规范,难以阅读。

阅读全文 »

1. 初始结构

原生

1
2
3
let promise1 = new Promise((resolve, reject) => {

})

[来源] 🔗6006.拿出最少数目的魔法豆

题目

给你一个 正 整数数组 beans ,其中每个整数表示一个袋子里装的魔法豆的数目。

请你从每个袋子中 拿出 一些豆子(也可以 不拿出),使得剩下的 非空 袋子中(即 至少 还有 一颗 魔法豆的袋子)魔法豆的数目 相等 。一旦魔法豆从袋子中取出,你不能将它放到任何其他的袋子中。

请你返回你需要拿出魔法豆的 最少数目。

示例 1:

1
2
3
4
5
6
7
8
9
10
11
12
输入:beans = [4,1,6,5]
输出:4
解释:
- 我们从有 1 个魔法豆的袋子中拿出 1 颗魔法豆。
剩下袋子中魔法豆的数目为:[4,0,6,5]
- 然后我们从有 6 个魔法豆的袋子中拿出 2 个魔法豆。
剩下袋子中魔法豆的数目为:[4,0,4,5]
- 然后我们从有 5 个魔法豆的袋子中拿出 1 个魔法豆。
剩下袋子中魔法豆的数目为:[4,0,4,4]
总共拿出了 1 + 2 + 1 = 4 个魔法豆,剩下非空袋子中魔法豆的数目相等。
没有比取出 4 个魔法豆更少的方案。

思路

  1. 拿出魔法豆 + 剩余魔法豆 = 初始魔法豆

  2. 考虑拿出最少数目的魔法豆,可以转化为剩余最多的魔法豆。

  3. beans数组的长度为n,对于每个i袋子,可以剩余的最多魔法豆为

    (n - i) * beans[i]

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
function minimumRemoval(beans: number[]): number {
let
len = beans.length,
sum = 0,
maxSum = 0; // 针对每个袋子可以拿出的魔法豆
beans.sort((a, b) => a - b)
for (let i = 0; i < beans.length; i++) {
sum += beans[i];
maxSum = Math.max(maxSum, beans[i] * (len - i) )
}

return sum - maxSum
};

[来源] 🔗55. 跳跃游戏

题目

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标。

示例 1:

1
2
3
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 13 步到达最后一个下标。

示例 2:

1
2
3
4
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

思路

  1. 首先这个思路和数组元素的索引值关系非常大。

  2. 对于数组 [2, 3, 1, 1, 4] 我们尽可能的取最大的跳跃长度,那么最大跳跃长度能跳到的索引位置为 2, 4, 3, 4

    我们可以得出 k = Math.max(k, nums[i] + i), k为能够跳到的最远位置。

    如果此时k == nums.length - 1 那么就能够到达最后一个标

  3. 此时考虑到以下情况,[1, 0, 2, 1, 4],针对每一个元素,最大跳跃长度能跳到的索引位置为[1, 1, 4, 4]。但是此时i = 1时,不能进行跳跃,也就是无法跳到下一个节点,那么尽管后面的元素跳跃长度能够到达最后一个下标,此时结果也为false。

    所以我们需要通过i 和 k值进行对比,当且仅当i ≤ k时,该节点能够进行下一个跳跃

代码

1
2
3
4
5
6
7
8
function canJump(nums: number[]): boolean {
let maxLen = 0, len = nums.length;
for (let i = 0; i < len; i++) {
if (i > maxLen) return false;
maxLen = Math.max(maxLen, nums[i] + i);
}
return true;
};