这里写目录标题
- 一、自定义封装各种工具类函数
- 1. 函数相关
- 1.1 call、apply、bind
- 1.1.1 call、apply、bind区别
- 语法延伸: globalThis
- 1.1.2 call函数封装实现
- 1.1.3 apply函数封装实现
- 1.1.4 bind函数封装实现
- 1.2 函数节流 & 函数防抖
- 1.2.1 手写函数节流
- 1.2.1 手写函数防抖
- 2. 数组相关
- 2.1 函数常用方法封装(实现方式不要求手写,不常用)
- 2.1.1 数组去重
- 2.1.2 数组扁平化
- 3. 对象相关
- 3.1 对象深浅拷贝
- 3.1.1 浅拷贝
- 3.1.2 深拷贝
- 4. 字符串相关
- 4.1.1 字符串倒叙
- 4.1.2 字符串是否是回文
- 5. npm 包发布
- 5.1.1 发布命令
- 5.1.2 webpack配置
- 5.1.3 npm账号登录与发布
一、自定义封装各种工具类函数
1. 函数相关
1.1 call、apply、bind
1.1.1 call、apply、bind区别
相同: 都是改变this指向,可以将某个函数的this指向修改为传入这三个方法中的第一个参数
不同: call、apply是立即执行,bind是返回一个函数
call(thisArg, param1, param2, …) 参数可以传递多个
apply(thisArg, [param1,param2,…]) 参数只能是一个,并且只能是数组
bind(thisArg, param1, param2, …) 参数可以传递多个,但是须有接收变量
语法延伸: globalThis
每个环境都有其自己的对象模型,并提供了不同的语法来访问全局对象。例如,在 Web 浏览器中,可以通过window,self或frames访问全局对象。但是,在 Node.js 中,这些属性不存在,而必须使用global。在Web Worker中,只有self可用。
globalThis旨在通过定义一个标准的全局属性来整合日益分散的访问全局对象的方法。该提案目前处于第四阶段,这意味着它已经准备好被纳入ES2020标准。所有流行的浏览器,包括Chrome 71+、Firefox 65+和Safari 12.1+,都已经支持这项功能。你也可以在Node.js 12+中使用它。
随着globalThis属性的引入,访问全局对象将变得更加简单,并且不再需要检测代码运行的环境。
1.1.2 call函数封装实现
call函数的自有实现方式:(call立即执行,并改变了this的指向)
function add(a, b) {
console.log(this, a, b);
return a + b + this.c;
};
const obj = {
c: 2,
};
add(); // window undefined undefined
add.call(obj, 3, 0); // {c: 2} 3 0
call函数的封装实现方式:
function call (Fn, obj, ...args) {
// 如果obj是undefined/null, this指定为window
if (obj === undefined || obj === null) {
// 全局对象,js中全局对象是window,但是node中的全局对象是global,这里使用globalThis(es11中新特性)来表示
obj = globalThis;
}
obj.temp= Fn;
let result = obj.temp(...args);
delete obj.temp;
return result;
}
1.1.3 apply函数封装实现
call函数的自有实现方式:(call立即执行,并改变了this的指向,与call却别为传参的形式不同)
function add(a, b) {
console.log(this, a, b);
return a + b + this.c;
};
const obj = {
c: 2,
};
add.apply(obj, [3, 0]); // {c: 2} 3 0
apply函数的封装实现方式:
function apply(fn, obj, args) {
if (obj===undefined || obj===null) {
obj = globalThis
}
obj.tempFn = fn
const result = obj.tempFn(...args)
delete obj.tempFn
return result
}
1.1.4 bind函数封装实现
函数的自有实现方式:(传参与call, 但返回参数需要变量接收)
function add(a, b) {
console.log(this, a, b);
return a + b + this.c;
};
const obj = {
c: 2,
};
// 注意这里需要执行函数,bind不是会立即执行的
const result = add.bind(obj, 100, 99)();// {c: 2} 100 99 201
// 这两种书写方式都可以
const result = add.bind(obj)(100, 99);// {c: 2} 100 99 201
bind函数的封装实现方式:
function bind(fn, obj, ...args) {
return (... args2) => {
// 通过call调用原函数, 并指定this为obj, 实参为args与args2
return call(fn, obj, ...args, ...args2)
}
}
// 这两种书写方式都可以
const result = bind(add, obj)(100, 99);
const result = bind(add, obj, 100, 99)();
console.log(result)// {c: 2} 100 99 201
1.2 函数节流 & 函数防抖
1.2.1 手写函数节流
两种写法都可以,但是上面这种会更好一点,因为定时器并非是精准到约定的时间就执行,有可能因为之前的代码执行慢而没有按照约定的时间执行的情况。并且定时器需要清除,防止内存泄漏
节流函数的亮点,在于this指向问题
function throttle(callback, wait) {
let start = 0;
// 返回一个事件监听函数(也就是节流函数)
return function (event) {
// console.log('throttle event')
// 只有当距离上次处理的时间间隔超过了wait时, 才执行处理事件的函数
const current = Date.now();
if ( current - start > wait) {
console.log(this)
// 如果满足条件则执行callback回调
callback.call(this, event); // 需要指定this和参数
start = current
}
}
}
function throttle(fn, delay = 100) {
let timer = null
return function(arguments) {
if(timer) {
retnrn
}
timer = setTimeOut(() => {
fn.apply(this, arguments)
timer = null;
}, delay)
}
}
1.2.1 手写函数防抖
如果需要在第一次立即执行,可以在函数中添加参数count,count === 0时立即执行。
function debounce (callback, wait) {
// 用来保存定时器任务的标识id
let timeoutId = -1;
// 返回一个事件监听函数(也就是防抖函数)
return function (event) {
// console.log('debounce event')
// 清除未执行的定时器任务
if (timeoutId!==-1) {
clearTimeout(timeoutId)
}
// 启动延迟 await 时间后执行的定时器任务
timeoutId = setTimeout(() => {
// 调用 callback 处理事件
callback.call(this, event);
// 处理完后重置标识
timeoutId = -1
}, wait)
}
}
2. 数组相关
2.1 函数常用方法封装(实现方式不要求手写,不常用)
map(): 返回一个由回调函数的返回值组成的新数组
reduce(): 从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值
filter(): 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回
find(): 找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回 undefined。
findIndex(): 找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1。
every(): 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。
some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false
/*
实现map()
*/
export function map (array, callback) {
const arr = []
for (let index = 0; index < array.length; index++) {
// 将callback的执行结果添加到结果数组中
arr.push(callback(array[index], index))
}
return arr
}
/*
实现reduce()
*/
export function reduce (array, callback, initValue) {
let result = initValue
for (let index = 0; index < array.length; index++) {
// 调用回调函数将返回的结果赋值给result
result = callback(result, array[index], index)
}
return result
}
/*
实现filter()
*/
export function filter(array, callback) {
const arr = []
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) {
arr.push(array[index])
}
}
return arr
}
/*
实现find()
*/
export function find (array, callback) {
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) {
return array[index]
}
}
return undefined
}
/*
实现findIndex()
*/
export function findIndex (array, callback) {
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) {
return index
}
}
return -1
}
/*
实现every()
*/
export function every (array, callback) {
for (let index = 0; index < array.length; index++) {
if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回false
return false
}
}
return true
}
/*
实现some()
*/
export function some (array, callback) {
for (let index = 0; index < array.length; index++) {
if (callback(array[index], index)) { // 只有一个结果为true, 直接返回true
return true
}
}
return false
}
2.1.1 数组去重
/*
方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些
*/
export function unique1 (array) {
const arr = []
array.forEach(item => {
if (arr.indexOf(item)===-1) {
arr.push(item)
}
})
return arr
}
/*
方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些
将数组中的数值作为下标存储在obj当中,这样可以少一层遍历
*/
export function unique2 (array) {
const arr = []
const obj = {}
array.forEach(item => {
if (!obj.hasOwnProperty(item)) {
obj[item] = true
arr.push(item)
}
})
return arr
}
/*
方法3: 利用ES6语法
1). from + Set
2). ... + Set
说明: 编码简洁
*/
export function unique3 (array) {
// return Array.from(new Set(array))
return [...new Set(array)]
}
2.1.2 数组扁平化
功能效果: 如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
reduce: array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
参数 | 描述 |
total | 必需。初始值, 或者计算结束后的返回值。 |
currentValue | 必需。当前元素 |
currentIndex | 可选。当前元素的索引 |
arr | 可选。当前元素所属的数组对象。 |
initialValue | 可选。传递给函数的初始值 |
// 方法一:递归 + reduce() + concat()
function flatten1 (array) {
return array.reduce((pre, item) => {
if (Array.isArray(item) && item.some(cItem => Array.isArray(cItem))) {
return pre.concat(flatten1(item))
} else {
return pre.concat(item)
}
}, [])
}
// 方法二:... + some() + concat()
function flatten2 (array) {
let arr = [].concat(...array)
// 条件为true循环
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
// 上面的方法也可以写成:
function flatten2 (array) {
let arr = [].concat(...array)
for (;arr.some(item => Array.isArray(item));) {
arr = [].concat(...arr)
}
return arr
}
reduce延伸: 初始值的设置不同,会导致返回的结果不同
const arr1 = [1, 2, 3, 4, 5];
const res = arr1.reduce((pre, item) => {
console.log(typeof item, typeof pre)
return pre + item
}, '')
const res1 = arr1.reduce((pre, item) => {
console.log(typeof item, typeof pre)
return pre + item
}, 0)
console.log(res, res1) // 12345 15
3. 对象相关
3.1 对象深浅拷贝
3.1.1 浅拷贝
// 方法一: 利用ES6语法
function clone1(target) {
// 如果是对象(不是函数, 也就是可能是object对象或者数组), typeof [] {} 都为object
if (target!=null && typeof target==='object') {
if (target instanceof Array) {
// return target.slice()
// return target.filter(() => true)
// return target.map(item => item)
return [...target]
} else {
// return Object.assign({}, target)
return {...target}
}
}
// 基本类型或者函数, 直接返回
return target
}
注意:cloneTarget[key] = target[key]这里是把引用数据类型直接赋值,他们地址指向依旧是相同的,所以这里依旧是浅拷贝
// 方法二: 利用ES5语法: for...in
// 可以与深克隆中的第二种方法进行对比
function clone2(target) {
if (target!=null && typeof target==='object') {
const cloneTarget = Array.isArray(target) ? [] : {}
for (let key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = target[key]
}
}
return cloneTarget
} else {
return target
}
}
3.1.2 深拷贝
1). 大众乞丐版
问题1: 函数属性会丢失
问题2: 循环引用会出错
优点:方便,但只能进行一些简单的拷贝
function deepClone1(target) {
return JSON.parse(JSON.stringify(target))
}
let target = {
a: 1,
b: ['e', 'f'],
c: {h: 3},
d: function () {
}
}
const result = deepClone1(target)
target.c.h = 100
console.log(result) // {a: 1, b: ['e', 'f'], c: {h: 3}} 注意这里的d属性丢失了
target.c.f = target.b
target.b.push(target.c)
const result = deepClone1(target) // 循环引用时,会报错:Uncaught TypeError: Converting circular structure to JSON
2). 面试基础版本 递归拷贝
解决问题1: 函数属性还没丢失
function deepClone2 (target) {
if (target!==null && typeof target==='object') {
const cloneTarget = target instanceof Array ? [] : {}
for (const key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = deepClone2(target[key])
}
}
return cloneTarget
}
return target
}
3). 面试加强版本
解决问题2: 循环引用正常
function deepClone3 (target, map=new Map()) {
if (target!==null && typeof target==='object') {
// 从缓存容器中读取克隆对象
let cloneTarget = map.get(target)
// 如果存在, 返回前面缓存的克隆对象
if (cloneTarget) {
return cloneTarget
}
// 创建克隆对象(可能是{}或者[])
cloneTarget = target instanceof Array ? [] : {}
// 缓存到map中
map.set(target, cloneTarget)
for (const key in target) {
if (target.hasOwnProperty(key)) {
// 递归调用, 深度克隆对象, 且传入缓存容器map
cloneTarget[key] = deepClone3(target[key], map)
}
}
return cloneTarget
}
return target
}
4). 面试加强版本2(优化遍历性能)
数组: while | for | forEach() 优于 for-in | keys()&forEach()
对象: for-in 与 keys()&forEach() 差不多
export function deepClone4 (target, map=new Map()) {
if (target!==null && typeof target==='object') {
// 从缓存容器中读取克隆对象
let cloneTarget = map.get(target)
// 如果存在, 返回前面缓存的克隆对象
if (cloneTarget) {
return cloneTarget
}
// 创建克隆对象(可能是{}或者[])
if (target instanceof Array) {
cloneTarget = []
// 缓存到map中
map.set(target, cloneTarget)
target.forEach((item, index) => {
cloneTarget[index] = deepClone4(item, map)
})
} else {
cloneTarget = {}
// 缓存到map中
map.set(target, cloneTarget)
Object.keys(target).forEach(key => {
cloneTarget[key] = deepClone4(target[key], map)
})
}
return cloneTarget
}
return target
}
4. 字符串相关
4.1.1 字符串倒叙
function reverseString(str) {
// return str.split('').reverse().join('')
// return [...str].reverse().join('')
// 1. Array.from把字符串变成数组 2.reverse将数组中的元素进行倒叙排列["p", "o", "i", "u", "r", "e", "w", "q"] 3. join将数组重新拼接组合成字符串
return Array.from(str).reverse().join('')
}
const str = 'qweruiop';
console.log(reverseString(str))
4.1.2 字符串是否是回文
palindrome(str) 如果给定的字符串是回文,则返回 true ;否则返回 false
利用回文对称结构的原理来判断
function palindrome(str) {
return str === reverseString(str)
}
5. npm 包发布
5.1.1 发布命令
创建项目
# 创建一个空的项目文件夹: atguigu-utils
# 在文件夹下执行命令
npm init -y
下载安装依赖
npm i webpack webpack-cli
在入口JS中暴露功能,src/index.js
export function test() {
document.write('测试自定义包')
console.log('test()')
}
配置打包命令,package.json
{
"name": "utils",
"version": "1.0.0",
"author": "mia",
"description": "自定义工具函数库",
"keywords": [
"atguigu",
"utils",
"array",
"object",
"function",
"string",
"axios",
"event-bus",
"pub-sub",
"promise"
],
"main": "dist/utils.js",
"license": "MIT",
"scripts": {
"build:watch": "webpack --watch",
"build": "webpack"
},
"devDependencies": {
"webpack": "^5.10.0",
"webpack-cli": "^4.2.0"
}
}
对项目进行打包
npm run build:watch
测试使用自定义包,test/first.html
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
aUtils.test()
</script>
</body>
5.1.2 webpack配置
webpack.config.js
const path = require('path')
module.exports = {
// 模式
mode: 'development', // 也可以使用 production
// 入口
entry: './src/index.js',
// 出口
output: {
// 打包文件夹
path: path.resolve(__dirname, 'dist'),
// 打包文件
filename: 'utils.js',
// 向外暴露的对象的名称
library: 'mUtils',
// 打包生成库可以通过esm/commonjs/reqirejs的语法引入
libraryTarget: 'umd',
},
}
5.1.3 npm账号登录与发布
登录
npm login
发布
npm publish
强制删除已发布的库(必须在72小时内, 否则不能再删除)
npm unpublish --force