目录
一、函数相关
1、call()& apply()& bind()
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方法)
实现说明
- 区别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、自定义函数对象的call方法
call.js
/*
自定义函数对象的call方法
Fn:要执行的函数
obj:函数运行时this指向的对象
args:函数运行时的参数
*/
function call(Fn, obj, ...args) {
// 如果obj是undefined/null, this指定为window
if (obj === undefined || obj === null) {
// return fn(...args)
obj = window
}
// 给obj添加一个临时方法, 方法指向的函数就是fn
obj.tempFn = Fn;//tempFn内部在执行时this是指向obj的,变相实现了this指向obj这样一个效果
// 通过obj来调用这个方法 ==> 也就会执行fn函数 ==> 此时fn中的this肯定为obj
const result = obj.tempFn(...args);
// 删除obj上的临时方法
delete obj.tempFn;
// 返回fn执行的结果
return result;
}
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/style.css" rel="stylesheet">
<script src="./call.js"></script>
</head>
<body>
<script>
// 声明一个函数
function add(a, b) {
return a + b + this.c;
}
// 声明一个对象
let obj = {
c: 521
}
//添加全局属性
window.c = 1314;
// 执行call函数
console.log(call(add, obj, 10, 20));// 151
console.log(call(add, null, 30, 40));// 1384
</script>
</body>
</html>
1.2、自定义函数对象的apply方法
/*
自定义函数对象的Apply方法
Fn:要执行的函数
obj:函数运行时this指向的对象
args:函数运行时的参数
*/
// 改变this的指向,执行函数,返回结果
function apply(Fn, obj, ...args) {
if (obj === undefined || obj === null) {
obj = window;
}
//给obj添加一个临时的方法,方法指向的函数就是Fn
obj.tempFn = Fn;
// 通过obj来调用这个方法 ==> 也就会执行fn函数 ==> 此时fn中的this肯定为obj
const result = obj.tempFn(...args)
// 删除obj上的临时方法
delete obj.tempFn
// 返回fn执行的结果
return resultv
}
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/style.css" rel="stylesheet">
<script src="./call.js"></script>
</head>
<body>
<script>
// 声明一个函数
function add(a, b) {
return a + b + this.c;
}
// 声明一个对象
let obj = {
c: 521
}
//添加全局属性
window.c = 1314;
// 执行call函数
console.log(apply(add, obj, 10, 20));// 151
console.log(apply(add, null, 30, 40));// 1384
</script>
</body>
</html>
1.3、自定义函数对象的bind方法
import {call} from './call'
/*
自定义函数对象的bind方法
*/
export function bind(fn, obj, ...args) {
console.log('bind()')
// 返回一个新函数
return (... args2) => {
// 通过call调用原函数, 并指定this为obj, 实参为args与args2
return call(fn, obj, ...args, ...args2)
}
}
2、函数节流与函数防抖
2.1. 相关理解
事件频繁触发可能造成的问题?
- 一些浏览器事件:window.onresize、window.mousemove等,触发的频率非常高,会造成界面卡顿
- 如果向后台发送请求,频繁触发,对服务器造成不必要的压力
如何限制事件处理函数频繁调用:
- 函数节流
- 函数防抖
函数节流(throttle)
- 理解:
- 在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次
- 适合多次事件按时间做平均分配触发
- 场景:
- 窗口调整(resize)
- 页面滚动(scroll)
- DOM 元素的拖拽功能实现(mousemove)
- 抢购疯狂点击(click)
函数防抖(debounce)
- 理解:
- 在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。
- 适合多次事件一次响应的情况
- 场景:
- 输入框实时搜索联想(keyup/input)
-
区别函数节流与防抖
2.2.API说明
- throttle() 节流
- 语法: throttle(callback, wait)
- 功能: 创建一个节流函数,在 wait 毫秒内最多执行
callback
一次- debounce() 防抖
- 语法: debounce(callback, wait)
- 功能: 创建一个防抖动函数,该函数会从上一次被调用后,延迟
wait
毫秒后调用callback
2.3.编码实现
throttle.js
: 函数节流
/*
实现函数节流
- 语法: throttle(callback, wait)
- 功能: 创建一个节流函数,在 wait 毫秒内最多执行 `callback` 一次
*/
export function throttle(callback, wait) {
let start = 0
// 返回一个事件监听函数(也就是节流函数)
return function (event) {
console.log('throttle event')
// 只有当距离上次处理的时间间隔超过了wait时, 才执行处理事件的函数
const current = Date.now()
if ( current - start > wait) {
callback.call(this, event) // 需要指定this和参数
start = current
}
}
}
debounce.js
: 函数防抖
/*
实现函数防抖
- 语法: debounce(callback, wait)
- 功能: 创建一个防抖动函数,该函数会从上一次被调用后,延迟 `wait` 毫秒后调用 `callback`
*/
export 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)
}
}
应用
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="css/style.css" rel="stylesheet">
<script src="./utils.js"></script>
</head>
<body>
<button id="handle">正常处理</button>
<button id="throttle">测试函数节流</button>
<button id="debounce">测试函数防抖</button>
<script>
/* 处理点击事件的回调函数 */
function handleClick(event) { // 处理事件的回调
console.log('处理点击事件', this, event)
}
document.getElementById('handle').onclick = handleClick
document.getElementById('throttle').onclick = throttle(handleClick, 2000)
document.getElementById('debounce').onclick = debounce(handleClick, 2000)
</script>
</body>
</html>
二、数组相关
1、API列表
- map()
- reduce()
- filter()
- find()
- findIndex()
- every()
- some()
- unique1() / unique2() / unique3()
- concat()
- slice()
- flatten()
- chunk() / chunk2()
- difference()
- pull()
- pullAll()
- drop()
- dropRight()
2、数组声明式系列方法
2.1.使用数组声明式系列方法
- map(): 返回一个由回调函数的返回值组成的新数组
- reduce(): 从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值
- filter(): 将所有在过滤函数中返回
true
的数组元素放进一个新数组中并返回- find(): 找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回
undefined
。- findIndex(): 找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回
-1
。- every(): 如果数组中的每个元素都满足测试函数,则返回
true
,否则返回false。
- some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。
2.2.编码实现
declares.js
: 实现数组声明式处理系列工具函数
/*
实现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.3.测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数组声明式系列方法</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
/*
需求:
1. 产生一个每个元素都比原来大10的新数组
2. 得到所有奇数的和
3. 得到值大于8且下标是偶数位的元素组成的数组
4. 找出一个值大于8且下标是偶数位的元素
5. 找出一个值大于8且下标是偶数位的元素的下标
6. 判断下标为偶数的元素是否都为奇数
7. 判断是否有下标为偶数的元素值为奇数
*/
const arr = [1, 3, 6, 9, 15, 19, 16]
/* 使用数组内置方法 */
// console.log(arr.map((item, index) => item + 10))
// console.log(arr.reduce((preTotal, item, index) => {
// return preTotal + (item%2===1 ? item : 0)
// }, 0))
// console.log(arr.filter((item, index) => item>8 && index%2===0))
// console.log(arr.find((item, index) => item>8 && index%2===0))
// console.log(arr.findIndex((item, index) => item>8 && index%2===0))
// console.log(arr.every((item, index) => index%2===1 || item%2===1))
// console.log(arr.some((item, index) => index%2===0 && item%2===1))
/* 使用自定义工具函数 */
console.log(aUtils.map(arr, (item, index) => item + 10))
console.log(aUtils.reduce(arr, (preTotal, item, index) => {
return preTotal + (item%2===1 ? item : 0)
}, 0))
console.log(aUtils.filter(arr, (item, index) => item>8 && index%2===0))
console.log(aUtils.find(arr, (item, index) => item>8 && index%2===0))
console.log(aUtils.findIndex(arr, (item, index) => item>8 && index%2===0))
console.log(aUtils.every(arr, (item, index) => index%2===1 || item%2===1))
console.log(aUtils.some(arr, (item, index) => index%2===0 && item%2===1))
</script>
</body>
</html>
3、数组去重
3.1.API 说明
- 根据当前数组产生一个去除重复元素后的新数组
- 如: [2, 3, 2, 7, 6, 7] ==> [2, 3, 7, 6]
3.2. 实现
-
方法1: 利用forEach()和indexOf()
- 说明: 本质是双重遍历, 效率差些
-
方法2: 利用forEach() + 对象容器
- 说明: 只需一重遍历, 效率高些
-
方法3: 利用ES6语法: from + Set 或者 ... + Set
- 说明: 编码简洁
3.3. 编码实现
unique.js
/*
方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些
*/
export function unique1 (array) {
const arr = []
array.forEach(item => {
if (arr.indexOf(item)===-1) {
arr.push(item)
}
})
return arr
}
/*
方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些
*/
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)]
}
4、数组合并与切片
4.1. API 说明
concat(): 合并
- 语法: var new_array = concat(array, value1[, value2[, ...[, valueN]]])
- 功能: 将n个数组或值与当前数组合并生成一个新数组, 原始数组不会被改变
slice(): 切片
- 语法: var new_array = slice(array, [begin[, end]])
- 功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变
4.2.编码实现
concat.js
: 自定义数组合并
/*
语法: var new_array = concat(old_array, value1[, value2[, ...[, valueN]]])
功能: 将n个数组或值与当前数组合并生成一个新数组
*/
export function concat (array, ...values) {
const arr = [...array]
values.forEach(value => {
if (Array.isArray(value)) {
arr.push(...value)
} else {
arr.push(value)
}
})
return arr
}
src/array/slice.js
: 自定义数组切片/* 语法: var new_array = slice(oldArr, [begin[, end]]) 功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变 */ export function slice (array, begin, end) { // 如果当前数组是[], 直接返回[] if (array.length === 0) { return [] } // 如果begin超过最大下标, 直接返回[] begin = begin || 0 if (begin >= array.length) { return [] } // 如果end不大于begin, 直接返回[] end = end || array.length if (end > array.length) { end = array.length } if (end <= begin) { return [] } // 取出下标在[begin, end)区间的元素, 并保存到最终的数组中 const arr = [] for (let index = begin; index < end; index++) { arr.push(array[index]) } return arr }
4.3 测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>数组合并与切片</title> </head> <body> <script src="../dist/atguigu-utils.js"></script> <script> console.log(aUtils.concat([1, 2], [3, 4], 6)) // [1, 2, 3, 4, 6] console.log(aUtils.slice([1, 3, 5, 7, 9])) // [1, 3, 5, 7, 9] console.log(aUtils.slice([1, 3, 5, 7, 9], 1, 3)) // [3, 5] console.log(aUtils.slice([1, 3, 5, 7, 9], 1, 10)) // [3, 5, 7, 9] </script> </body> </html>
5、数组扁平化
5.1 API 说明
- 语法: flatten(array)
- 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中
-
src/array/flatten.js
-
方法一: 递归 + reduce() + concat()
-
方法二: ... + some() + concat()
- 如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
JavaScript中的数组
some()
方法用于检查数组中是否至少有一个元素满足指定条件。该方法会遍历数组,并对每个元素应用指定的测试函数。如果有任何一个元素使得测试函数返回true,则some()
方法返回true,否则返回false。
5.2 编码实现
/*
数组扁平化: 取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中
如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
*/
/*
方法一: 递归 + reduce() + concat()
*/
export 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()
*/
export function flatten2 (array) {
let arr = [].concat(...array)
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr)
}
return arr
}
5.3 测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数组扁平化</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
console.log(aUtils.flatten1([1, [3, [2, 4]]]))
console.log(aUtils.flatten2([1, [3, [2, 4]]]))
</script>
</body>
</html>
6.数组分块
6.1.API 说明
- 语法: chunk(array, size)
- 功能: 将数组拆分成多个 size 长度的区块,每个区块组成小数组,整体组成一个二维数组
- 如: [1, 3, 5, 6, 7, 8] 调用chunk(arr, 4) ==> [[1, 3, 5, 6], [7,8]]
6.2.编码实现
/*
将数组拆分成多个 size 长度的区块,每个区块组成小数组,整体组成一个二维数组
*/
export function chunk (array, size) {
if (array.length===0) {
return []
}
size = size || 1
const bigArr = []
let smallArr = []
array.forEach(item => {
if (smallArr.length===0) {
bigArr.push(smallArr)
}
smallArr.push(item)
if (smallArr.length===size) {
smallArr = []
}
})
return bigArr
}
6.3.测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数组分块</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
console.log(aUtils.chunk([1, 2, 3, 4, 5, 6, 7], 3)) // [[1,2,3], [4,5,6],[7]]
console.log(aUtils.chunk([1, 2, 3, 4, 5, 6, 7]))// [[1],[2],[3],[4],[5],[6],[7]]
console.log(aUtils.chunk([1, 2, 3, 4, 5, 6, 7], 8))// [[1, 2, 3, 4, 5, 6, 7]]
</script>
</body>
</html>
7.数组取差异
7.1.API 说明
- 语法: difference(arr1, arr2)
- 功能: 得到当前数组中所有不在arr中的元素组成的数组(不改变原数组)
- 例子: difference([1,3,5,7], [5, 8]) ==> [1, 3, 7]
7.2.编码实现
src/array/difference.js
/* difference(arr1, arr2): 得到arr1中所有不在arr2中的元素组成的数组(不改变原数组) 如: [1,3,5,7].difference([5, 8]) ==> [1, 3, 7] */ export function difference (arr1, arr2) { if (arr1.length===0) { return [] } else if (arr2.length===0) { return arr1.slice() } return arr1.filter(item => arr2.indexOf(item)===-1) }
7.3.测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>数组取差异</title> </head> <body> <script src="../dist/atguigu-utils.js"></script> <script> console.log(aUtils.difference([1,3,5,7], [5, 8])) </script> </body> </html>
8.删除数组中部分元素
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]
8.2. 编码实现
/*
1. pull(array, ...values):
删除数组中与value相同的元素, 返回所有删除元素的数组
说明: 数组发生了改变
如: pull([1,3,5,3,7], 2, 7, 3, 7) ===> 数组变为[1, 5], 返回值为[3,3,7]
2. pullAll(array, values):
功能与pull一致, 只是参数变为数组
如: pullAll([1,3,5,3,7], [2, 7, 3, 7]) ===> 数组变为[1, 5], 返回值为[3,3,7]
*/
export function pull (array, ...values) {
if (array.length===0 || values.length===0) {
return []
}
var result = []
for (let index = 0; index < array.length; index++) {
const item = array[index]
if (values.indexOf(item)!==-1) {
array.splice(index, 1) //删除指定元素,index开始的位置,1表示删除一个
result.push(item)
index--
}
}
return result
}
export function pullAll (array, values) {
if (!values || !Array.isArray(values)) {
return []
}
return pull(array, ...values)
}
8.3.测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>删除数组中部分元素</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
var arr = [1,3,5,3,7]
console.log(aUtils.pull(arr, 2, 7, 3, 7), arr)
var arr2 = [1,3,5,3,7]
console.log(aUtils.pullAll(arr2, [2, 7, 3, 7]), arr2)
</script>
</body>
</html>
9. 得到数组的部分元素
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]
9.2. 编码实现
/*
1. drop(array, count):
得到数组过滤掉左边count个后剩余元素组成的数组
说明: 不改变当前数组, count默认是1
如: drop([1,3,5,7], 2) ===> [5, 7]
2. dropRight(array, count):
得到数组过滤掉右边count个后剩余元素组成的数组
说明: 不改变数组, count默认是1
如: dropRight([1,3,5,7], 2) ===> [1, 3]
*/
export function drop (array, count=1) {
if (array.length === 0 || count >= array.length) {
return []
}
return array.filter((item, index) => index>=count)
}
export function dropRight (array, count=1) {
if (array.length === 0 || count >= array.length) {
return []
}
return array.filter((item, index) => index < array.length-count)
}
9.3.测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>得到数组的部分元素</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
console.log(aUtils.drop([1,3,5,7], 2))
console.log(aUtils.drop([1,3,5,7], 4))
console.log(aUtils.drop([1,3,5,7]))
console.log(aUtils.dropRight([1,3,5,7], 2))
console.log(aUtils.dropRight([1,3,5,7], 4))
console.log(aUtils.dropRight([1,3,5,7]))
</script>
</body>
</html>
三、对象相关
1.相关API
- newInstance()
- myInstanceOf()
- mergeObject()
- clone1() / clone2()
- deepClone1() / deepClone2() / deepClone3() / deepClone4()
2.自定义new
2.1.API 相关
- 语法: newInstance(Fn, ...args)
- 功能: 创建Fn构造函数的实例对象
2.2.编码实现
src/object/newInstance.js
export function newInstance (Fn, ...args) { // 创建一个空的object实例对象obj, 作为Fn的实例对象 const obj = {} // 将Fn的prototype属性值赋值给obj的__proto__属性值 obj.__proto__ = Fn.prototype // 调用Fn, 指定this为obj, 参数为args列表 const result = Fn.call(obj, ...args) // 如果Fn返回的是一个对象类型, 那返回的就不再是obj, 而是Fn返回的对象 // 否则返回obj return result instanceof Object ? result : obj }
2.3.测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>自定义new</title> </head> <body> <script src="../dist/atguigu-utils.js"></script> <script> function Person(name, age) { this.name = name this.age = age // return {} // return [] // return function (){} // return 1 // return undefined } const p = new Person('tom', 12) console.log(p) const p2 = aUtils.newInstance(Person, 'Jack', 13) console.log(p2, p2.constructor) </script> </body> </html>
3.自定义instanceof
3.1. API 相关
语法: myInstanceOf(obj, Type) 功能: 判断obj是否是Type类型的实例3.2.编码实现
src/object/myInstanceOf.js
- 实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回tru, 否则返回false
export function myInstanceOf(obj, Type) {
// 得到原型对象
let protoObj = obj.__proto__
// 只要原型对象存在
while(protoObj) {
// 如果原型对象是Type的原型对象, 返回true
if (protoObj === Type.prototype) {
return true
}
// 指定原型对象的原型对象
protoObj = protoObj.__proto__
}
return false
}
3.3. 测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义instanceof</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
function Person(name, age) {
this.name = name
this.age = age
}
const p = new Person('tom', 12)
console.log(aUtils.myInstanceOf(p, Object), p instanceof Object)
console.log(aUtils.myInstanceOf(p, Person), p instanceof Person)
console.log(aUtils.myInstanceOf(p, Function), p instanceof Function)
</script>
</body>
</html>
4.合并多个对象
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' }
4.2.编码实现
5.2.实现浅拷贝
src/object/mergeObject.js
export function mergeObject(...objs) { const result = {} // 遍历objs objs.forEach(obj => { Object.keys(obj).forEach(key => { // 如果result还没有key值属性 if (!result.hasOwnProperty(key)) { result[key] = obj[key] } else { // 否则 合并属性 result[key] = [].concat(result[key], obj[key]) } }) }) // 可以使用reduce来代替forEach手动添加 return result }
4.3.测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>合并多个对象</title> </head> <body> <script src="../dist/atguigu-utils.js"></script> <script> const object = { a: [{ x: 2 }, { y: 4 }], b: 1 } const other = { a: { z: 3}, b: [2, 3], c: 'foo' } console.log(aUtils.merge(object, other)) </script> </body> </html>
5. 对象/数组拷贝
5.1.区别浅拷贝与深拷贝
- 纯语言表达:
- 浅拷贝: 只是复制了对象属性或数组元素本身(只是引用地址值)
- 深拷贝: 不仅复制了对象属性或数组元素本身, 还复制了指向的对象(使用递归)
- 举例说明: 拷贝persons数组(多个人对象的数组)
- 浅拷贝: 只是拷贝了每个person对象的引用地址值, 每个person对象只有一份
- 深拷贝: 每个person对象也被复制了一份新的
5.2.实现浅拷贝
src/object/clone.js
/* 实现浅拷贝 方法一: 利用ES6语法 方法二: 利用ES5语法: for...in */ /* 方法一: 利用ES6语法*/ export function clone1(target) { // 如果是对象(不是函数, 也就是可能是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 } /* 方法二: 利用ES5语法: for...in */ export 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 } }
5.3.实现深拷贝
4.2. 编码实现
-
实现一: 大众乞丐版
- 问题1: 函数属性会丢失
- 问题2: 循环引用会出错
-
实现二: 面试基础版
- 解决问题1: 函数属性还没丢失
-
实现三: 面试加强版本
- 解决问题2: 循环引用正常
-
实现四: 面试加强版本2(优化遍历性能)
- 数组: while | for | forEach() 优于 for-in | keys()&forEach()
- 对象: for-in 与 keys()&forEach() 差不多
-
编码实现:
src/object/deepClone.js
/* 深度克隆 1). 大众乞丐版 问题1: 函数属性会丢失 问题2: 循环引用会出错 2). 面试基础版本 解决问题1: 函数属性还没丢失 3). 面试加强版本 解决问题2: 循环引用正常 4). 面试加强版本2(优化遍历性能) 数组: while | for | forEach() 优于 for-in | keys()&forEach() 对象: for-in 与 keys()&forEach() 差不多 */ /* 1). 大众乞丐版 问题1: 函数属性会丢失 问题2: 循环引用会出错 */ export function deepClone1(target) { return JSON.parse(JSON.stringify(target)) } /* 2). 面试基础版本 解决问题1: 函数属性还没丢失 */ export 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: 循环引用正常 */ export 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 }
5.4.测试
- 浅拷贝测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>浅克隆/浅复制/浅拷贝</title> </head> <body> <!-- 实现浅拷贝 方法一: 利用ES6语法 方法二: 利用ES5语法: for...in --> <script src="../dist/atguigu-utils.js"></script> <script> const obj1 = { x: 'abc', y: {m: 1} } // const obj2 = aUtils.clone1(obj1) const obj2 = aUtils.clone2(obj1) console.log(obj2, obj2===obj1, obj2.x===obj1.x, obj2.y===obj1.y) const arr1 = ['abc', {m: 1}] // const arr2 = aUtils.clone1(arr1) const arr2 = aUtils.clone2(arr1) console.log(arr2, arr2===arr1, arr2[0]===arr1[0], arr2[1]===arr1[1]) </script> </body> </html>
- 深拷贝测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>深度克隆/深复制/深拷贝</title> </head> <body> <script src="https://cdn.bootcss.com/lodash.js/4.17.15/lodash.min.js"></script> <script src="../dist/atguigu-utils.js"></script> <script> const obj1 = { a: 1, b: [ 'e', 'f', 'g'], c: { h: { i: 2 } }, d: function (){} } obj1.b.push(obj1.c) obj1.c.j = obj1.b // const obj2 = _.cloneDeep(obj1) // const obj2 = aUtils.deepClone1(obj1) // const obj2 = aUtils.deepClone2(obj1) // const obj2 = aUtils.deepClone3(obj1) const obj2 = aUtils.deepClone4(obj1) console.log(obj2, obj2.c === obj1.c, obj2.d===obj1.d) </script> </body> </html>
四、字符串相关
4.1. API相关
- 字符串倒序
- 语法: reverseString(str)
- 功能: 生成一个倒序的字符串
- 字符串是否是回文
- 语法: palindrome(str)
- 功能: 如果给定的字符串是回文,则返回 true ;否则返回 false
4.2 编码实现
src/string/index.js
- 截取字符串
- 语法: truncate(str, num)
- 功能: 如果字符串的长度超过了num, 截取前面num长度部分, 并以...结束
/*
1. 字符串倒序: reverseString(str) 生成一个倒序的字符串
*/
export function reverseString(str) {
// return str.split('').reverse().join('')
// return [...str].reverse().join('')
return Array.from(str).reverse().join('')
}
/*
2. 字符串是否是回文: palindrome(str) 如果给定的字符串是回文,则返回 true ;否则返回 false
*/
export function palindrome(str) {
return str === reverseString(str)
}
/*
3. 截取字符串: truncate(str, num) 如果字符串的长度超过了num, 截取前面num长度部分, 并以...结束
*/
export function truncate(str, num) {
return str.length > num ? str.slice(0, num) + '...' : str
}
4.3.测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>字符串处理的系列方法</title>
</head>
<body>
<script src="../dist/atguigu-utils.js"></script>
<script>
console.log(aUtils.reverseString('abcd')) // dcba
console.log(aUtils.palindrome('abcb'), aUtils.palindrome('abcba')) // false true
console.log(aUtils.truncate('boomerang', 7)) // boomera...
</script>
</body>
</html>
五、手写ajax请求函数
5.1.API 相关
- 语法:
- 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库功能类似
5.2. 实现整体流程
-
函数的参数为一个配置对象
{ url: '', // 请求地址 method: '', // 请求方式GET/POST/PUT/DELETE params: {}, // GET/DELETE请求的query参数 data: {}, // POST或DELETE请求的请求体参数 }
-
返回值: 函数的返回值为promise, 成功的结果为response, 失败的结果为error
-
能处理多种类型的请求: GET/POST/PUT/DELETE
-
响应json数据自动解析为js的对象/数组
5.3. 编码实现
src/axios/index.js
/* 发送任意类型请求的函数 */ function axios({ url, method='GET', params={}, data={} }) { // 返回一个promise对象 return new Promise((resolve, reject) => { // 处理method(转大写) method = method.toUpperCase() // 处理query参数(拼接到url上) id=1&xxx=abc /* { id: 1, xxx: 'abc'} */ let queryString = '' Object.keys(params).forEach(key => { queryString += `${key}=${params[key]}&` }) if (queryString) { // id=1&xxx=abc& // 去除最后的& queryString = queryString.substring(0, queryString.length-1) // 接到url url += '?' + queryString } // 1. 执行异步ajax请求 // 创建xhr对象 const request = new XMLHttpRequest() // 打开连接(初始化请求, 没有请求) request.open(method, url, true) // 发送请求 if (method==='GET') { request.send() } else if (method==='POST' || method==='PUT' || method==='DELETE'){ // 告诉服务器请求体的格式是json request.setRequestHeader('Content-Type', 'application/json;charset=utf-8') // 发送json格式请求体参数 request.send(JSON.stringify(data)) } // 绑定状态改变的监听 request.onreadystatechange = function () { // 如果请求没有完成, 直接结束 if (request.readyState!==4) { return } // 如果响应状态码在[200, 300)之间代表成功, 否则失败 const {status, statusText} = request // 2.1. 如果请求成功了, 调用resolve() if (status>=200 && status<=299) { // 准备结果数据对象response const response = { data: JSON.parse(request.response), status, statusText } resolve(response) } else { // 2.2. 如果请求失败了, 调用reject() reject(new Error('request error status is ' + status)) } } }) } /* 发送特定请求的静态方法 */ axios.get = function (url, options) { return axios(Object.assign(options, {url, method: 'GET'})) } axios.delete = function (url, options) { return axios(Object.assign(options, {url, method: 'DELETE'})) } axios.post = function (url, data, options) { return axios(Object.assign(options, {url, data, method: 'POST'})) } axios.put = function (url, data, options) { return axios(Object.assign(options, {url, data, method: 'PUT'})) } export default axios
5.4.测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>手写ajax请求函数</title> </head> <body> <button onclick="testGet()">发送GET请求</button><br> <button onclick="testPost()">发送POST请求</button><br> <button onclick="testPut()">发送PUT请求</button><br> <button onclick="testDelete()">发送Delete请求</button><br> <script src="../dist/atguigu-utils.js"></script> <script> const { axios } = aUtils /* 1. GET请求: 从服务器端获取数据*/ function testGet() { axios({ // url: 'http://localhost:3000/posts', url: 'http://localhost:3000/posts2', method: 'GET', params: { id: 1, xxx: 'abc' } }) // axios.get('http://localhost:3000/posts', {params: {id: 1}}) .then( response => { console.log(response) }, error => { alert(error.message) } ) } /* 2. POST请求: 服务器端保存数据*/ function testPost() { axios({ url: 'http://localhost:3000/posts', method: 'POST', data: { "title": "json-server---", "author": "typicode---" } }) // axios.post('http://localhost:3000/posts', {title: 'aaa', author: 'bbb'}) .then( response => { console.log(response) }, error => { alert(error.message) } ) } /* 3. PUT请求: 服务器端更新数据*/ function testPut() { axios({ url: 'http://localhost:3000/posts/1', method: 'put', data: { "title": "json-server+++", "author": "typicode+++" } }) // axios.put('http://localhost:3000/posts/1', {title: 'aaa222', author: 'bbb222'}) .then( response => { console.log(response) }, error => { alert(error.message) } ) } /* 2. DELETE请求: 服务器端删除数据*/ function testDelete() { axios({ url: 'http://localhost:3000/posts/2', method: 'delete' }) // axios.delete('http://localhost:3000/posts/2') .then( response => { console.log(response) }, error => { alert(error.message) } ) } </script> </body> </html>