muxing-my-tools

1.0.9 • Public • Published

自定义工具函数库

一、各种自定义

1. 函数相关

1.1 call , apply , bind

1.1.1. API 说明

call()

语法: call(fn, obj, ...args)

功能: 执行fn, 使this为obj, 并将后面的n个参数传给fn(功能等同于函数对象的call方法)

apply()

语法: apply(fn, obj, args)

功能: 执行fn, 使this为obj, 并将args数组中的元素传给fn(功能等同于函数对象的apply方法)

bind()

语法: bind(fn, obj, ...args)

功能: 给fn绑定this为obj, 并指定参数为后面的n个参数 (功能等同于函数对象的bind方法)

1.1.2. 实现说明

区别call()/apply()/bind()

call(obj)/apply(obj): 调用函数, 指定函数中的this为第一个参数的值

bind(obj): 返回一个新的函数, 新函数内部会调用原来的函数, 且this为bind()指定的第一参数的值

注意: 如果obj是null/undefined, this为window

应用

call()/apply()应用: 根据伪数组生成真数组

bind(): react中组件的自定义方法 / vue中的事件回调函数内部

自定义call()/apply()

给obj添加一个临时方法, 方法名任意, 值为当前函数

通过obj调用这个临时方法, 并将接收的参数传入

删除obj上的这个临时方法属性

自定义实现bind()

返回一个新函数

在新函数内部通过原函数对象的call方法来执行原函数

指定原函数的this为obj

指定参数为bind调用的参数和后面新函数调用的参数

1.1.3. 编码实现

自定义函数对象的call方法: src/function/call.js

1 /*

2 自定义函数对象的call方法
3 */
4 export function call(fn, obj, ...args) {
5 console.log('call()')
6 ​
7 // 如果obj是undefined/null, this指定为window
8 if (obj===undefined || obj===null) {
9 // return fn(...args)
10 obj = window
11 }
12 ​
13 // 给obj添加一个临时方法, 方法指向的函数就是fn
14 obj.tempFn = fn
15 // 通过obj来调用这个方法 ==> 也就会执行fn函数 ==> 此时fn中的this肯定为obj
16 const result = obj.tempFn(...args)
17 // 删除obj上的临时方法
18 delete obj.tempFn
19 // 返回fn执行的结果
20 return result
21 }

自定义函数对象的apply方法: src/function/apply.js

1 /*

2 自定义函数对象的apply方法
3 */
4 export function apply(fn, obj, args) {
5 console.log('apply()')
6 ​
7 // 如果obj是undefined/null, this指定为window
8 if (obj===undefined || obj===null) {
9 // return fn(...args)
10 obj = window
11 }
12 ​
13 // 给obj添加一个临时方法, 方法指向的函数就是fn
14 obj.tempFn = fn
15 // 通过obj来调用这个方法 ==> 也就会执行fn函数 ==> 此时fn中的this肯定为obj
16 const result = obj.tempFn(...args)
17 // 删除obj上的临时方法
18 delete obj.tempFn
19 // 返回fn执行的结果
20 return result
21 }

自定义函数对象的bind方法: src/function/bind.js

1 import {call} from './call'
2 /*
3 自定义函数对象的bind方法
4 */
5 export function bind(fn, obj, ...args) {
6 console.log('bind()')
7 // 返回一个新函数
8 return (... args2) => {
9 // 通过call调用原函数, 并指定this为obj, 实参为args与args
10 return call(fn, obj, ...args, ...args2)
11 }
12 }
1 // call的实现
2 Function.prototype.call2 = function (context) {
3 var context = context || window;
4 context.fn = this;
5 ​
6 var args = [];
7 for(var i = 1 , len = arguments.length; i < len; i++) {
8 args.push('arguments[' + i + ']');
9 }
10 ​
11 var result = eval('context.fn(' + args +')');
12 ​
13 delete context.fn
14 return result;
15 }
16 ​
17 // 测试一下
18 var value = 2 ;
19 ​
20 var obj = {
21 value: 1
22 }
23 ​
24 function bar(name, age) {
25 console.log(this.value);
26 return {
27 value: this.value,
28 name: name,
29 age: age
30 }
31 }
32 ​
33 bar.call2(null); // 2
34 ​
35 console.log(bar.call2(obj, 'kevin', 18 ));
1 // apply实现
2 Function.prototype.apply = function (context, arr) {
3 var context = Object(context) || window;
4 context.fn = this;
5 ​
6 var result;
7 if (!arr) {
8 result = context.fn();
9 }else {
10 var args = [];
11 for (var i = 0 , len = arr.length; i < len; i++) {
12 args.push('arr[' + i + ']');
13 }
14 result = eval('context.fn(' + args + ')')
15 }
16 ​
17 delete context.fn
18 return result;
19 }
1 // bind实现
2 Function.prototype.bind2 = function (context) {
3 if (typeof this !== "function") {
4 throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
5 }
6 var self = this;
7 var args = Array.prototype.slice.call(arguments, 1 );
8 var fNOP = function () {};
9 var fBound = function () {
10 var bindArgs = Array.prototype.slice.call(arguments);
11 return self.apply(this instanceof fNOP? this : context, args.concat(bindArgs));
12 }
13 fNOP.prototype = this.prototype;
14 fBound.prototype = new fNOP();
15 return fBound;
16 }

1.2. 函数节流与函数防抖

(^) 1.2.1. 相关理解

事件频繁触发可能造成的问题?

一些浏览器事件:window.onresize、window.mousemove等,触发的频率非常高,会造成界面卡顿

