call()
函数的作用与函数原型对象上的call
方法是一样的:改变函数运行时的this
值,并得到函数运行的执行结果
jsfunction call(Fn, obj, ...args) {
// 判断obj
if(obj === undefined || obj === null) {
obj = globalThis // globalThis: ES11新特性,用来指向全局对象
}
// 为obj添加临时的方法
obj.temp = Fn
// 调用temp方法
let result = obj.temp(...args)
// 删除temp方法
delete obj.temp
// 返回执行结果
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> <script src="./1_call.js"></script> </head> <body> <script> // 声明一个函数 function add(a, b) { console.log('this', this) return a + b + this.c } // 声明一个对象 let obj = { c: 123 } // 添加全局属性 window.c = 456 // 执行call函数 console.log(call(add, obj, 10, 20)) console.log(call(add, null, 20, 50)) console.log(obj) </script> </body> </html>
apply()
函数作用和call()
函数相同,区别在于,apply()
函数的参数是一个数组,而call()
函数的参数是单独存在的
jsfunction apply(Fn, obj, args) {
// 判断
if (obj === undefined || obj === null) {
obj = globalThis
}
// 为obj添加临时方法
obj.temp = Fn
// 执行方法
let result = obj.temp(...args)
// 删除临时属性
delete obj.temp
// 返回执行结果
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> <script src="./2_apply.js"></script> </head> <body> <script> // 声明一个函数 function add(a, b) { console.log('this', this) return a + b + this.c } // 声明一个对象 let obj = { c: 123 } // 添加全局属性 window.c = 456 // 执行apply函数 console.log(apply(add, obj, [10, 20])) console.log(apply(add, null, [20, 50])) console.log(obj) </script> </body> </html>
bind在call的基础上会返回一个函数
jsfunction bind(Fn, obj, ...args) {
// 返回一个新的函数
return function(...args2) {
// 执行call函数
return call(Fn, obj, ...args, ...args2)
}
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Document</title> <script src="./1_call.js"></script> <script src="./3_bind.js"></script> </head> <body> <script> // 声明一个函数 function add(a, b) { console.log('this', this) return a + b + this.c } // 声明一个对象 let obj = { c: 123 } // 添加全局属性 window.c = 456 // 执行bind函数 let fn = bind(add, obj, 10, 20) console.log(fn()) let fn2 = bind(add, obj) console.log(fn2(30, 40)) </script> </body> </html>
函数节流(throttle):控制事件执行的时间间隔(在函数需要频繁触发时,函数执行一次后,只有大于设定的执行周期后才会执行第二次,适合多次事件按时间做平均分配触发)
场景:
jsfunction throttle(callback, wait) {
// 定义开始时间
let start = 0
// 返回结果是一个函数
return function(event) {
// 获取当前时间戳
let now = Date.now()
// 判断
if (now - start >= wait) {
// 若满足条件则执行回调函数
callback.call(this, event)
// 更新开始时间
start = now
}
}
}
throttle(callback, wait)
wait
毫秒内最多执行 callback
一次测试:
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>函数节流</title> <style> body { height: 2000px; background: linear-gradient(to right bottom, rgb(0, 255, 240), rgb(92, 159, 247) 40%, rgb(211, 34, 255) 80%); } </style> <script src="./throttle.js"></script> </head> <body> <script> // 绑定滚动事件 /* window.addEventListener('scroll', function() { console.log(Date.now()) }) */ window.addEventListener('scroll', throttle(function() { console.log(Date.now()) }, 500)) </script> </body> </html>
函数防抖(debounce):在函数需要频繁触发时,在规定时间内,只让最后一次生效,前面的不生效,适合多次事件一次响应的情况。
场景:
jsfunction debounce(callback, wait) {
// 定时器变量
let timeId = null
// 返回一个函数
return function(event) {
// 判断
if (timeId !== null) { // 已经有定时器在运行
// 清空定时器
clearTimeout(timeId)
}
// 启动定时器
timeId = setTimeout(() => {
// 执行回调
callback.call(this, event)
// 重置定时器变量
clearTimeout(timeId)
}, wait)
}
}
debounce(callback, wait)
wait
毫秒后调用 callback
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>函数防抖</title> <script src="./debounce.js"></script> </head> <body> <input type="text"> <script> let input = document.querySelector('input') /* input.onkeydown = function(e) { console.log(e.key) } */ input.onkeydown = debounce(function(e) { console.log(e.key) }, 1000) </script> </body> </html>
map()
方法创建一个新数组,其结果是该数组中的每个元素是调用一次 提供的函数 后的返回值
js/**
* map()
*
* @param {Array} arr
* @param {Function} callback
* @return {*}
*/
function map(arr, callback) {
// 声明空数组
let result = []
// 遍历数组
for (let i = 0; i < arr.length; i++) {
// 执行回调
// result[i] = callback(arr[i], i)
result.push(callback(arr[i], i))
}
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> const arr = [1, 2, 3, 4, 5] const result = map(arr, (item) => { return item * 10 }) console.log(result) </script> </body> </html>
reduce()
从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值作为参数传给下次回调函数,并返回最后一次回调函数的返回值
js/**
* reduce()
*
* @param {Array} arr
* @param {Function} callback
* @param {*} initValue
* @return {*}
*/
function reduce(arr, callback, initValue) {
let result = initValue
// 遍历数组
for (let i = 0; i < arr.length; i++) {
// 判断initValue
if (result === undefined) {
result = arr[i]
continue
}
// 执行回调
result = callback(result, arr[i])
}
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> const arr = [1, 2, 3, 4, 5] let result = reduce(arr, (prev, current) => { return prev + current }, 0) console.log(result) </script> </body> </html>
filter()
将所有在过滤函数中返回 true
的数组元素放进一个新数组中并返回
js/**
* filter()
*
* @param {Array} arr
* @param {Function} callback
*/
function filter(arr, callback) {
// 声明空数组,接收满足条件的结果值
let result = []
// 遍历数组
for (let i = 0; i < arr.length; i++) {
// 执行回调
let res = callback(arr[i], i)
// 为真则压入数组
if (res) {
result.push(arr[i])
}
}
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> const arr = [1, 2, 3, 4, 5] const result = filter(arr, item => item%2 === 1) console.log(result) </script> </body> </html>
find()
找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回 undefined
。
findIndex()
找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1
。
js/**
* find()
*
* @param {Array} arr
* @param {Function} callback
*/
function find(arr, callback) {
// 遍历数组
for (let i = 0; i < arr.length; i++) {
// 执行回调
let res = callback(arr[i], i)
if (res) {
return arr[i]
}
}
return undefined
}
/**
* findIndex()
*
* @param {Array} arr
* @param {Function} callback
*/
function findIndex(arr, callback) {
for (let i = 0; i < arr.length; i++) {
let res = callback(arr[i], i)
if (res) {
return i
}
}
return -1
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> const arr = [1, 2, 3, 4, 5] const result = find(arr, item => { return item > 3 }) console.log(result) const res = findIndex(arr, item => { return item > 3 }) console.log(res) </script> </body> </html>
every()
: 如果数组中的每个元素都满足测试函数,则返回 true
,否则返回 false
。
some()
: 如果数组中至少有一个元素满足测试函数,则返回 true
,否则返回 false
。
js/**
* every()
*
* @param {Array} arr
* @param {Function} callback
* @returns
*/
function every(arr, callback) {
for (let i = 0; i < arr.length; i++) {
let res = callback(arr[i], i)
if (!res) {
return false
}
}
return true
}
/**
* some()
*
* @param {Array} arr
* @param {Function} callback
* @returns
*/
function some(arr, callback) {
for (let i = 0; i < arr.length; i++) {
let res = callback(arr[i], i)
if (res) {
return true
}
}
return false
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> const arr = [1, 2, 3, 4, 5] // every const result1 = every(arr, item => { return item > 3 }) const result2 = every(arr, item => { return item > 0 }) console.log('result1', result1) console.log('result2', result2) //some const result3 = some(arr, item => { return item > 3 }) const result4 = some(arr, item => { return item > 30 }) console.log('result3', result3) console.log('result4', result4) </script> </body> </html>
forEach()
和indexOf()
forEach()
+ 对象容器
from
+ Set
或者 ...
+ Set
js/**
* unique
* forEach() + indexOf()
*
* @param {Array} arr
*/
function unique1(arr) {
const result = []
arr.forEach(item => {
if(result.indexOf(item) === -1) {
result.push(item)
}
})
return result
}
/**
* unique
* forEach() + 对象容器
*
* @param {Array} arr
*/
function unique2(arr) {
const result = []
const obj = {}
arr.forEach(item => {
if (obj[item] === undefined) {
// 将item作为下标存储在obj中
obj[item] = true
result.push(item)
}
})
return result
}
/**
* unique
* 或者 ... + Set
*
* @param {Array} arr
*/
function unique3(arr) {
// 将数组转化为集合Set
let set = new Set(arr)
// 将Set展开创建数组
let result = [...set]
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> const arr = [1, 3, 5, 2, 2, 1, 2, 3, 4, 5] console.log('@', unique1(arr)) console.log('#', unique2(arr)) console.log('$', unique2(arr)) </script> </body> </html>
js/**
* concat()
*
* @param {Array} arr
* @param {...any} args
*/
function concat(arr, ...args) {
const result = [...arr]
args.forEach(item => {
// 判断item是否为数组
if (Array.isArray(item)) {
result.push(...item)
} else {
result.push(item)
}
})
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> let arr = [1, 2, 3] const result = concat(arr, [3, 4, 5], 6, 7, [7, 6, 8]) console.log(result) </script> </body> </html>
js/**
* slice()
*
* @param {Array} arr
* @param {Number} begin
* @param {Number} end
*/
function slice(arr, begin, end) {
// 若arr数组为空
if (arr.length === 0) {
return []
}
// 判断begin和end
begin = begin || 0
end = end || arr.length
if (begin >= arr.length || end < 0) {
return []
}
begin = begin<0 ? begin+arr.length : begin
let result = []
for (let i = 0; i < arr.length; i++) {
if (i >= begin && i < end) {
result.push(arr[i])
}
}
return result
}
测试:
js<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> let arr = [1, 2, 3, 4, 5, 6, 7, 8] console.log('无参数', slice(arr)) console.log('两个参数', slice(arr, 2, 6)) console.log('begin为负', slice(arr, -3, 8)) console.log('参数超范围', slice(arr, -51, 122)) console.log('end大于length', slice(arr, 12, 15)) console.log('begin小于0', slice(arr, -8, -5)) console.log('空数组', slice([])) </script> </body> </html>
取出嵌套数组(多维)中的所有元素放到一个新数组(一维)中,如: [1, [3, [2, 4]]] ==> [1, 3, 2, 4]
reduce()
+ concat()
...
+ some()
+ concat()
js/**
* flatten()
* 递归 + reduce() + concat()
*
* @param {Array} arr
*/
function flatten1(arr) {
let result = []
arr.forEach(item => {
if (Array.isArray(item)) {
result = result.concat(flatten1(item))
} else {
result = result.concat(item)
}
})
return result
}
/**
* flatten()
* ... + some() + concat()
*
* @param {Array} arr
*/
function flatten2(arr) {
let result = [...arr]
// 循环判断
while (result.some(item => Array.isArray(item))) {
result = [].concat(...result)
}
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> let arr = [1, 2, [3, 4, [5, 6, 7], 8], 9, [10, 11]] console.log('first', flatten1(arr)) console.log('second', flatten2(arr)) </script> </body> </html>
chunk()
将数组拆分成多个 size
长度的区块,每个区块组成小数组,整体组成一个二维数组
js/**
* chunk()
*
* @param {Array} arr
* @param {Number} size
*/
function chunk(arr, size = 1) {
if (arr.length === 0) {
return []
}
let result = []
let temp = []
arr.forEach(item => {
// 判断temp长度
if (temp.length === 0) {
// 将temp压入result中
result.push(temp)
}
// 将元素压入临时数组temp中
temp.push(item)
// 判断temp长度
if (temp.length === size) {
temp = []
}
})
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> console.log(chunk([1, 2, 3, 4, 5, 6, 7, 8], 3)) </script> </body> </html>
difference(arr1, arr2)
得到arr1
中所有不在arr2
中的元素组成的数组(不改变原数组)
js/**
* difference()
*
* @param {Array} arr1
* @param {Array} arr2
*/
function difference(arr1, arr2=[]) {
if (arr1.length === 0) {
return []
}
if (arr2.length === 0) {
return arr1.slice() // 返回一个新数组
}
return arr1.filter(item => !arr2.includes(item))
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> console.log(difference([1, 3, 5 ,7], [2, 5 ,8])) </script> </body> </html>
pull(array, ...values)
:
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]
原数组发生改变
js/**
*
* @param {Array} arr
* @param {...any} args
* @returns
*/
function pull(arr, ...args) {
// 保存删掉的元素
const deleted = []
for (let i = 0; i < arr.length; i++) {
// 判断当前元素是否存在于args中
if (args.includes(arr[i])) {
// 将当前元素存入deleted
deleted.push(arr[i])
// 删除当前的元素
arr.splice(i, 1)
// 下标自检
i--
}
}
return deleted
}
/**
* pullAll()
*
* @param {Array} arr
* @param {Array} args
* @returns
*/
function pullAll(arr, args) {
return pull(arr, ...args)
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> let arr = [1, 3, 5, 3, 7] console.log(pull(arr, 2, 7, 3, 7)) console.log(arr) let array = [1, 3, 5, 3, 7] console.log(pullAll(array, [2, 7, 3, 7])) console.log(array) </script> </body> </html>
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]
js/**
* drop()
*
* @param {Array} arr
* @param {Number} count
*/
function drop(arr, count) {
// 过滤源数组 产生新数组
return arr.filter((value, index) => {
return index >= count
})
}
/**
* dropRight()
*
* @param {Array} arr
* @param {Number} count
*/
function dropRight(arr, count) {
return arr.filter((value, index) => index < arr.length - count)
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>数组API</title> <script src="./utils.js"></script> </head> <body> <script> console.log(drop([1, 3, 5, 7, 9, 11], 2)) console.log(dropRight([1, 3, 5, 7, 9, 11], 2)) </script> </body> </html>
newInstance(Fn, ...args)
Fn
构造函数的实例对象js/**
* newInstance(Fn, ...args)
*
* @param {Function} Fn
* @param {...any} args
*/
function newInstance(Fn, ...args) {
// 1.创建一个新对象
const obj = {}
// 2.修改函数内部this指向新对象 并执行
Fn.call(obj, ...args)
// 3.修改新对象的原型对象
obj.__proto__ = Fn.prototype
// 4.返回新对象
return obj
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>对象API - newInstance</title> <script src="./utils.js"></script> </head> <body> <script> function Person(name, age) { this.name = name this.age = age } function Person2(name, age) { this.name = name this.age = age return {a: 100} } function Person3(name, age) { this.name = name this.age = age return [1, 2, 3] } function Person4(name, age) { this.name = name this.age = age return function(){} } function Person5(name, age) { this.name = name this.age = age return 'morales' } // 调用 newInstance 函数创建对象 console.log(newInstance(Person, 'ABC', 18)) console.log(newInstance (Person2, 'ABC', 18)) console.log(newInstance(Person3, 'ABC', 18)) console.log(newInstance(Person4, 'ABC', 18)) console.log(newInstance(Person5, 'ABC', 18)) </script> </body> </html>
myInstanceOf(obj, Type)
obj
是否是Type
类型的实例Type
的原型对象是否是obj
的原型链上的某个对象, 如果是返回true
, 否则返回false
js/**
* myInstanceof(obj, Fn)
*
* @param {Object} obj
* @param {Function} Fn
*/
function myInstanceof(obj, Fn) {
// 获取函数的显式原型
let prototype = Fn.prototype
// 获取obj的隐式原型对象
let proto = obj.__proto__
// 遍历原型链
while (proto) {
// 检测原型对象是否相等
if (prototype === proto) {
return true
}
proto = proto.__proto__
}
return false
}
object mergeObject(...objs)
obj1
: { a: [{ x: 2 }, { y: 4 }], b: 1}
obj2
: { a: { z: 3}, b: [2, 3], c: 'foo'}
{ a: [ { x: 2 }, { y: 4 }, { z: 3 } ], b: [ 1, 2, 3 ], c: 'foo' }
js/**
* mergeObject(...objList)
*
* @param {...any} objList
*/
function mergeObject(...objList) {
// 声明空对象
const result = {}
// 遍历所有参数对象
objList.forEach((obj) => {
// 获取当前对象所有属性
Object.keys(obj).forEach((key) => {
// 检测 result 中是否存在 key 属性
if (result.hasOwnProperty(key)) {
result[key] = [].concat(result[key], obj[key])
} else {
// 直接写入
result[key] = obj[key]
}
})
})
return result
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>合并多个对象</title> <script src="./utils.js"></script> </head> <body> <script> const obj1 = { a: [{x: 2}, {y: 4}], b: 1 } const obj2 = { a: {z: 3}, b: [2, 3], c: 'abc' } console.log(mergeObject(obj1, obj2)) </script> </body> </html>
persons
数组(多个人对象的数组)
person
对象的引用地址值, 每个person对象只有一份person
对象也被复制了一份新的方法1:利用ES6语法:展开运算符
js/**
* clone1(target)
* ...
*
* @param {*} target
* @returns
*/
function clone1(target) {
// 类型判断
if(typeof target === 'object' && target !== null) {
// 判断为数组
if(Array.isArray(target)) {
return [...target]
} else {
return {...target}
}
} else {
return target
}
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>对象 / 数组拷贝</title> <script src="clone.js"></script> </head> <body> <script> const obj = {x: 'abc', y: {a: 1}} const result = clone1(obj) // 修改某个引用类型的值 result.y.a = 2 console.log('obj', obj) console.log('result', result) </script> </body> </html>
方法2:利用ES5语法:for...in
js/**
* clone2(target)
* for...in
*
* @param {*} target
* @returns
*/
function clone2(target) {
// 类型判断
if(typeof target === 'object' && target !== null) {
// 创建一个容器
const result = Array.isArray(target) ? [] : {}
// 遍历target数据
for(let key in target) {
// 判断当前对象是否包含该属性
if(target.hasOwnProperty(key)) {
result[key] = target[key]
}
}
return result
} else {
return target
}
}
测试:
jsconst obj = {x: 'abc', y: {a: 1}} const result = clone2(obj) // 修改某个引用类型的值 result.y.a = 3 console.log('obj', obj) console.log('result', result) console.log(obj === result)
1 - 大众乞丐版
js/**
* deepClone1(target)
* 大众乞丐版
*
* @param {*} target
* @returns
*/
function deepClone1(target) {
// 通过数据创建json格式字符串
let str = JSON.stringify(target)
// 将json字符串创建为JS数据
let data = JSON.parse(str)
return data
}
测试:
const obj = { a: 1, b: ['e', 'f', 'g'], c: {h: 'i', j: 30}, d: function(){} } const result = deepClone1(obj) result.c.j = 1909 console.log('obj', obj) console.log('result', result)
循环引用报错:
js// 循环引用 obj.b.push(obj.c) obj.c.k = obj.b
2 - 面试基础版(递归)
解决了函数属性丢失的问题
js/**
* deepClone2(target)
* 面试基础版
*
* @param {*} target
* @returns
*/
function deepClone2(target) {
// 检测数据类型
if(typeof target === 'object' && target !== null) {
// 创建容器
const result = Array.isArray(target) ? [] : {}
// 遍历对象
for(let key in target) {
// 检测该属性是否为target本身的属性(不能拷贝原型对象上的属性)
if(target.hasOwnProperty(key)) {
// 拷贝
result[key] = deepClone2(target[key])
}
}
return result
} else {
return target
}
}
测试:
jsconst obj = { a: 1, b: ['e', 'f', 'g'], c: {h: 'i', j: 30}, d: function(){} } // 循环引用 - 报错 // obj.b.push(obj.c) // obj.c.k = obj.b const result = deepClone2(obj) result.c.j = 2004 console.log('obj', obj) console.log('result', result)
循环引用会导致栈溢出:
3 - 面试加强版
解决了循环引用出错的问题
js/**
* deepClone3(target)
* 面试加强版
*
* @param {*} target
* @returns
*/
function deepClone3(target, map=new Map()) {
// 检测数据类型
if(typeof target === 'object' && target !== null) {
// 克隆数据之前判断:数据是否克隆过
let cache = map.get(target)
if(cache) {
return cache
}
// 创建容器
const result = Array.isArray(target) ? [] : {}
// 将新的结果存到容器中
map.set(target, result)
// 遍历对象
for(let key in target) {
// 检测该属性是否为target本身的属性(不能拷贝原型对象上的属性)
if(target.hasOwnProperty(key)) {
// 拷贝
result[key] = deepClone3(target[key], map)
}
}
return result
} else {
return target
}
}
测试:
jsconst obj = { a: 1, b: ['e', 'f', 'g'], c: {h: 'i', j: 30}, d: function(){} } obj.b.push(obj.c) obj.c.k = obj.b const result = deepClone3(obj) result.c.j = '20H2' console.log('obj', obj) console.log('result', result)
4 - 面试加强优化版
优化了遍历性能
while
| for
| forEach()
优于 for-in
| keys()&forEach()
for-in
与 keys()&forEach()
差不多js/**
* deepClone4(target)
* 面试加强版 - 优化遍历性能
*
* @param {*} target
* @returns
*/
function deepClone4(target, map=new Map()) {
// 检测数据类型
if(typeof target === 'object' && target !== null) {
// 克隆数据之前判断:数据是否克隆过
let cache = map.get(target)
if(cache) {
return cache
}
// 判断目标数据类型
let targetIsArray = Array.isArray(target)
// 创建容器
const result = targetIsArray ? [] : {}
// 将新的结果存到容器中
map.set(target, result)
// 遍历对象
// 如果目标数据为数组
if(targetIsArray) {
// forEach
target.forEach((item, index) => {
result[index] = deepClone4(item, map)
})
} else {
Object.keys(target).forEach(key => {
result[key] = deepClone4(target[key], map)
})
}
return result
} else {
return target
}
}
测试:
jsconst obj = { a: 1, b: ['e', 'f', 'g'], c: {h: 'i', j: 30}, d: function(){} } obj.b.push(obj.c) obj.c.k = obj.b const result = deepClone4(obj) result.c.j = '21H2' console.log('obj', obj) console.log('result', result)
需求:给出一个对象,要求删除对象中的空字符串、null、空数组、空对象
jsfunction check(target) {
if(target === '') {
return true
}
if(target === null) {
return true
}
if(typeof target === 'object') {
if(Array.isArray(target)) {
if(JSON.stringify(target) === '[]') {
return true
}
} else {
if(JSON.stringify(target) === '{}') {
return true
}
}
}
return false
}
function clearValue(obj) {
Object.keys(obj).forEach(key => {
if (check(obj[key])) {
delete obj[key]
} else if(typeof obj[key] === 'object') {
clearValue(obj[key])
}
})
}
测试:
jsconst obj = { a: 1, b: '', c: [], d: { e: [], f: {}, g: 'abc' } } console.log('Before', obj) clearValue(obj) console.log('After', obj)
reverseString(str)
palindrome(str)
truncate(str, len)
len
, 截取前面len
长度部分, 并以...
结束js/**
* reverseString(str)
*
* @param {String} str
* @returns
*/
function reverseString(str) {
// 将字符串转为数组
// return str.split('').reverse().join('')
// return [...str].reverse().join('')
return Array.from(str).reverse().join('')
}
/**
* palindrome(str)
*
* @param {String} str
* @returns
*/
function palindrome(str) {
return reverseString(str) === str
}
/**
* truncate(str, len)
*
* @param {String} str
* @param {Number} len
* @returns
*/
function truncate(str, len) {
return str.slice(0, len) + '...'
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>字符串</title> <script src="string.js"></script> </head> <body> <script> let str1 = 'moralesrx' let str2 = 'win101niw' let str3 = '哔哩哔哩干杯 - 哔哩哔哩' // 字符串翻转 console.log(reverseString(str1)) // 回文字符串 console.log(palindrome(str2)) // 字符串截断 console.log(truncate(str3, 10)) </script> </body> </html>
html<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>事件捕获与冒泡</title>
<style>
.outter {
margin-left: 50px;
margin-top: 50px;
width: 200px;
height: 200px;
background-color: #16a086;
border-radius: 50%;
position: relative;
}
.inner {
position: absolute;
left: 50px;
top: 50px;
width: 100px;
height: 100px;
background-color: #f39c11;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="outter"><div class="inner"></div></div>
<script>
let outter = document.querySelector('.outter')
let inner = document.querySelector('.inner')
inner.addEventListener('click', function() {
console.log('捕获 inner')
}, true) // true:捕获,false(默认):冒泡
outter.addEventListener('click', function() {
console.log('捕获 outter')
}, true)
inner.addEventListener('click', function() {
console.log('冒泡 inner')
})
outter.addEventListener('click', function() {
console.log('冒泡 outter')
})
</script>
</body>
</html>
js// 父级元素选择器,绑定事件类型,事件处理程序,绑定元素选择器
/**
* addEventListener(el, type, fn, selector)
* 如果selector没有,直接给element绑定事件
* 如果selector有,将selector对应的多个元素的事件委托绑定给父元素element
*
* @param {String} el 父级元素选择器
* @param {String} type 绑定事件类型
* @param {Function} fn 事件处理程序
* @param {String} selector 绑定元素选择器
*/
function addEventListener(el, type, fn, selector) {
// 判断el类型
if(typeof el === 'string') {
el = document.querySelector(el)
}
// 事件绑定
if(!selector) { // 没有传递子元素选择器,普通的事件绑定
el.addEventListener(type, fn)
} else {
el.addEventListener(type, function(e) {
// 获取点击的目标事件源
const target = e.target
// 判断选择器与目标元素是否相符合
if(target.matches(selector)) {
fn.call(target, e)
}
})
}
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>事件绑定</title> <script src="dom.js"></script> </head> <body> <ul id="items"> <li>AAAA</li> <li>BBBB</li> <li>CCCC</li> <li>DDDD</li> <div>EEEE</div> <div>FFFF</div> </ul> <script> // 调用函数绑定事件 addEventListener('#items', 'click', function() { // 输出标签文本 console.log(this.innerHTML) }, 'li') </script> </body> </html>
eventBus
: 包含所有功能的事件总线对象eventBus.on(eventName, listener)
: 绑定事件监听eventBus.emit(eventName, data)
: 分发事件eventBus.off(eventName)
: 解绑指定事件名的事件监听, 如果没有指定解绑所有在绑定时把事件类型与事件回调保存起来,在触发以后查找该类型是否有与之对应的回调,如果有则取出并执行
jsconst eventBus = {
// 保存类型与回调的容器
callbacks: {}
}
/**
* 绑定事件
*
* @param {String} type
* @param {Function} callback
*/
eventBus.on = function(type, callback) {
// 判断
if(this.callbacks[type]) {
this.callbacks[type].push(callback)
} else {
this.callbacks[type] = [callback]
}
}
/**
* 触发事件
*
* @param {String} type
* @param {*} data
*/
eventBus.emit = function(type, data) {
// 判断
if(this.callbacks[type] && this.callbacks[type].length > 0) {
// 遍历数组
this.callbacks[type].forEach(callback => {
// 执行回调
callback(data)
})
}
}
/**
* 事件解绑
*
* @param {*} eventName
*/
eventBus.off = function(eventName) {
// 若传入eventName
if(eventName) {
// 删除eventName对应事件的回调
delete this.callbacks[eventName]
} else {
this.callbacks = {}
}
}
测试:
html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>事件总线</title> <script src="event-bus.js"></script> </head> <body> <script> // eventBus eventBus.on('login', data => { console.log(data + '用户已登录') }) eventBus.on('login', data => { console.log(data + '登录数据已写入') }) // 触发 setTimeout(() => { eventBus.emit('login', 'Morales') }, 2000) // 解绑事件 eventBus.off('login') // eventBus.off() // console.log(eventBus) </script> </body> </html>
PubSub
: 包含所有功能的订阅/发布消息的管理者PubSub.subscribe(msg, subscriber)
: 订阅消息: 指定消息名和订阅者回调函数PubSub.publish(msg, data)
: 异步发布消息: 指定消息名和数据PubSub.publishSync(msg, data)
: 同步发布消息: 指定消息名和数据PubSub.unsubscribe(flag)
: 取消订阅: 根据标识取消某个或某些消息的订阅jsconst Pubsub = {
// 订阅唯一id
id: 1,
// 频道与回调保存容器
callbacks: {
/*pay: {
token_1: fn1,
token_2: fn2
}*/
}
}
Pubsub.subscribe = function(channel, callback) {
// 为每个订阅创建唯一编号
let token = "token_" + this.id++
// 判断callbacks属性中是否存在pay
if(this.callbacks[channel]) {
this.callbacks[channel][token] = callback
} else {
this.callbacks[channel] = {
[token]: callback
}
}
return token
}
Pubsub.publish = function(channel, data) {
// 获取当前频道中所有回调
if(this.callbacks[channel]) {
Object.values(this.callbacks[channel]).forEach(callback => {
// 执行回调
callback(data)
})
}
}
Pubsub.unsubscribe = function(flag) {
// 没有传值, flag为undefined:全部取消
// 传入token字符串
// msgName字符串
if(flag === undefined) {
this.callbacks = {}
} else if(typeof flag === 'string') {
// 判断是否为token_开头
if(flag.indexOf('token_') === 0) {
let callbackObj = Object.values(this.callbacks).find(obj => obj.hasOwnProperty(flag))
if(callbackObj) {
delete callbackObj[flag]
}
} else {
delete this.callbacks[flag]
}
}
}
测试:
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>消息订阅与发布</title> <script src="pubsub.js"></script> </head> <body> <script> // 订阅一个频道 Pubsub.subscribe('pay', data => { console.log('商家接单,准备制作', data.name) }) Pubsub.subscribe('pay', data => { console.log('骑手接单,准备取餐') }) Pubsub.subscribe('cancel', data => { console.log('取消了') }) console.log(Pubsub) // 发布消息 // Pubsub.publish('pay', { // name: '水煮肉片', // price: 13, // position: '松新街21号' // }) // 取消订阅 // Pubsub.unsubscribe('pay') Pubsub.unsubscribe('token_2') console.log(Pubsub) Pubsub.publish('pay', { name: '水煮肉片', price: 13, position: '松新街21号' }) </script> </body> </html>
函数的参数为一个配置对象
{ url: '', // 请求地址 method: '', // 请求方式GET/POST/PUT/DELETE params: {}, // GET/DELETE请求的query参数 data: {}, // POST或DELETE请求的请求体参数 }
返回值: 函数的返回值为promise
, 成功的结果为response
, 失败的结果为error
能处理多种类型的请求: GET
/ POST
/ PUT
/ DELETE
响应json
数据自动解析为js
的对象/数组
axios(options)
url
, method
, params
与data
promise
对象jsfunction axios({method, url, params, data}) {
method = method.toUpperCase()
return new Promise((resolve, reject) => {
// 1.创建Ajax对象
const xhr = new XMLHttpRequest()
// 2.初始化
// 处理 params 对象:a=100&b=200
let str = ''
for(let k in params) {
str += `${k}=${params[k]}&`
}
str = str.slice(0, -1)
xhr.open(method, url+'?'+str)
// 3.发送
if(method === 'POST' || method === 'PUT' || method === 'DELETE') {
// 告诉服务器请求体的格式是json
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8')
// 设置请求体
xhr.send(JSON.stringify(data))
} else {
xhr.send()
}
// 设置响应结果的类型为json
xhr.responseType = 'json'
// 4.绑定状态改变的监听
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status >= 200 && xhr.status < 300) {
// 成功的状态
resolve({
status: xhr.status,
message: xhr.statusText,
body: xhr.response
})
} else {
reject(new Error('请求失败,状态码为:' + xhr.status))
}
}
}
})
}
测试:
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Axios</title> <script src="axios.js"></script> </head> <body> <script> axios({ method: 'get', url: 'https://v1.hitokoto.cn', params: { c: 'd', encode: 'json' }, data: { a: 100, b: 200 } }).then(response => { console.log(response) }, reason => { console.log(reason) }) axios({ method: 'get', url: 'https://v118.hitokoto.cn', params: { c: 'd', encode: 'json' }, data: { a: 100, b: 200 } }).then(response => { console.log(response) }, reason => { console.log(reason) }) </script> </body> </html>
发送特定请求的静态方法:
axios.get(url, options)
axios.post(url, data, options)
axios.put(url, data, options)
axios.delete(url, options)
jsaxios.get = function(url, options) {
return axios(Object.assign(options, {method: 'GET', url: url}))
}
axios.post = function(url, options) {
return axios(Object.assign(options, {method: 'POST', url: url}))
}
axios.put = function(url, options) {
return axios(Object.assign(options, {method: 'PUT', url: url}))
}
axios.delete = function(url, options) {
return axios(Object.assign(options, {method: 'DELETE', url: url}))
}
本文作者:Morales
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 License 许可协议。转载请注明出处!