- 手写 Promise
- 手写 Reduce
- 手写 funPromiseInSequence(按顺序运行 promise)
- 数组去重
- 手写 pipe
- 手写 compose
- 考察 this
- 考察原型链
- 考察闭包
- 性能相关
- 手写观察者模式
- 手写 EventEmitrer 发布订阅模式
- 手写数组方法
- 实现一个精确的定时器
- 使用 setTimeout 模拟 setInterval
- 轮播图
- usePrevious
- useFetch
- 手写深拷贝
- 使用 class 或原型链实现计算器,要求能够链式调用
- 进制转换
- 向 URL 中插入 query 参数
- 实现 PascalCase 到 camelCase 之间的转换
- 实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间
- parseURLParams
- URLSearchParams
- 手写 chunk
- 手写一个异步加法
- 原生手写一个 Modal
- 转换成树形结构
手写 Promise
function IPromise(executor) {
this.status = 'pending' // 当前状态
this.value = null // resolve后的值
this.reason = null // reject后的值
this.onFullfilledArray = [] // fullfilled状态数组,用于存放fullfilled状态的回调函数
this.onRejectedArray = [] // rejected状态数组,用于存放rejected状态的回调函数
const resolve = value => {
if (value instanceof IPromise) {
return value.then(resolve, reject)
}
// 模拟微任务
setTimeout(() => {
if (this.status === 'pending') {
this.value = value
this.status = 'fullfilled'
// 遍历执行
this.onFullfilledArray.forEach(func => {
func(value)
})
}
})
}
const reject = reason => {
setTimeout(() => {
if (this.status === 'pending') {
this.reason = reason
this.status = 'rejected'
this.onRejectedArray.forEach(func => {
func(reason)
})
}
})
}
try {
executor(resolve, reject)
} catch (e) {
reject(e)
}
}
// 由于变量result既可以是普通值,也可以是promise实例,所以用resolvePromise统一管理
const resolvePromise = (promise2, result, resolve, reject) => {
// result 和 promise2 相等会出现死循环
if (result === promise2) {
reject(new TypeError('error due to circular reference'))
}
let consumed = false // 是否已经执行过
let thenable
if (result instanceof IPromise) {
if (result.status === 'pending') {
result.then(function (data) {
resolvePromise(promise2, data, resolve, reject)
}, reject)
} else {
result.then(resolve, reject)
}
return
}
let isComplexResult = target =>
typeof target === 'function' ||
(typeof target === 'object' && target !== null)
// 如果返回的是疑似 Promise类型
if (isComplexResult(result)) {
try {
thenable = result.then
// 判断返回值是否是promise类型
if (typeof thenable === 'function') {
thenable.call(
result,
function (data) {
if (consumed) {
return
}
consumed = true
return resolvePromise(promise2, data, resolve, reject)
},
function (error) {
if (consumed) {
return
}
consumed = true
reject(error)
}
)
} else {
resolve(result)
}
} catch (e) {
if (consumed) return
consumed = true
return reject(e)
}
} else {
resolve(result)
}
}
IPromise.prototype.then = function (onFullfilled, onRejected) {
// promise穿透
onFullfilled =
typeof onFullfilled === 'function' ? onFullfilled : data => data
onRejected =
typeof onRejected === 'function'
? onRejected
: err => {
throw error
}
let promise2 // 作为then方法的返回值
if (this.status === 'fullfilled') {
return (promise2 = new IPromise((resolve, reject) => {
setTimeout(() => {
try {
let result = onFullfilled(this.value)
resolvePromise(promise2, result, resolve, reject)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'rejected') {
return (promise2 = new IPromise((resolve, reject) => {
setTimeout(() => {
try {
let result = onRejected(this.reason)
resolvePromise(promise2, result, resolve, reject)
} catch (e) {
reject(e)
}
})
}))
}
if (this.status === 'pending') {
return (promise2 = new IPromise((resolve, reject) => {
setTimeout(() => {
this.onFullfilledArray.push(value => {
try {
let result = onFullfilled(value)
resolvePromise(promise2, result, resolve, reject)
} catch (e) {
reject(e)
}
})
this.onRejectedArray.push(reason => {
try {
let result = onRejected(reason)
resolvePromise(promise2, result, resolve, reject)
} catch (e) {
reject(e)
}
})
})
}))
}
}
const p = new IPromise((resolve, reject) => {
setTimeout(() => {
resolve('ha0ran')
}, 2000)
})
p.then(null).then(data => console.log(data))
手写 Promise.all
该方法接收一个 promise 的 iterable 类型,并且只返回一个 Promise 实例,那个输入的所有 promise 的 resolve 回调的结果是一个数组。
这个 Promise 实例的 resolve 回调执行是在所有输入的 promise 的 resolve 回调都结束,或者输入的 iterable 里没有 promise 了的时候。
它的 reject 回调执行是在任意一个输入的 promise 的 reject 执行或者
如果传入的参数不包含任何 promise,则返回一个异步完成的 Promise
其他情况下返回一个处理中的 Promise。返回的数组的值会按照参数内的 promise 顺序排列。
/**
* 输入为 iterable 的参数,可以是 Array、Map、Set、String,可能也得包括模块的Iterator
* 若输入的可迭代数据里不是 Promise,则也需要原样输出
* 返回一个 Promise实例,可以调用 then 和 catch 方法
* 输出在then里面体现为保持原顺序的数组
* 输出在 catch 里面体现为最早的reject返回值
* 空 iterator,resolve 返回空数组
*/
function all(promises) {
// your code here
if (
promises == null ||
(Array.isArray(promises) && promises.length === 0)
) {
return Promise.resolve([])
}
return new Promise((resolve, reject) => {
let unSettledPromiseCount = promises.length
const result = Array(unSettledPromiseCount)
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
res => {
// - result.push(res);
// +
result[index] = res
unSettledPromiseCount--
if (unSettledPromiseCount === 0) {
resolve(result)
}
},
err => {
reject(err)
}
)
})
})
}
Promise.allSettled
字节实习一面
与 Promise.all 类似,会返回一个对象数组,但是该方法会返回所有的 promise 结果,不论 promise 的状态是 fullfilled 还是 rejected。
function allSettled(promises) {
// 如果传入一个空对象,返回一个已经完成的Promise
if (promises.length === 0) {
return Promise.resolve([])
}
return new Promise((resolve, reject) => {
let unSettledPromiseCount = promises.length
const result = Array(unSettledPromiseCount)
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
value => {
result[index] = {
status: 'fulfilled',
value,
}
unSettledPromiseCount--
// 如果全部都 resolve 掉了,就返回结果
if (unSettledPromiseCount === 0) {
resolve(result)
}
},
reason => {
result[index] = {
status: 'rejected',
reason,
}
unSettledPromiseCount--
if (unSettledPromiseCount === 0) {
resolve(result)
}
}
)
})
})
}
手写 Promise.race
function PromiseRace(args) {
let iteratorIndex = 0
return new Promise((resolve, reject) => {
for (const item of args) {
iteratorIndex++
Promise.resolve(item).then(resolve, reject)
}
if (iteratorIndex === 0) {
resolve([])
}
})
}
Promise.any
Promise.then
Promise.resolve
Promise.reject
Promise.catch
Promise.finally
猿辅导二面
Promise.prototype.finally = function (cb) {
this.then(val => {
return Promise.resolve(cb()).then(() => val)
}, err => {
return Promise.resolve(cb()).then(() => {
throw err
})
})
}
手写 Reduce
Array.prototype.ireduce = function (func, initialValue) {
let arr = this
let base = initialValue === 'undefined' ? arr[0] : initialValue
let startPoint = initialValue === 'undefined' ? 1 : 0
arr.slice(startPoint).forEach((value, index) => {
base = func(base, value, index + startPoint, arr) // pre, cur, index, arr
})
return base
}
手写 funPromiseInSequence(按顺序运行 promise)
const runPromiseInSequence = (array, value) => {
return array.reduce(
(promiseChain, currentFunc) => promiseChain.then(currentFunc),
Promise.resolve(value)
)
}
数组去重
// 可去重 NaN & 引用类型
function unique(arr) {
let newObj = {};
let newArr = [];
arr.forEach((item) => {
if (typeof item !== 'object') {
// NaN是唯一一个不等于任何自身的类型
if (item !== item) {
if (!newObj[item.toString()]) {
newArr.push(item);
newObj[item.toString()] = true;
}
} else {
if (newArr.indexOf(item) === -1) {
newArr.push(item);
}
}
} else {
let str = JSON.stringify(item);
if (!newObj[str]) {
newArr.push(item);
newObj[str] = true;
}
}
});
return newArr;
}
手写 pipe
const pipe =
(...funcs) =>
input =>
funcs.reduce((pre, cur) => cur(pre), input)
手写 compose
function compose(...args) {
if (args.length === 0) {
return arg => arg
}
if (args.length === 1) {
return args[0]
}
return args.reduce(
(a, b) =>
(...arg) =>
a(b(...arg))
)
}
写法四:迭代法
var compose = function (funcs) {
var len = funcs.length
var index = length
while (index--) {
if (typeof funcs[index] !== 'function') {
throw new TypeError('expected a function')
}
}
return function (...args) {
var index = 0
var result = length ? funcs.reverse()[index].apply(this, args) : args[0]
while (++index < length) {
result = funcs[index].call(this, result)
}
return result
}
}
考察 this
手写 bind
Function.prototype.bind = function (context) {
const me = this
const args = Array.prototype.slice.call(arguments, 1)
let F = function () {}
F.prototype = this.prototype
let bound = function () {
const innerArgs = Array.prototype.slice.call(arguments)
const finalArgs = args.concat(innerArgs)
return me.apply(this instanceof F ? this : context || this, finalArgs)
}
// 因为直接使用 bound.prototype = this.protoype 有个缺点
// 修改 bound.prototype 的时候,也会修改 this.prototype
// 这里就相当于使用了 Object.create
bound.prototype = new F()
return bound
}
手写 apply
Function.prototype.apply = function (context = window, argsArray = []) {
context = new Object(context)
const targetFnKey = 'targetFnKey' + Date.now()
context[targetFnKey] = this
const result = context[targetFnKey](...argsArray)
delete context[targetFnKey]
return result
}
手写 call
差不多同上
Function.prototype.call = function (context = window, ...args) {
context = new Object(context)
const targetKey = 'targetKey' + Date.now()
context[targetKey] = this
let result = context[targetKey](...args)
delete context[targetKey]
return result
}
手写 new
function newFun(...args) {
// 获得构造函数
const constructor = args.shift()
// 创建一个空对象,且使这个空对象继承构造函数的prototype属性
// obj.__proto__ = constructor.prototype;
const obj = Object.create(constructor.prototype)
// 执行构造函数
const result = constructor.apply(obj, args)
return typeof result === 'object' && result !== null ? result : obj
}
链式调用
Number.prototype.add = function(num) {
let n = +num
return Number(this + n)
}
// case
let n = 1
n.add(2).add(3) // -> 6
考察原型链
手写 instanceof
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left), // 获取对象的原型
prototype = right.prototype // 获取构造函数的 prototype 对象
// 判断构造函数的 prototype 对象是否在对象的原型链上
while (true) {
if (!proto) return false
if (proto === prototype) return true
proto = Object.getPrototypeOf(proto)
}
}
手写 Object.create
创建一个对象,使用现有对象作为新创建对象的原型(prototype),新对象继承了原对象
function create(proto) {
let F = function () {}
F.prototype = proto
return new F()
}
寄生式组合继承
function Person() {}
function Child() {
Person.call(this)
}
let prototype = Object.create(Person.prototype) // 创建对象,创建父类原型的一个副本
prototype.constructor = Child // 增强对象,解决重写原型导致默认 constructor 丢失的问题
Child.prototype = prototype // 赋值对象,将新创建的对象赋值给子类的原型
考察闭包
柯里化
function curry(fn, ...args) {
if (args.length >= fn.length) {
return fn.apply(null, args)
}
return (...args2) => curry(fn, ...args, ...args2)
}
性能相关
手写 防抖
在一段时间结束之后才执行
应用场景:
- 搜索框:用户最后一次输入完成之后,才发送请求
- 手机号、邮箱等的验证
- 窗口大小的 resize
function debounce(func, wait, immediate) {
let timeout = null
return function debounced () {
const ctx = this, args = arguments
if (timeout) {
clearTimeout(timeout)
}
if (immediate) {
// 刚进来的时候callnow = !timeout = true
let callnow = !timeout
timeout = setTimeout(function () {
timeout = null // 到时间后 timeout = null, callnow 就为 true,就可以执行了
}, wait)
if (callnow) {
func.apply(context, args)
}
} else {
timeout = setTimeout(function (){
func.apply(context, args)
}, wait)
}
}
}
手写节流
在一段时间之内连续触发,只执行一次
第一次立即执行
在一段时间间隔内只执行一次
在时间的间隔的末尾也要执行一次(也就是最后一次的操作要触发)
应用:
- 搜索:联想搜索
- 滚动加载
- 高频点击,表单重复提交
// 立即执行
var throttle = function (cb, wait, isImmediate = true) {
let execTime = +new Date()
let timeId = null
// ! 为什么要返回一个函数
return function () {
const context = this
const args = arguments
if (isImmediate) {
cb.apply(context, args)
execTime = +new Date()
isImmediate = false
} else {
// * 达到wait时间
const currentTime = +new Date()
if (currentTime - execTime >= wait) {
cb.apply(context, args)
execTime = +new Date()
} else {
// 实现执行最后一次动作
// ! 还剩多少时间执行
const timeWait = wait - (+new Date() - execTime)
timeId && clearTimeout(timeId)
timeId = setTimeout(() => {
cb.apply(context, args)
execTime = +new Date()
timer = null
}, timeWait)
}
}
}
}
useDebounce
function useDebounce<T>(value: T, delay?: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay || 500)
return () => {
clearTimeout(timer)
}
}, [value, delay])
return debouncedValue
}
useThrottle
function useThrottle <T>(value: T, ms: number = 200) {
const [state, setState] = useState<T>(value)
const timeout = useRef<ReturnType<typeof setTimeout>>()
const nextValue = useRef(null) as any
const hasNextValue = useRef(0) as any
useEffect(() => {
if (!timeout.current) {
setState(value)
const timeoutCallback = () => {
if (hasNextValue.current) {
hasNextValue.current = false
setState(nextValue.current)
timeout.current = setTimeout(timeoutCallback, ms)
} else {
timeout.current = undefined
}
}
timeout.current = setTimeout(timeoutCallback, ms)
} else {
nextValue.current = value
hasNextValue.current = true
}
return () => {
timeout.current && clearTimeout(timeout.current)
}
}, [value, ms])
return state
}
限制并发数
猿辅导二面
function sendRequest(urls, max, callback) {
let limit = max,
n = urls.length,
res = [],
cnt = 0
const tasks = urls.map(
(url, index) => () =>
fetch(url)
.then(data => {
res[index] = data
})
.catch(err => {
res[index] = err
})
.finally(() => {
if (++cnt === n) {
return callback(res)
}
limit++
doTask()
})
)
doTask()
function doTask() {
while (limit > 0 && tasks.length) {
limit--
const task = tasks.shift()
task()
}
}
}
手写带最大请求数的并发请求
猿辅导二面
function bingfa(requestArr, limitNum) {
const n = requestArr.length
const maxNum = n >= limitNum ? limitNum : n
return new Promise((resolve, reject) => {
const res = Array(n)
let runnedCount = 0
const recursive = (requestArr, index) => {
Promise.resolve(requestArr[index]()).then((val) => {
// 第一次执行then的时机是微任务队列里最快的promise完成
res[index] = val // 执行结果按顺序记录
// 表示requestArr中的请求任务已经全部添加完毕
// 并且此时微任务队列里的任务一定是小于等于maxNum
// 没有后续添加的任务了,所以可以直接正常执行微任务队列里的任务
// 它们会自行按照快慢执行
if (runnedCount >= n) {
return resolve(res)
}
// 有一个进到这里(执行了then),说明它执行完毕,需要再往任务队列里添加一个
// 补齐manNum个
recursive(requestArr, runnedCount++)
})
}
// 经过下面的循环之后,微任务队列里会有maxNum个promise的回调
// runnedCount的值等于maxNum
for (let i = 0; i < maxNum; i++) {
recursive(requestArr, i)
runnedCount++
}
})
}
手写一个带并发限制的异步调度器 Scheduler
class Scheduler {
constructor(limitCount) {
this.promises = []
this.limit = limitCount
this.runnedCount = 0
}
add(promiseCreator) {
return new Promise((resolve, reject) => {
promiseCreator.resolve = resolve
promiseCreator.reject = reject
this.promises.push(promiseCreator)
this.run()
})
}
run() {
if (this.runnedCount < this.limit && this.promises.length) {
this.runnedCount++
const promise = this.promises.shift()
promise().then(res => {
promise.resolve(res)
}).catch(err => {
promise.reject(err)
}).finally(() => {
this.runnedCount--
this.run()
})
}
}
}
const scheduler = new Scheduler(2)
const timeout = (wait) => {
return new Promise((resolve, reject) => {
setTimeout(resolve, wait)
})
}
const addTask = (time, content) => {
scheduler.add(() => timeout(time)).then(() => console.log(content))
}
addTask(1000, '1')
addTask(500, '2')
addTask(300, '3')
addTask(400, '4')
手写观察者模式
// 被观察者对象;
class Subject {
constructor() {
this.observerList = []
}
addObserver(observer) {
this.observerList.push(observer)
}
removeObserver(observer) {
this.observerList = this.observerList.filter(
o => o.name !== observer.name
)
}
notifyObserver(message) {
this.observerList.forEach(observer => observer.notified(message))
}
}
// 观察者;
class Observer {
constructor(name, subject) {
this.name = name
if (subject) {
subject.addObserver(this)
}
}
notified(message) {
console.log(this.name, message)
}
}
手写 EventEmitrer 发布订阅模式
class EventEmitter {
constructor() {
this.listeners = Object.create(null)
}
/**
* 注册事件 监听者
* @param {string} eventName
* @param {Function} listener
*/
on(eventName, listener) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = []
}
this.listeners[eventName].push(listener)
}
// 取消订阅
off(eventName, listener) {
if (!this.hasBind(eventName)) {
console.warn(`this event ${eventName} don't exist`)
return
}
// remove all listener
if (!listener) {
delete this.listeners[eventName]
return
}
this.listeners[eventName] = this.listeners[eventName].filter(
callback => callback !== listener
)
}
// 只订阅一次
once(eventName, listener) {
function one() {
listener.apply(this, arguments)
this.off(eventName)
}
this.on(eventName, one) // 这样就可以用emit触发此函数
}
emit(eventName, ...args) {
if (!this.hasBind(eventName)) {
console.warn(`this event ${eventName} don't exist`)
return
}
this.listeners[eventName].forEach(listener =>
listener.call(this, ...args)
)
}
hasBind(eventName) {
return this.listeners[eventName] && this.listeners[eventName].length
}
}
const baseEvent = new EventEmitter()
function cb(value) {
console.log(`hello ${value}`)
}
baseEvent.on('click', cb)
baseEvent.emit('click', 2022)
baseEvent.emit('click', 2023)
手写数组方法
Splice
/**
* 计算真实的start
* @param {number} start 开始的下标
* @param {number} len 数组的长度
*/
function computeSpliceStartIndex(start, len) {
if (start < 0) {
start += len
return start < 0 ? 0 : start
}
return start > len - 1 ? len - 1 : start
}
/**
* 计算真实的 deleteCount
* @param {number} startIndex 开始删除的下标
* @param {number} deleteCount 要删除的个数
* @param {number} len 数组长度
*/
function computeSpliceDeleteCount(startIndex, deleteCount, len) {
if (deleteCount > len - startIndex) {
deleteCount = len - startIndex
}
if (deleteCount < 0) deleteCount = 0
return deleteCount
}
/**
* 记录删除元素
* @param {*} startIndex 开始删除的下标
* @param {*} delCount 要删除的个数
* @param {*} array 目标数组
* @param {array} deletedElements 记录删除的元素
*/
function recordDeleteElements(startIndex, delCount, array, deletedElements) {
for (let i = 0; i < delCount; i++) {
deletedElements[i] = array[startIndex + i]
}
}
/**
* 移动元素,使可以插入新元素
* @param {*} startIndex
* @param {*} delCount
* @param {*} addCount 要添加的元素个数
* @param {*} array
*/
function moveElement(startIndex, delCount, addCount, array) {
let over = addCount - delCount
// 后移
if (over > 0) {
for (let i = array.length - 1; i >= startIndex + delCount; i--) {
array[i + over] = array[i]
}
}
// 左移
else if (over < 0) {
for (let i = startIndex + delCount + over; i <= array.length - 1; i++) {
if (i + Math.abs(over) > array.length - 1) {
delete array[i]
continue
}
array[i] = array[i + Math.abs(over)]
}
}
}
Array.prototype._splice = function (start, deleteCount, ...rest) {
let array = Object(this)
let len = array.length
let addCount = rest.length
let startIndex = computeSpliceStartIndex(start, len)
let delCount = computeSpliceDeleteCount(startIndex, deleteCount, len)
let deletedElements = new Array(delCount)
recordDeleteElements(startIndex, delCount, array, deletedElements)
if (delCount !== addCount && Object.isSealed(array)) {
throw new TypeError('the array is sealed')
}
if (delCount > 0 && addCount > 0 && Object.isFrozen(array)) {
throw new TypeError('the array is frozen')
}
moveElement(startIndex, delCount, addCount, array)
let i = startIndex
let argumentsIndex = 2
while (rest.length > argumentsIndex) {
array[i++] = rest[argumentsIndex++]
}
array.length = len - delCount + addCount
return array
}
console.log([1, 2, 3, 4]._splice(1, 2))
Map
Array.prototype.myMap = function (callback, thisArg) {
// 确保调用者是个数组
if (Object.prototype.toString.call(this) !== '[object Array]') {
throw new TypeError('调用者必须是数组')
}
// 确保回调函数是个函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function')
}
let res = []
for (let i = 0; i < this.length; i++) {
res[i] = callback.call(this[i], i, this, thisArg)
}
return res
}
let arr = [1, 2, 3]
arr.map((ele, index) => {
console.log(`${ele}, ${index}`)
})
filter
Array.prototype.myFilter = function (callback, thisArg) {
// 确保调用着是个数组
if (Object.prototype.toString.call(this) !== '[object Array]') {
throw new TypeError('调用者必须是数组');
}
// 确保回调函数是个函数
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
let res = [];
for (let i = 0; i < this.length; i++) {
// this[i]代表原数组当前元素
// i 代表 下标
// this 代表原数组
if (callback.call(this[i], i this, thisArg)) {
res.push(this[i]);
}
}
return res;
};
some
Array.prototype.isome = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new Error(callback + ' is not a function')
}
if (!Array.isArray(this)) {
throw new Error('调用者必须是数组')
}
for (let i = 0; i < this.length; i++) {
if (callback(this[i], i, this, thisArg)) {
return true
}
}
}
实现一个精确的定时器
var start = +new Date(),
time = 0
function instance() {
time += 1000
var diff = +new Date() - start - time
console.log(diff)
window.setTimeout(instance, 1000 - diff)
}
window.setTimeout(instance, 1000)
使用 setTimeout 模拟 setInterval
let count = 0
let timerId = null
timerId = setTimeout(function run() {
console.log('run -> ', count)
if (count >= 3) {
clearTimeout(timerId)
return
}
count += 1
timerId = setTimeout(run, 1000)
}, 1000)
轮播图
- 首先用一个
div
包起来,设定整个轮播图的位置,大小等 - 然后里面第一个
ul
里面放图片,第二个ul
里面放下面的小点使用绝对定位 - 然后就是左右两个箭头按钮,绝对定位
- js 代码部分,监听
window.onload
方法- 获取整个盒子,获取图片所有的
li
标签,获取小点所有的li
,获取左右箭头 - 定义两个变量,一个是 index 记录当前位置,一个是 timerId 用来清除 定时器
- 定义左滑方法,index —,如果减到比 0 小,则 index 变为最后一个,并展示最后一张图片
- 右滑方法,index++,如果加到比图片个数还多,那么 index = 0,然后展示第一张图片
- 鼠标经过,移除定时器,鼠标移除,添加定时器
- 在外面添加定时器
- run 方法,用来图片自己向右滑 index++,和右滑方法类似
- 展示图片的方法,排他原则,先设置所有不展示,然后把特定 index 的设置为显示,并将 index 设为这个特定的 index
- 获取整个盒子,获取图片所有的
<style>
* {
list-style: none;
}
.wrap {
width: 590px;
height: 400px;
margin: 150px auto;
position: relative;
cursor: pointer;
}
.pic li {
display: none;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
img {
width: 590px;
height: 400px;
}
.num {
position: absolute;
z-index: 2;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
.num li {
float: left;
width: 8px;
height: 8px;
margin: 5px;
border-radius: 50%;
border: 1px solid #fff;
line-height: 20px;
background: transparent;
text-align: center;
}
.num li.active {
background: #fefefe;
}
.arrow {
z-index: 3;
height: 40px;
width: 30px;
position: absolute;
top: 45%;
line-height: 40px;
background: rgba(0, 0, 0, 0.3);
text-align: center;
display: none;
}
.wrap:hover .arrow {
display: block;
}
.arrow:hover {
background: rgba(0, 0, 0, 0.7);
}
#left {
left: 0;
}
#right {
right: 0;
}
</style>
<div class="wrap">
<ul class="pic">
<li style="display: none;"><img src="./img/1.jpg" alt="" /></li>
<li style="display: none;"><img src="./img/2.jpg" alt="" /></li>
<li style="display: block;">
<img src="./img/3.jpg" alt="" />
</li>
<li style="display: none;"><img src="./img/4.jpg" alt="" /></li>
<li style="display: none;"><img src="./img/5.png" alt="" /></li>
<li style="display: none;"><img src="./img/6.png" alt="" /></li>
</ul>
<ul class="num">
<li class="active"></li>
<li class=""></li>
<li class=""></li>
<li class=""></li>
<li class=""></li>
<li class=""></li>
</ul>
<div class="arrow" id="left"><</div>
<div class="arrow" id="right">></div>
</div>
<script>
window.onload = function () {
const wrap = document.querySelector('.wrap')
const pic = document.querySelector('.pic').getElementsByTagName('li')
const num = document.querySelector('.num').getElementsByTagName('li')
const onLeft = document.querySelector('#left')
const onRight = document.querySelector('#right')
let index = 0
let timerId = null
const change = currentIndex => {
//将所有都设置为不可见
pic.forEach((item, index) => {
item.style.display = 'none'
num[index].className = ''
})
pic[currentIndex].style.display = 'block'
num[currentIndex].className = 'active'
index = currentIndex
}
onLeft.addEventListener('click', () => {
index--
if (index < 0) {
index = pic.length - 1
}
change(index)
})
onRight.addEventListener('click', () => {
index++
if (index > pic.length - 1) {
index = 0
}
change(index)
})
wrap.addEventListener('mouseover', () => {
clearTimeout(timerId)
})
wrap.addEventListener('mouseout', () => {
timerId = setTimeout(function fn() {
run()
timerId = setTimeout(fn, 2000)
}, 2000)
})
timerId = setTimeout(function fn() {
run()
timerId = setTimeout(fn, 2000)
}, 2000)
const run = () => {
index++
if (index > pic.length - 1) {
index = 0
}
change(index)
}
num.forEach((item, index) => {
item.index = index
item.addEventListener('mouseover', function () {
change(this.index)
})
})
}
</script>
usePrevious
function usePrevious(state) {
const ref = useRef()
useEffect(() => {
ref.current = state
}, [state])
return ref.current
}
useRef 不仅仅用来管理 DOM ref,还可以存放任何变量。可以很好的解决闭包带来的不便性。当 useRef 的内容改变时,不会通知,更改 .current 属性不会导致重新呈现,因为它只是一个引用。
useFetch
function useFetch(url, options) {
const cache = useRef({})
const cancelRequest = useRef(false)
const initialState = {
data: undefined,
error: undefined,
}
const fetchReducer = (state, action) => {
switch (action.type) {
case 'loading':
return { ...initialState }
case 'fetched':
return { ...initialState, data: action.payload }
case 'error':
return { ...initialState, error: action.payload }
default:
return state
}
const [state, dispatch] = useReducer(fetchReducer, initialState)
useEffect(() => {
if (!url) {
return
}
const fetchData = async () => {
dispatch({ type: 'loading' })
if (cache.current[url]) {
dispatch({ type: 'fetched', payload: cache.current[url] })
return
}
try {
const response = await fetch(url, options)
if (!response.ok) {
throw new Error(response.statusText)
}
const data = await response.json()
cache.current[url] = data
if (cancelRequest.current) return
dispatch({ type: 'fetched', payload: data })
} catch (error) {
if (cancelRequest.current) return
dispatch({ type: 'error', payload: error })
}
}
void fetchData()
return () => {
cancelRequest.current = true
}
}, [url])
}
return state
}
手写深拷贝
深拷贝和浅拷贝的区别
- 浅拷贝会创建一个新的对象,这个对象有着原有对象 属性值的一份拷贝。如果属性值是基础数据类型,拷贝的就是基础数据类型的值;如果是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。改变浅拷贝的第一层是不会影响原始对象的
- 深拷贝是将一个对象从内存中完整地拷贝一份出来,从堆内存中开辟一个新的区域存放对象,且修改新对象不会影响原始对象。
基础版
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return
}
let newObj = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] =
typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key]
}
}
return newObj
}
解决循环引用问题
function deepClone(obj, map = new Map()) {
if (obj === null || typeof obj !== 'object') {
return
}
let newObj = Array.isArray(obj) ? [] : {}
if (map.has(obj)) {
return map.get(obj)
}
map.set(obj, newObj)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] =
typeof obj[key] === 'object'
? deepClone(obj[key], map)
: obj[key]
}
}
return newObj
}
使用 class 或原型链实现计算器,要求能够链式调用
class MyCalculator {
constructor(value) {
this.value = value
}
add(num) {
this.value += num
return this
}
minus(num) {
this.value -= num
return this
}
multi(num) {
this.value *= num
return this
}
div(num) {
this.value /= num
return this
}
pow(num) {
this.value = this.value ** num
return this
}
}
进制转换
function translate(num) {
let remStack = []
let digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
let rem
let res = ''
while (num > 0) {
rem = Math.floor(num % 2)
remStack.push(rem)
rem = Math.floor(num / 2)
}
while (remStack.length) {
res += digits[remStack.pop()]
}
return res
}
向 URL 中插入 query 参数
function resolve(url) {
const params = qs.stringify({ a: 1, b: 2 })
const urlObj = new URL(url)
urlObj.search += urlObj.search ? '&' + params : '?' + params
return urlObj.href
}
or
function buildUrl(url, params) {
const urlObj = new URL(url)
const paramsObj = qs.parse(urlObj.search.slice(1))
Object.assign(paramsObj, params)
urlObj.search = qs.stringify(paramsObj)
return urlObj.href
}
实现 PascalCase 到 camelCase 之间的转换
// 实现CamelCase转换为PascalCase
function camelCase(str) {
return str.replace(/^(\w)/, function (l) {
return l.toUpperCase()
})
}
// 实现PascalCase转换为CamelCase
function camelCase(str) {
return str.replace(/^(\w)/, function (l) {
return l.toLowerCase()
})
}
实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间
//实现一个可以重复调用指定回调的函数,传入参数为回调函数,执行次数,间隔时间
function repeat(fn, times, interval) {
let i = 0
let timer = setInterval(function () {
fn()
i++
if (i === times) {
clearInterval(timer)
}
}, interval)
}
let fn = () => console.log('hello')
repeat(fn, 3, 1000)
parseURLParams
function parseURLParams(url) {
let queryParams = {};
let reg = /([^?=&]+)=([^?=&]+)/g;
url.replace(reg, function () {
queryParams[arguments[1]] = arguments[2];
});
return queryParams;
}
// result
{
source: 'gtx',
sl: 'auto',
tl: 'en',
text: '%E7%A9%BA%E6%A0%BC',
op: 'translate'
}
URLSearchParams
class MyURLSearchParams {
/**
* @params {string} init
*/
constructor(init) {
this.val = ''
if (init.indexOf('http') > -1) {
this.val = init
} else {
this.val = init.slice(init.indexOf('?') + 1)
}
this.arr = this.val.split('&')
// console.log(this.val);
}
/**
* @params {string} name
* @params {any} value
*/
append(name, value) {
let tmp = name + '=' + value
this.arr.push(tmp)
return '?' + this.arr.join('&')
}
/**
* @params {string} name
*/
delete(name) {
let res = []
for (let i = 0; i < this.arr.length; i++) {
if (this.arr[i].indexOf(name) != 0) {
res.push(this.arr[i])
}
}
this.arr = res
return '?' + this.arr.join('&')
}
/**
* @returns {Iterator}
*/
*entries() {
let res = []
for (let i = 0; i < this.arr.length; i++) {
let tmp = this.arr[i].split('=')
yield [tmp[0], tmp[1]]
}
}
/**
* @param {(value, key) => void} callback
*/
forEach(callback) {
for (let i = 0; i < this.arr.length; i++) {
let tmp = this.arr[i].split('=')
let obj = {}
callback(tmp[1], tmp[0])
}
}
/**
* @param {string} name
* returns the first value of the name
*/
get(name) {
let res = null
this.arr.filter(value => {
if (res == null && value.indexOf(name) == 0) {
let tmp = value.split('=')
res = tmp && tmp[1]
}
})
return res
}
/**
* @param {string} name
* @return {string[]}
* returns the value list of the name
*/
getAll(name) {
let res = []
this.arr.filter(value => {
if (value.indexOf(name) == 0) {
let tmp = value.split('=')
res.push(tmp[1])
}
})
return res
}
/**
* @params {string} name
* @return {boolean}
*/
has(name) {
let res = false
this.arr.filter(value => {
if (res == false && value.indexOf(name) == 0) {
let tmp = value.split('=')
res = tmp.length > 0 ? true : false
}
})
return res
}
/**
* @return {Iterator}
*/
keys() {
let res = []
for (let i = 0; i < this.arr.length; i++) {
let tmp = this.arr[i].split('=')
if (!res.includes(tmp[0])) {
res.push(tmp[0])
}
}
return res
}
/**
* @param {string} name
* @param {any} value
*/
set(name, value) {
let tmp = 0
for (let i = 0; i < this.arr.length; i++) {
if (this.arr[i].indexOf(name) == 0) {
tmp += 1
this.arr[i] = name + '=' + value
}
}
if (tmp == 0) {
this.arr.push(name + '=' + value)
}
return '?' + this.arr.join('&')
}
// sor all key/value pairs based on the keys
sort() {
let res = []
for (let i = 0; i < this.arr.length; i++) {
let tmp = this.arr[i].split('=')
res.push(tmp)
}
res.sort(function (a, b) {
if (a[0] > b[0]) {
return 1
} else {
return -1
}
})
let sum = []
res.forEach(value => {
sum.push(value.join('='))
})
this.arr = sum
return res
}
/**
* @return {string}
*/
toString() {
return this.arr.join('&')
}
/**
* @return {Iterator} values
*/
values() {
let res = []
for (let i = 0; i < this.arr.length; i++) {
let tmp = this.arr[i].split('=')
if (!res.includes(tmp[0])) {
res.push(tmp[1])
}
}
return res
}
}
手写 chunk
function chunk(arr, size) {
let temp = []
let res = []
arr.forEach((e, index) => {
temp.push(e)
if (temp.length === size || index === arr.length - 1) {
res.push(temp)
temp = []
}
})
return res
}
手写一个异步加法
function asyncAdd(a, b, callback) {
setTimeout(function () {
callback(null, a + b)
}, 500)
}
// 解决方案
// 1. promisify
const promiseAdd = (a, b) =>
new Promise((resolve, reject) => {
asyncAdd(a, b, (err, res) => {
if (err) {
reject(err)
} else {
resolve(res)
}
})
})
// 2. 串行处理
async function serialSum(...args) {
return args.reduce(
(task, now) => task.then(res => promiseAdd(res, now)),
Promise.resolve(0)
)
}
// 3. 并行处理
async function parallelSum(...args) {
if (args.length === 1) return args[0]
const tasks = []
for (let i = 0; i < args.length; i += 2) {
tasks.push(promiseAdd(args[i], args[i + 1] || 0))
}
const results = await Promise.all(tasks)
return parallelSum(...results)
}
// 测试
;(async () => {
console.log('Running...')
const res1 = await serialSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res1)
const res2 = await parallelSum(1, 2, 3, 4, 5, 8, 9, 10, 11, 12)
console.log(res2)
console.log('Done')
})()
原生手写一个 Modal
<style>
#popLayer {
display: none;
background-color: #b3b3b3;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
opacity: 0.6;
}
#popBox {
display: none;
background: #fff;
width: 200px;
height: 200px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 10;
}
#popBox .close {
text-align: right;
margin-right: 5px;
background-color: #f8f8f8;
}
#popBox .close a {
text-decoration: none;
color: #2d2c3b;
}
</style>
<input type="button" name="popBox" value="弹出框" onclick="openBox()" />
<div id="popLayer"></div>
<div id="popBox">
<div class="close">
<a href="javascript:void(0)" onclick="closeBox()">关闭</a>
</div>
<div class="content">我是弹出层</div>
</div>
<script>
var popBox = document.getElementById('popBox')
var popLayer = document.getElementById('popLayer')
function openBox() {
popBox.style.display = 'block'
popLayer.style.display = 'block'
}
/*点击关闭按钮*/
function closeBox() {
popBox.style.display = 'none'
popLayer.style.display = 'none'
}
</script>
转换成树形结构
list2tree
function list2tree(list) {
const map = new Map()
const tree = []
list.forEach((item, index) => {
map.set(item.id, item)
})
list.forEach((item) => {
if (item.pid) {
const parent = map.get(item.pid)
if (parent) {
parent.children = parent.children || []
parent.children.push(item)
}
} else {
tree.push(item)
}
})
return tree
}
function list2tree(list) {
const tree = []
for (const node of list) {
// 如果没有pid就可以认为是根节点
if (!node.pid) {
let p = { ...node }
p.children = getChildren(p.id, list)
tree.push(p)
}
}
return tree
}
function getChildren(id, list) {
const children = []
for (const node of list) {
if (node.pid === id) {
children.push(node)
}
}
// 注意这里遍历的是children
for (const node of children) {
const children = getChildren(node.id, list)
if (children.length) {
node.children = children
}
}
return children
}
function list2tree(list) {
list.forEach((child) => {
const pid = child.pid
if (pid) {
list.forEach((parent) => {
if (parent.id === pid) {
parent.children = parent.children || []
parent.children.push(child)
}
})
}
})
return list.filter((n) => !n.pid)
}