如果向后台发送请求,频繁触发,对服务器造成不必要的压力

如何限制事件处理函数频繁调用

函数节流

函数防抖

函数节流(throttle)

理解:

在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次

适合多次事件按时间做平均分配触发

场景:

窗口调整(resize)

⻚面滚动(scroll)

DOM 元素的拖拽功能实现(mousemove)

抢购疯狂点击(click)

函数防抖(debounce)

理解:

在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。

适合多次事件一次响应的情况

场景:

输入框实时搜索联想(keyup/input)

区别函数节流与防抖

1.2.2.API 说明

throttle() 节流

语法: throttle(callback, wait)

功能: 创建一个节流函数,在 wait 毫秒内最多执行 callback 一次

debounce() 防抖

语法: debounce(callback, wait)

功能: 创建一个防抖动函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 callback

1.2.3.编码实现

src/function/throttle.js: 函数节流

1 /*

2 实现函数节流

3 - 语法: throttle(callback, wait)
4 - 功能: 创建一个节流函数,在 wait 毫秒内最多执行 `callback` 一次
5 */
6 export function throttle(callback, wait) {
7 let start = 0
8 // 返回一个事件监听函数(也就是节流函数)
9 return function (event) {
10 console.log('throttle event')
11 // 只有当距离上次处理的时间间隔超过了wait时, 才执行处理事件的函数
12 const current = Date.now()
13 if ( current - start > wait) {
14 callback.call(this, event) // 需要指定this和参数
15 start = current
16 }
17 }
18 }

src/function/debounce.js: 函数防抖

1 /*

2 实现函数防抖

3 - 语法: debounce(callback, wait)
4 - 功能: 创建一个防抖动函数,该函数会从上一次被调用后,延迟 `wait` 毫秒后调用 `callback`
5 */
6 export function debounce (callback, wait) {
7 // 用来保存定时器任务的标识id
8 let timeoutId = - 1
9 // 返回一个事件监听函数(也就是防抖函数)
10 return function (event) {
11 console.log('debounce event')
12 // 清除未执行的定时器任务
13 if (timeoutId!==- 1 ) {
14 clearTimeout(timeoutId)
15 }
16 // 启动延迟 await 时间后执行的定时器任务
17 timeoutId = setTimeout(() => {
18 // 调用 callback 处理事件
19 callback.call(this, event)
20 // 处理完后重置标识
21 timeoutId = - 1
22 }, wait)
23 }
24 }

2. 数组相关

2.1. API 列表

1. map()

2. reduce()

3. filter()

4. find()

5. findIndex()

6. every()

7. some()

8. unique1() / unique2() / unique3()

9. concat()

10. slice()

11. flatten()

12. chunk() / chunk2()

13. difference()

14. pull()

15. pullAll()

16. drop()

17. dropRight()

2.2. 2.2.1. 数组声明式系列方法使用数组声明式系列方法

map(): 返回一个由回调函数的返回值组成的新数组

reduce(): 从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次

回调函数,并返回最后一次回调函数的返回值

filter(): 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回

find(): 找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回 undefined。

findIndex(): 找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1。

every(): 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。

some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。

2.2.2. 编码实现

1 src/array/declares.js: 实现数组声明式处理系列工具函数
2 /*
3 实现map()
4 */
5 export function map (array, callback) {
6 const arr = []
7 for (let index = 0 ; index < array.length; index++) {
8 // 将callback的执行结果添加到结果数组中
9 arr.push(callback(array[index], index))
10 }
11 return arr
12 }
13 ​
14 /*
15 实现reduce()
16 */
17 export function reduce (array, callback, initValue) {
18 let result = initValue
19 for (let index = 0 ; index < array.length; index++) {
20 // 调用回调函数将返回的结果赋值给result
21 result = callback(result, array[index], index)
22 }
23 return result
24 }
25 ​
26 /*
27 实现filter()
28 */
29 export function filter(array, callback) {
30 ​
31 const arr = []
32 for (let index = 0 ; index < array.length; index++) {
33 if (callback(array[index], index)) {
34 arr.push(array[index])
35 }

36 }

37 return arr
38 }
39 ​
40 /*
41 实现find()
42 */
43 export function find (array, callback) {
44 for (let index = 0 ; index < array.length; index++) {
45 if (callback(array[index], index)) {
46 return array[index]
47 }
48 }
49 return undefined
50 }
51 ​
52 /*
53 实现findIndex()
54 */
55 export function findIndex (array, callback) {
56 for (let index = 0 ; index < array.length; index++) {
57 if (callback(array[index], index)) {
58 return index
59 }
60 }
61 return - 1
62 }
63 ​
64 /*
65 实现every()
66 */
67 export function every (array, callback) {
68 for (let index = 0 ; index < array.length; index++) {
69 if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回false
70 return false
71 }
72 }
73 return true
74 }
75 ​
76 /*
77 实现some()
78 */
79 export function some (array, callback) {
80 for (let index = 0 ; index < array.length; index++) {
81 if (callback(array[index], index)) { // 只有一个结果为true, 直接返回true
82 return true
83 }
84 }
85 return false
86 }

2.3.2.3.1.API 数组去重说明

根据当前数组产生一个去除重复元素后的新数组

如: [2, 3, 2, 7, 6, 7] ==> [2, 3, 7, 6]

2.3.2. 实现

方法1: 利用forEach()和indexOf()

说明: 本质是双重遍历, 效率差些

方法2: 利用forEach() + 对象容器

说明: 只需一重遍历, 效率高些

