muxing-my-tools
v1.0.9
Published
自定义工具包
Downloads
5
Maintainers
Readme
自定义工具函数库
一、各种自定义
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