方法3: 利用ES6语法: from + Set 或者 ... + Set

说明: 编码简洁

2.3.3. src/array/unique.js 编码实现

1 /*
2 方法 1 : 利用forEach()和indexOf()
3 说明: 本质是双重遍历, 效率差些
4 */
5 export function unique1 (array) {
6 const arr = []
7 array.forEach(item => {
8 if (arr.indexOf(item)===- 1 ) {
9 arr.push(item)
10 }
11 })
12 return arr
13 }
14 ​
15 /*
16 方法 2 : 利用forEach() + 对象容器
17 说明: 只需一重遍历, 效率高些
18 */
19 export function unique2 (array) {
20 const arr = []
21 const obj = {}
22 array.forEach(item => {
23 if (!obj.hasOwnProperty(item)) {
24 obj[item] = true
25 arr.push(item)
26 }
27 })
28 return arr
29 }
30 ​
31 /*
32 方法 3 : 利用ES6语法
33 1 ). from + Set
34 2 ). ... + Set
35 说明: 编码简洁
36 */
37 export function unique3 (array) {
38 // return Array.from(new Set(array))
39 return [...new Set(array)]
40 }

2.4. 数组合并与切片

2.4.1. API concat(): 说明 合并

语法: var new_array = concat(array, value1[, value2[, ...[, valueN]]])

功能: 将n个数组或值与当前数组合并生成一个新数组, 原始数组不会被改变

slice(): 切片

语法: var new_array = slice(array, [begin[, end]])

功能: 返回一个由 begin 和 end 决定的原数组的浅拷⻉, 原始数组不会被改变

2.4.2. 编码实现 src/array/concat.js: 自定义数组合并

1 /*
2 语法: var new_array = concat(old_array, value1[, value2[, ...[, valueN]]])
3 功能: 将n个数组或值与当前数组合并生成一个新数组
4 */
5 export function concat (array, ...values) {
6 const arr = [...array]
7 values.forEach(value => {
8 if (Array.isArray(value)) {
9 arr.push(...value)
10 } else {
11 arr.push(value)
12 }
13 })
14 return arr
15 }

src/array/slice.js: 自定义数组切片

1 /*

2 语法: var new_array = slice(oldArr, [begin[, end]])
3 功能: 返回一个由 begin 和 end 决定的原数组的浅拷⻉, 原始数组不会被改变
4 */
5 export function slice (array, begin, end) {
6 // 如果当前数组是[], 直接返回[]
7 if (array.length === 0 ) {
8 return []
9 }
10 ​
11 // 如果begin超过最大下标, 直接返回[]
12 begin = begin || 0
13 if (begin >= array.length) {
14 return []
15 }
16 ​
17 // 如果end不大于begin, 直接返回[]
18 end = end || array.length
19 if (end > array.length) {
20 end = array.length
21 }
22 if (end <= begin) {
23 return []
24 }
25 ​
26 // 取出下标在[begin, end)区间的元素, 并保存到最终的数组中
27 const arr = []
28 for (let index = begin; index < end; index++) {

29 arr.push(array[index]) 30 } 31 ​ 32 return arr 33 }

2.5. 数组扁平化

2.5.1.API 说明

语法: flatten(array)

取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中

如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]

2.5.2. 编码实现 src/array/flatten.js

方法一: 递归 + reduce() + concat()

方法二: ... + some() + concat()

1 /*

2 数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中

3 如: [ 1 , [ 3 , [ 2 , 4 ]]] ==> [ 1 , 3 , 2 , 4 ]

4 */

5 ​

6 /*

7 方法一: 递归 + reduce() + concat()
8 */
9 export function flatten1 (array) {
10 return array.reduce((pre, item) => {
11 if (Array.isArray(item) && item.some(cItem => Array.isArray(cItem))) {
12 return pre.concat(flatten1(item))
13 } else {
14 return pre.concat(item)
15 }
16 }, [])
17 }
18 ​
19 /*
20 方法二: ... + some() + concat()
21 */
22 export function flatten2 (array) {
23 let arr = [].concat(...array)
24 while (arr.some(item => Array.isArray(item))) {
25 arr = [].concat(...arr)
26 }
27 return arr
28 }

2.6.2.6.1.API 数组分块说明

语法: chunk(array, size)

功能: 将数组拆分成多个 size ⻓度的区块,每个区块组成小数组,整体组成一个二维数组

如: [1, 3, 5, 6, 7, 8] 调用chunk(arr, 4) ==> [[1, 3, 5, 6], [7,8]]

2.6.2. 编码实现 src/array/chunk.js

1 /*
2 将数组拆分成多个 size ⻓度的区块,每个区块组成小数组,整体组成一个二维数组

3 */

4 export function chunk (array, size) {
5 if (array.length=== 0 ) {
6 return []
7 }
8 size = size || 1
9 ​
10 const bigArr = []
11 let smallArr = []
12 ​
13 array.forEach(item => {
14 if (smallArr.length=== 0 ) {
15 bigArr.push(smallArr)
16 }
17 smallArr.push(item)
18 if (smallArr.length===size) {
19 smallArr = []
20 }
21 })
22 ​
23 return bigArr
24 }

2.7. 数组取差异

2.7.1.API 说明

语法: difference(arr1, arr2)

功能: 得到当前数组中所有不在arr中的元素组成的数组(不改变原数组)

例子: difference([1,3,5,7], [5, 8]) ==> [1, 3, 7]

2.7.2. 编码实现 src/array/difference.js

1 /*
2 difference(arr1, arr2): 得到arr1中所有不在arr2中的元素组成的数组(不改变原数组)
3 如: [ 1 , 3 , 5 , 7 ].difference([ 5 , 8 ]) ==> [ 1 , 3 , 7 ]
4 */
5 export function difference (arr1, arr2=[]) {
6 if (arr1.length=== 0 ) {
7 return []
8 } else if (arr2.length=== 0 ) {
9 return arr1.slice()
10 }
11 return arr1.filter(item => arr2.indexOf(item)===- 1 )
12 }

2.8.2.8.1.API 删除数组中部分元素相关

pull(array, ...values):

删除原数组中与value相同的元素, 返回所有删除元素的数组

说明: 原数组发生了改变

如: pull([1,3,5,3,7], 2, 7, 3, 7) ===> 原数组变为[1, 5], 返回值为[3,3,7]

pullAll(array, values):

功能与pull一致, 只是参数变为数组

如: pullAll([1,3,5,3,7], [2, 7, 3, 7]) ===> 数组 1 变为[1, 5], 返回值为[3,3,7]

2.8.2. 编码实现

src/array/pull.js

1 /*

2 1. pull(array, ...values):
3 删除数组中与value相同的元素, 返回所有删除元素的数组
4 说明: 数组发生了改变
5 如: pull([ 1 , 3 , 5 , 3 , 7 ], 2 , 7 , 3 , 7 ) ===> 数组变为[ 1 , 5 ], 返回值为[ 3 , 3 , 7 ]
6 2. pullAll(array, values):
7 功能与pull一致, 只是参数变为数组
8 如: pullAll([ 1 , 3 , 5 , 3 , 7 ], [ 2 , 7 , 3 , 7 ]) ===> 数组变为[ 1 , 5 ], 返回值为[ 3 , 3 , 7 ]
9 */
10 export function pull (array, ...values) {
11 if (array.length=== 0 || values.length=== 0 ) {
12 return []
13 }
14
15 var result = []
16 for (let index = 0 ; index < array.length; index++) {
17 const item = array[index]
18 if (values.indexOf(item)!==- 1 ) {
19 array.splice(index, 1 )
20 result.push(item)
21 index--
22 }
23 }
24 ​
25 return result
26 }
27 ​
28 export function pullAll (array, values) {
29 if (!values || !Array.isArray(values)) {
30 return []
31 }
32 return pull(array, ...values)
33 }

2.9. 2.9.1.API 得到数组的部分元素相关

drop(array, count)

得到当前数组过滤掉左边count个后剩余元素组成的数组

说明: 不改变当前数组, count默认是 1

如: drop([1,3,5,7], 2) ===> [5, 7]

dropRight(array, count)

得到当前数组过滤掉右边count个后剩余元素组成的数组

说明: 不改变当前数组, count默认是 1

如: dropRight([1,3,5,7], 2) ===> [1, 3]

2.9.2. 编码实现

src/array/drop.js

1 /*

2 1. drop(array, count):
3 得到数组过滤掉左边count个后剩余元素组成的数组
4 说明: 不改变当前数组, count默认是 1
5 如: drop([ 1 , 3 , 5 , 7 ], 2 ) ===> [ 5 , 7 ]
6 2. dropRight(array, count):
7 得到数组过滤掉右边count个后剩余元素组成的数组
8 说明: 不改变数组, count默认是 1
9 如: dropRight([ 1 , 3 , 5 , 7 ], 2 ) ===> [ 1 , 3 ]
10 */
11 ​
12 export function drop (array, count= 1 ) {
13 if (array.length === 0 || count >= array.length) {
14 return []
15 }
16 ​
17 return array.filter((item, index) => index>=count)
18 }
19 ​
20 export function dropRight (array, count= 1 ) {
21 if (array.length === 0 || count >= array.length) {
22 return []
23 }
24 ​
25 return array.filter((item, index) => index < array.length-count)
26 }

3. 对象相关

3.1. 相关 API

newInstance()

myInstanceOf()

mergeObject()

clone1() / clone2()

deepClone1() / deepClone2() / deepClone3() / deepClone4()

3.2.3.2.1.API 自定义相关 new

语法: newInstance(Fn, ...args)

功能: 创建Fn构造函数的实例对象

3.2.3. 编码实现 src/object/newInstance.js

1 export function newInstance (Fn, ...args) {
2 // 创建一个空的object实例对象obj, 作为Fn的实例对象
3 const obj = {}
4 // 将Fn的prototype属性值赋值给obj的__proto__属性值
5 obj.__proto__ = Fn.prototype
6 // 调用Fn, 指定this为obj, 参数为args列表
7 const result = Fn.call(obj, ...args)
8 // 如果Fn返回的是一个对象类型, 那返回的就不再是obj, 而是Fn返回的对象
9 // 否则返回obj
10 return result instanceof Object? result : obj
11 }
12 ​

3.3.3.3.1. API 自定义相关 instanceof

语法: myInstanceOf(obj, Type)

功能: 判断obj是否是Type类型的实例

实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回tru, 否则返回false

3.3.2. 编码实现 src/object/myInstanceOf.js

1 export function myInstanceOf(obj, Type) {
2 // 得到原型对象
3 let protoObj = obj.__proto__
4 ​
5 // 只要原型对象存在
6 while(protoObj) {
7 // 如果原型对象是Type的原型对象, 返回true
8 if (protoObj === Type.prototype) {
9 return true
10 }
11 // 指定原型对象的原型对象
12 protoObj = protoObj.__proto__
13 }
14
15 return false
16 }

3.4.3.4.1.API 合并多个对象相关

语法: object mergeObject(...objs)

功能: 合并多个对象, 返回一个合并后对象(不改变原对象)

例子:

{ a: [{ x: 2 }, { y: 4 }], b: 1}

{ a: { z: 3}, b: [2, 3], c: 'foo'}

合并后: { a: [ { x: 2 }, { y: 4 }, { z: 3 } ], b: [ 1, 2, 3 ], c: 'foo' }

3.4.2. 编码实现 src/object/mergeObject.js

1 export function mergeObject(...objs) {
2 const result = {}
3 ​
4 // 遍历objs
5 objs.forEach(obj => {
6 Object.keys(obj).forEach(key => {
7 // 如果result还没有key值属性
8 if (!result.hasOwnProperty(key)) {
9 result[key] = obj[key]
10 } else { // 否则 合并属性
11 result[key] = [].concat(result[key], obj[key])
12 }
13 })
14 })
15 ​
16 // 可以使用reduce来代替forEach手动添加
17 return result
18 }

3.5. 3.5.1. 对象区别浅拷⻉与深拷⻉ / 数组拷⻉

纯语言表达:

浅拷⻉: 只是复制了对象属性或数组元素本身(只是引用地址值)

深拷⻉: 不仅复制了对象属性或数组元素本身, 还复制了指向的对象(使用递归)

举例说明: 拷⻉persons数组(多个人对象的数组)

浅拷⻉: 只是拷⻉了每个person对象的引用地址值, 每个person对象只有一份

深拷⻉: 每个person对象也被复制了一份新的

3.5.2. 实现浅拷⻉ src/object/clone.js

1 /*
2 实现浅拷⻉
3 方法一: 利用ES6语法
4 方法二: 利用ES5语法: for...in
5 */
6 /* 方法一: 利用ES6语法*/
7 export function clone1(target) {
8 // 如果是对象(不是函数, 也就是可能是object对象或者数组)
9 if (target!=null && typeof target==='object') {
10 if (target instanceof Array) {
11 // return target.slice()
12 // return target.filter(() => true)
13 // return target.map(item => item)
14 return [...target]
15 } else {
16 // return Object.assign({}, target)
17 return {...target}
18 }
19 }
20 // 基本类型或者函数, 直接返回
21 return target
22 }
23 ​
24 /* 方法二: 利用ES5语法: for...in */
25 export function clone2(target) {
26 if (target!=null && typeof target==='object') {
27 const cloneTarget = Array.isArray(target)? [] : {}
28 for (let key in target) {
29 if (target.hasOwnProperty(key)) {
30 cloneTarget[key] = target[key]
31 }
32 }
33 return cloneTarget
34 } else {
35 return target
36 }
37 }

3.5.3. 实现深拷⻉

实现一: 大众乞丐版

问题1: 函数属性会丢失

问题2: 循环引用会出错

实现二: 面试基础版

解决问题1: 函数属性还没丢失

实现三: 面试加强版本

解决问题2: 循环引用正常

实现四: 面试加强版本2(优化遍历性能)

数组: while | for | forEach() 优于 for-in | keys()&forEach()

对象: for-in 与 keys()&forEach() 差不多

编码实现: src/object/deepClone.js

1 /*

2 深度克隆

3 1 ). 大众乞丐版

4 问题 1 : 函数属性会丢失

5 问题 2 : 循环引用会出错

6 2 ). 面试基础版本

7 解决问题 1 : 函数属性还没丢失

8 3 ). 面试加强版本

9 解决问题 2 : 循环引用正常

10 4 ). 面试加强版本 2 (优化遍历性能)

11 数组: while | for | forEach() 优于 for-in | keys()&forEach() 12 对象: for-in 与 keys()&forEach() 差不多 13 / 14 ​ 15 / 16 1 ). 大众乞丐版 17 问题 1 : 函数属性会丢失 18 问题 2 : 循环引用会出错 19 / 20 export function deepClone1(target) { 21 return JSON.parse(JSON.stringify(target)) 22 } 23 ​ 24 / 25 2 ). 面试基础版本 26 解决问题 1 : 函数属性还没丢失 27 / 28 export function deepClone2 (target) { 29 if (target!==null && typeof target==='object') { 30 const cloneTarget = target instanceof Array? [] : {} 31 32 for (const key in target) { 33 if (target.hasOwnProperty(key)) { 34 cloneTarget[key] = deepClone2(target[key]) 35 } 36 } 37 ​ 38 return cloneTarget 39 } 40 ​ 41 return target 42 } 43 ​ 44 / 45 3 ). 面试加强版本 46 解决问题 2 : 循环引用正常 47 */ 48 export function deepClone3 (target, map=new Map()) { 49 if (target!==null && typeof target==='object') {

50 // 从缓存容器中读取克隆对象

51 let cloneTarget = map.get(target) 52 // 如果存在, 返回前面缓存的克隆对象 53 if (cloneTarget) { 54 return cloneTarget 55 } 56 // 创建克隆对象(可能是{}或者[]) 57 cloneTarget = target instanceof Array? [] : {} 58 // 缓存到map中 59 map.set(target, cloneTarget) 60 ​ 61 for (const key in target) { 62 if (target.hasOwnProperty(key)) { 63 // 递归调用, 深度克隆对象, 且传入缓存容器map 64 cloneTarget[key] = deepClone3(target[key], map) 65 } 66 } 67 ​ 68 return cloneTarget 69 } 70 ​ 71 return target 72 } 73 ​ 74 /* 75 4 ). 面试加强版本 2 (优化遍历性能) 76 数组: while | for | forEach() 优于 for-in | keys()&forEach() 77 对象: for-in 与 keys()&forEach() 差不多 78 */ 79 export function deepClone4 (target, map=new Map()) { 80 if (target!==null && typeof target==='object') { 81 // 从缓存容器中读取克隆对象 82 let cloneTarget = map.get(target) 83 // 如果存在, 返回前面缓存的克隆对象 84 if (cloneTarget) { 85 return cloneTarget 86 } 87 // 创建克隆对象(可能是{}或者[]) 88 if (target instanceof Array) { 89 cloneTarget = [] 90 // 缓存到map中 91 map.set(target, cloneTarget) 92 target.forEach((item, index) => { 93 cloneTarget[index] = deepClone4(item, map) 94 }) 95 } else { 96 cloneTarget = {} 97 // 缓存到map中 98 map.set(target, cloneTarget) 99 Object.keys(target).forEach(key => { 100 cloneTarget[key] = deepClone4(target[key], map) 101 }) 102 } 103 ​

104 return cloneTarget
105 }
106 ​
107 return target
108 }

4.4.1. API 字符串相关相关

字符串倒序

语法: reverseString(str)

功能: 生成一个倒序的字符串

字符串是否是回文

语法: palindrome(str)

功能: 如果给定的字符串是回文,则返回 true ;否则返回 false

截取字符串

语法: truncate(str, num)

功能: 如果字符串的⻓度超过了num, 截取前面num⻓度部分, 并以...结束

4.2. 编码实现 src/string/index.js

1 /*

2 1. 字符串倒序: reverseString(str) 生成一个倒序的字符串
3 */
4 export function reverseString(str) {
5 // return str.split('').reverse().join('')
6 // return [...str].reverse().join('')
7 return Array.from(str).reverse().join('')
8 }
9 ​
10 /*
11 2. 字符串是否是回文: palindrome(str) 如果给定的字符串是回文,则返回 true ;否则返回 false
12 */
13 export function palindrome(str) {
14 return str === reverseString(str)
15 }
16 ​
17 /*
18 3. 截取字符串: truncate(str, num) 如果字符串的⻓度超过了num, 截取前面num⻓度部分, 并以...结束
19 */
20 export function truncate(str, num) {
21 return str.length > num? str.slice( 0 , num) + '...' : str
22 }
23 ​

6. 手写 DOM 事件监听 ( 带委托 )

6.1. 理解事件冒泡与事件委托

事件冒泡的流程

基于DOM树形结构

事件在目标元素上处理后, 会由内向外(上)逐层传递

应用场景: 事件代理/委托/委派

事件委托/代理

将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上

好处:

减少内存占用(事件监听回调从n变为

动态添加的内部元素也能响应

6.2. API 相关

语法:addEventListener(element, type, fn, selector)

说明:如果selector没有,直接给element绑定事件,如果selector有,将selector对应的多个元素的事件委托绑

定给父元素element

6.2. 编码实现

src/event-bind/index.js

1 /*

2 语法:addEventListener(element, type, fn, selector)
3 说明:如果selector没有,直接给element绑定事件,
4 如果selector有,将selector对应的多个元素的事件委托绑定给父元素element
5 */
6 export function addEventListener(element, type, fn, selector) {
7 // 如果没有指定selector, 普通的事件绑定
8 if (!selector) {
9 element.addEventListener(type, fn)
10 } else {// 否则是代委托的事件绑定
11 element.addEventListener(type, function (event) {
12 // 得到真正发生事件的目标元素
13 const target = event.target
14 // 如果与选择器匹配
15 if (target.matches(selector)) {
16 // 调用处理事件的回调fn, 并指定this为目标元素, 参数为event
17 fn.call(target, event)
18 }
19 })
20 }
21 }

7.7.1. A P I 手写 ajax 相关请求函数

语法:

axios(options)

参数配置对象:url, method, params与data

返回值为:promise对象

axios.get(url, options)

axios.post(url, data, options)

axios.put(url, data, options)

axios.delete(url, options)

功能:使用xhr发送ajax请求的工具函数,与axios库功能类似

7. 2. 实现整体流程

1. 函数的参数为一个配置对象

{ url: '', // 请求地址 method: '', // 请求方式GET/POST/PUT/DELETE params: {}, // GET/DELETE请求的query参数 data:

{}, // POST或DELETE请求的请求体参数 }

2. 返回值: 函数的返回值为promise, 成功的结果为response, 失败的结果为error

3. 能处理多种类型的请求: GET/POST/PUT/DELETE

4. 响应json数据自动解析为js的对象/数组

7. 3. 编码实现 src/axios/index.js

1 /* 发送任意类型请求的函数 */

2 function axios({
3 url,
4 method='GET',
5 params={},
6 data={}
7 }) {
8 // 返回一个promise对象

9 return new Promise((resolve, reject) => { 10 ​ 11 // 处理method(转大写) 12 method = method.toUpperCase() 13 14 // 处理query参数(拼接到url上) id=1&xxx=abc 15 /* { id: 1, xxx: 'abc'} */ 16 let queryString = '' 17 Object.keys(params).forEach(key => { 18 queryString += ${key}=${params[key]}& 19 }) 20 if (queryString) { // id=1&xxx=abc& 21 // 去除最后的& 22 queryString = queryString.substring( 0 , queryString.length- 1 ) 23 // 接到url 24 url += '?' + queryString 25 } 26 ​ 27 ​ 28 // 1. 执行异步ajax请求 29 // 创建xhr对象 30 const request = new XMLHttpRequest() 31 // 打开连接(初始化请求, 没有请求) 32 request.open(method, url, true) 33 34 // 发送请求 35 if (method==='GET') { 36 request.send() 37 } else if (method==='POST' || method==='PUT' || method==='DELETE'){ 38 // 告诉服务器请求体的格式是json 39 request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') 40 // 发送json格式请求体参数 41 request.send(JSON.stringify(data)) 42 } 43 44 // 绑定状态改变的监听 45 request.onreadystatechange = function () { 46 // 如果请求没有完成, 直接结束 47 if (request.readyState!== 4 ) { 48 return 49 } 50 // 如果响应状态码在[200, 300)之间代表成功, 否则失败 51 const {status, statusText} = request 52 // 2.1. 如果请求成功了, 调用resolve() 53 if (status>= 200 && status<= 299 ) { 54 // 准备结果数据对象response 55 const response = { 56 data: JSON.parse(request.response), 57 status, 58 statusText 59 } 60 resolve(response) 61 } else { // 2.2. 如果请求失败了, 调用reject() 62 reject(new Error('request error status is ' + status))

63 }

64 }

65 })

66 }

67 ​

68 /* 发送特定请求的静态方法 */

69 axios.get = function (url, options) {
70 return axios(Object.assign(options, {url, method: 'GET'}))
71 }
72 axios.delete = function (url, options) {
73 return axios(Object.assign(options, {url, method: 'DELETE'}))
74 }
75 axios.post = function (url, data, options) {
76 return axios(Object.assign(options, {url, data, method: 'POST'}))
77 }
78 axios.put = function (url, data, options) {
79 return axios(Object.assign(options, {url, data, method: 'PUT'}))
80 }
81 ​
82 export default axios

8. 手写事件总线

8.1.API 说明

1. eventBus: 包含所有功能的事件总线对象

2. eventBus.on(eventName, listener): 绑定事件监听

3. eventBus.emit(eventName, data): 分发事件

4. eventBus.off(eventName): 解绑指定事件名的事件监听, 如果没有指定解绑所有

8.2. 编码实现 src/event-bus/index.js

1 const eventBus = {}
2 ​
3 /*
4 {
5 add: [callback1, callback2]
6 delete: [callback3]
7 }
8 */
9 let callbacksObj = {}
10 ​
11 /*
12 绑定事件监听
13 */
14 eventBus.on = function (eventName, callback) {
15 const callbacks = callbacksObj[eventName]
16 if (callbacks) {
17 callbacks.push(callback)
18 } else {
19 callbacksObj[eventName] = [callback]
20 }
21 }
22 ​
23 /*
24 分发事件

25 */

26 eventBus.emit = function (eventName, data) {
27 const callbacks = callbacksObj[eventName]
28 if (callbacks && callbacks.length > 0 ) {
29 callbacks.forEach(callback => {
30 callback(data)
31 })
32 }
33 }
34 ​
35 /*
36 移除事件监听
37 */
38 eventBus.off = function (eventName) {
39 if (eventName) {
40 delete callbacksObj[eventName]
41 } else {
42 callbacksObj = {}
43 }
44 }
45 ​
46 export default eventBus
47 ​

9.9.1.API 手写消息订阅与发布说明

1. PubSub: 包含所有功能的订阅/发布消息的管理者

2. PubSub.subscribe(msg, subscriber): 订阅消息: 指定消息名和订阅者回调函数

3. PubSub.publish(msg, data): 异步发布消息: 指定消息名和数据

4. PubSub.publishSync(msg, data): 同步发布消息: 指定消息名和数据

5. PubSub.unsubscribe(flag): 取消订阅: 根据标识取消某个或某些消息的订阅

9.2. 编码实现

1 /*

2 自定义消息订阅与发布

3 */

4 ​

5 const PubSub = {}
6 /*
7 {
8 add: {
9 token1: callback1,
10 token2: callback2
11 },
12 update: {
13 token3: callback3
14 }
15 }
16 */
17 let callbacksObj = {} // 保存所有回调的容器
18 let id = 0 // 用于生成token的标记
19 ​
20 // 1. 订阅消息
21 PubSub.subscribe = function (msgName, callback) {

22 ​

23 // 确定token 24 const token = 'token_' + ++id 25 // 取出当前消息对应的callbacks 26 const callbacks = callbacksObj[msgName] 27 if (!callbacks) { 28 callbacksObj[msgName] = { 29 [token]: callback 30 } 31 } else { 32 callbacks[token] = callback 33 } 34 // 返回token 35 return token 36 } 37 ​ 38 ​ 39 // 2. 发布异步的消息 40 PubSub.publish = function (msgName, data) { 41 // 取出当前消息对应的callbacks 42 let callbacks = callbacksObj[msgName] 43 // 如果有值 44 if (callbacks) { 45 // callbacks = Object.assign({}, callbacks) 46 // 启动定时器, 异步执行所有的回调函数 47 setTimeout(() => { 48 Object.values(callbacks).forEach(callback => { 49 callback(data) 50 }) 51 }, 0 ) 52 } 53 } 54 ​ 55 // 3. 发布同步的消息 56 PubSub.publishSync = function (msgName, data) { 57 // 取出当前消息对应的callbacks 58 const callbacks = callbacksObj[msgName] 59 // 如果有值 60 if (callbacks) { 61 // 立即同步执行所有的回调函数 62 Object.values(callbacks).forEach(callback => { 63 callback(data) 64 }) 65 } 66 } 67 ​ 68 /* 69 4. 取消消息订阅 70 1 ). 没有传值, flag为undefined 71 2 ). 传入token字符串 72 3 ). msgName字符串 73 */ 74 PubSub.unsubscribe = function (flag) { 75 // 如果flag没有指定或者为null, 取消所有

76 if (flag === undefined) {
77 callbacksObj = {}
78 } else if (typeof flag === 'string') {
79 if (flag.indexOf('token_') === 0 ) { // flag是token
80 // 找到flag对应的callbacks
81 const callbacks = Object.values(callbacksObj).find(callbacks => callbacks.hasOwnProperty(flag
82 // 如果存在, 删除对应的属性
83 if (callbacks) {
84 delete callbacks[flag]
85 }
86 } else { // flag是msgName
87 delete callbacksObj[flag]
88 }
89 ​
90 } else {
91 throw new Error('如果传入参数, 必须是字符串类型')
92 }
93 }
94 ​
95 export default PubSub
96 ​

二、打包自定义工具库

1. 创建工具包项目

1.1. 安装 Node

检查是否已经安装node(node中自带npm)

如果node与npm命令不识别,说明你还没有安装node

安装node, 这里我就不带大家一步一步安装了(比较简单)

方案一: 下载尚硅谷node的视频教程来学习安装

方案二: 百度查找node的安装教程

1.2. 创建项目

1 # 创建一个空的项目文件夹: myTools
2 # 在文件夹下执行命令
3 npm init -y

1.3.下载依赖包

1 npm i webpack webpack-cli

1.4.配置webpack.config.jswebpack

1 const path = require('path');
2 module.exports = {
3 // 模式
4 mode: 'development', // 也可以使用 production
5 // 入口
6 entry: './src/index.js',
7 // 出口
8 output: {
9 // 打包文件夹
10 path: path.resolve(__dirname, 'dist'),
11 // 打包文件
12 filename: 'myTools-utils.js',
13 // 向外暴露的对象的名称
14 library: 'myTools',
15 // 打包生成库可以通过esm/commonjs/reqirejs的语法引入
16 libraryTarget: 'umd',
17 }
18 }

1.5.在入口src/index.jsJS中暴露功能

1 export function test() {
2 document.write('测试自定义包')
3 console.log('test()')
4 }

1.6.配置打包命令package.json

1 "scripts": {
2 "build:watch": "webpack --watch"
3 },

1.7.对项目进行打包

1 npm run build:watch

1.8.测试使用自定义包

test/first.html
1 <body>
2 <script src="../dist/myTools-utils.js"></script>
3 <script>
4 aUtils.test()
5 </script>
6 </body>

2. 发布到 npm 中央仓库

2.1. 完善 package.json

注意:

name: 必须是唯一的名称(在npm在线中央仓库中没有同名的)

main: 必须指定为打包生成的js文件

keywords: 指定一些方便别的程序员搜索到当前库的关键字

1 {

2 "name": "muxing-my-tools",
3 "version": "1.0.2",
4 "description": "",
5 "main": "./dist/myTools-utils.js",
6 "directories": {
7 "test": "test"
8 },
9 "scripts": {
10 "test": "echo \"Error: no test specified\" && exit 1",
11 "build:watch": "webpack --watch"
12 },
13 "keywords": [
14 "utils",
15 "array",
16 "object",
17 "function",
18 "string",
19 "axios",
20 "event-bus",
21 "pub-sub"
22 ],
23 "author": "muxing",
24 "license": "ISC",
25 "dependencies": {
26 "webpack": "^5.28.0",
27 "webpack-cli": "^4.5.0"
28 }
29 }

2.2.npm 配置

npm配置的中央仓库不能是淘宝镜像

发布前必须执行: npm config set registry https://registry.npmjs.org/

不用发布时: npm config set registry http://registry.npm.taobao.org/

查看配置: npm config list

2.3. 注册 npm 中央仓库账号

注册地址: https://www.npmjs.com/

关键信息: 用户名/密码/邮箱(需要验证)

2.4. 添加用户

执行: npm login

登陆npm仓库

依次指定用户名/密码/邮箱

2.5. 发布仓库

执行: npm publish

2.6. 更新代码后再发布

修改项目库的版本号: package.json 中的version 从1.0.0 改为1.0.1, 注意一定要变大

修改代码后重新打包: npm run build

执行发布: npm publish

2.7. 强制删除已发布的库

执行: npm unpublish --force

注意: 必须在 72 小时内, 否则不能再删除

3. 使用自定义工具包

3.1. 下载工具包

1 # 名称是你前面指定的库的名称

2 npm install muxing-my-tools
3 ​
4 # 更新包
5 npm upgrade muxing-my-tools

3.2. 网⻚中引入并使用

1 <script src="../../dist/myTools-utils.js"></script>
2 <script>
3 myTools.test()
4 </script>

3.3. 模块化引入并使用

1 // 使用ESM引入

2 import {test} from 'muxing-my-tools'
3 test()
4 ​
5 // 使用commonjs引入
6 const {test} = require('muxing-my-tools')
7 test()

文档:自定义工具函数库.note 参考: https://zxfjd3g.github.io/atguigu_utils-docs/ 链接:http://note.youdao.com/noteshare?id=e9c8c96e7e0de1ae1d094fbdc3ab687b&sub=C9959E9683BA4C508716EFCF91E713D7

Package Sidebar

Install

npm i muxing-my-tools

Weekly Downloads

0

Version

1.0.9

License

ISC

Unpacked Size

142 kB

Total Files

56

Last publish

Collaborators

  • star1105